UNPKG

540 kBJavaScriptView Raw
1/**!
2 * @preserve nanogallery2 - javascript photo / video gallery and lightbox
3 * Homepage: http://nanogallery2.nanostudio.org
4 * Sources: https://github.com/nanostudio-org/nanogallery2
5 *
6 * License: GPLv3 and commercial licence
7 *
8 * Requirements:
9 * - jQuery (http://www.jquery.com) - version >= 1.7.1
10 *
11 * Embeded components:
12 * - shifty (https://github.com/jeremyckahn/shifty)
13 * - imagesloaded (https://github.com/desandro/imagesloaded)
14 * - hammer.js (http://hammerjs.github.io/)
15 * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
16 * Tools:
17 * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
18 * - ICO online converter: https://iconverticons.com/online/
19 */
20
21/*
22 V2.3.0
23
24- new loading spinner with support of gif/png files with transparency
25- new default lightbox image transition 'swipe2'
26- optimized thumbnails lazy loading and display animation
27- fixed #130 Joomla3/Bootstrap2 Image Zoom In Bug
28- fixed #131 deep linking to image when only one album loaded
29- fixed #144 copy-n-paste error - thanks to citrin for the fix
30
31*/
32
33
34
35// ###########################################
36// ##### nanogallery2 as a JQUERY PLUGIN #####
37// ###########################################
38
39
40// Expose plugin as an AMD module if AMD loader is present:
41(function (factory) {
42 "use strict";
43 if (typeof define === 'function' && define.amd) {
44 // AMD. Register as an anonymous module.
45 // define('nanogallery2', ['jquery'], factory);
46 define(['jquery'], factory);
47 } else if (typeof exports === 'object' && typeof require === 'function') {
48 // Browserify
49 factory(require('jquery'));
50 } else {
51 // Browser globals
52 factory(jQuery);
53 }
54}(function ($) {
55// ;(function ($) {
56 "use strict";
57
58 //##### TOOLS/HELPERS ####
59
60 // Convert color to RGB/RGBA
61 function ColorHelperToRGB( color ) {
62 var obj = document.getElementById('ngyColorHelperToRGB');
63 if (obj === null) {
64 obj = document.createElement('div');
65 obj.id = "ngyColorHelperToRGB";
66 obj.style.cssText = 'display: none; color:' + color + ';';
67 document.body.appendChild(obj);
68 }
69
70 var rgb = getComputedStyle(obj).color;
71
72 // to get HEX value:
73 // var rgb = getComputedStyle(obj).color.match(/\d+/g);
74 // var r = parseInt(rgb[0]).toString(16);
75 // var g = parseInt(rgb[1]).toString(16);
76 // var b = parseInt(rgb[2]).toString(16);
77 // var hex = '#' + r + g + b;
78
79 return rgb;
80 }
81
82
83 // ##### helper for color handling
84 // - normalise RGB/RGBA/HEX format
85 // - lighten/darken color
86 // Inspired by:
87 // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
88 // http://www.pimptrizkit.com/?t=20%20Shades
89 function ShadeBlendConvert (p, from, to) {
90 var rgba='';
91 if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
92 rgba='a';
93 from='rgb('+from.substring(5);
94 }
95
96 if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
97 //if(!this.sbcRip)this.sbcRip=function(d){
98 function sbcRip(d){
99 var l=d.length,RGB=new Object();
100 if(l>9){
101 d=d.split(",");
102 if(d.length<3||d.length>4)return null;
103 RGB[0]=i(d[0].slice(4)),RGB[1]=i(d[1]),RGB[2]=i(d[2]),RGB[3]=d[3]?parseFloat(d[3]):-1;
104 }else{
105 if(l==8||l==6||l<4)return null;
106 if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
107 d=i(d.slice(1),16),RGB[0]=d>>16&255,RGB[1]=d>>8&255,RGB[2]=d&255,RGB[3]=l==9||l==5?r(((d>>24&255)/255)*10000)/10000:-1;
108 }
109 return RGB;
110 }
111 var i=parseInt,r=Math.round,h=from.length>9,h=typeof(to)=="string"?to.length>9?true:to=="c"?!h:false:h,b=p<0,p=b?p*-1:p,to=to&&to!="c"?to:b?"#000000":"#FFFFFF",f=sbcRip(from),t=sbcRip(to);
112 if(!f||!t)return null;
113 if(h)return "rgb"+rgba+"("+r((t[0]-f[0])*p+f[0])+","+r((t[1]-f[1])*p+f[1])+","+r((t[2]-f[2])*p+f[2])+(f[3]<0&&t[3]<0?")":","+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*10000)/10000:t[3]<0?f[3]:t[3])+")");
114 else return "#"+(0x100000000+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*255):t[3]>-1?r(t[3]*255):f[3]>-1?r(f[3]*255):255)*0x1000000+r((t[0]-f[0])*p+f[0])*0x10000+r((t[1]-f[1])*p+f[1])*0x100+r((t[2]-f[2])*p+f[2])).toString(16).slice(f[3]>-1||t[3]>-1?1:3);
115 }
116
117
118 // ##### clone a javascript object
119 function cloneJSObject( obj ) {
120 if (obj === null || typeof obj !== 'object') {
121 return obj;
122 }
123
124 var temp = obj.constructor(); // give temp the original obj's constructor
125 for (var key in obj) {
126 temp[key] = cloneJSObject(obj[key]);
127 }
128 return temp;
129 }
130
131 // get viewport coordinates and size
132 function getViewport() {
133 var $win = jQuery(window);
134 return {
135 l: $win.scrollLeft(),
136 t: $win.scrollTop(),
137 w: $win.width(),
138 h: $win.height()
139 }
140 }
141
142
143 // avoid if possible (performance issue)
144 function inViewport( $elt, threshold ) {
145 var wp = getViewport(),
146 eltOS = $elt.offset(),
147 th = $elt.outerHeight(true),
148 tw = $elt.outerWidth(true);
149 if( eltOS.top >= (wp.t - threshold)
150 && (eltOS.top + th) <= (wp.t + wp.h + threshold)
151 && eltOS.left >= (wp.l - threshold)
152 && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
153 return true;
154 }
155 else {
156 return false;
157 }
158 }
159
160 // avoid if possible (performance issue)
161 function inViewportVert( $elt, threshold ) {
162 var wp = getViewport(),
163 eltOS = $elt.offset(),
164 th = $elt.outerHeight(true);
165 //var tw=$elt.outerWidth(true);
166
167 if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
168
169 if( eltOS.top >= (wp.t)
170 && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
171 return true;
172 }
173 else {
174 return false;
175 }
176 }
177
178
179 // set z-index to display 2 elements on top of all others
180 function set2ElementsOnTop( start, elt1, elt2 ) {
181 var highest_index = 0;
182 if( start=='' ) { start= '*'; }
183 jQuery(start).each(function() {
184 var cur = parseInt(jQuery(this).css('z-index'));
185 highest_index = cur > highest_index ? cur : highest_index;
186 });
187 highest_index++;
188 jQuery(elt2).css('z-index',highest_index+1);
189 jQuery(elt1).css('z-index',highest_index);
190 }
191
192 // set z-index to display element on top of all others
193 function setElementOnTop( start, elt ) {
194 var highest_index = 0;
195 if( start == '' ) { start = '*'; }
196 jQuery(start).each(function() {
197 var cur = parseInt(jQuery(this).css('z-index'));
198 highest_index = cur > highest_index ? cur : highest_index;
199 });
200 highest_index++;
201 jQuery(elt).css('z-index',highest_index);
202 }
203
204 // return the real type of the object
205 var toType = function( obj ) {
206 // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
207 return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
208 };
209
210
211 $.nanogallery2 = function (elt, options) {
212 // To avoid scope issues, use '_this' instead of 'this'
213 // to reference this class from internal events and functions.
214 var _this = this;
215
216 // Access to jQuery and DOM versions of element
217 _this.$e = jQuery(elt);
218 _this.e = elt;
219
220 // Add a reverse reference to the DOM object
221 _this.$e.data('nanogallery2data', _this);
222
223 _this.init = function () {
224
225 // define these global objects only once per HTML page
226 if (typeof window.NGY2Item === 'undefined') {
227
228 window.NGY2Tools = (function () {
229
230 function NGY2Tools() {
231 var nextId = 1; // private static --> all instances
232 }
233
234 // check album name - albumList/blackList/whiteList
235 NGY2Tools.FilterAlbumName = function( title, ID ) {
236 var s = title.toUpperCase();
237 if( this.albumList.length > 0 ) {
238 for( var j=0; j < this.albumList.length; j++) {
239 if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
240 return true;
241 }
242 }
243 }
244 else {
245 var found = false;
246 if( this.whiteList !== null ) {
247 //whiteList : authorize only album cointaining one of the specified keyword in the title
248 for( var j = 0; j < this.whiteList.length; j++) {
249 if( s.indexOf(this.whiteList[j]) !== -1 ) {
250 found = true;
251 }
252 }
253 if( !found ) { return false; }
254 }
255
256
257 if( this.blackList !== null ) {
258 //blackList : ignore album cointaining one of the specified keyword in the title
259 for( var j = 0; j < this.blackList.length; j++) {
260 if( s.indexOf(this.blackList[j]) !== -1 ) {
261 return false;
262 }
263 }
264 }
265 return true;
266 }
267 };
268
269
270 /** @function nanoAlert */
271 /* Display an alert message in a specific element */
272 NGY2Tools.NanoAlert = function(context, msg, verbose) {
273 NGY2Tools.NanoConsoleLog.call(context, msg);
274 if( context.$E.conConsole != null ) {
275 context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
276 if( verbose == false ) {
277 context.$E.conConsole.append('<p>' + msg + '</p>');
278 }
279 else {
280 context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
281 }
282 }
283 };
284
285
286 /** @function NanoConsoleLog */
287 /* write message to the browser console */
288 NGY2Tools.NanoConsoleLog = function(context, msg) {
289 if (window.console) { console.log('nanogallery2: ' + msg + ' ['+context.baseEltID+']'); }
290 };
291
292
293 /** @function PreloaderDisplay() */
294 /* Display/hide preloader */
295 NGY2Tools.PreloaderDisplay = function(display) {
296 if( display === true ) {
297 this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
298 }
299 else {
300 this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
301 }
302 };
303
304 //+ Jonas Raoni Soares Silva
305 //@ http://jsfromhell.com/array/shuffle [v1.0]
306 NGY2Tools.AreaShuffle = function (o) {
307 for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
308 return o;
309 };
310
311 /** @function GetImageTitleFromURL() */
312 /* retrieve filemane */
313 NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
314 if( this.O.thumbnailLabel.get('title') == '%filename' ) {
315 return (imageURL.split('/').pop()).replace('_',' ');
316 }
317
318 if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
319 var s=imageURL.split('/').pop();
320 return (s.split('.').shift()).replace('_',' ');
321 }
322 // return imageURL;
323 return '';
324 };
325
326
327 /** @function AlbumPostProcess() */
328 /* post process one album based on plugin general parameters --> sorting/maxItems*/
329 NGY2Tools.AlbumPostProcess = function(albumID) {
330
331 // this function can probably be optimized....
332
333 var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
334 var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
335
336 if( sortOrder != '' || maxItems > 0 ) {
337
338 // copy album's items to a new array
339 var currentAlbum = this.I.filter( function( obj ) {
340 return( obj.albumID == albumID && obj.kind != 'albumUp' );
341 });
342
343 // sorting options
344 switch( sortOrder ) {
345 case 'RANDOM':
346 currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
347 break;
348 case 'REVERSED':
349 currentAlbum = currentAlbum.reverse();
350 break;
351 case 'TITLEASC':
352 currentAlbum.sort(function (a, b) {
353 return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
354 });
355 break;
356 case 'TITLEDESC':
357 currentAlbum.sort(function (a, b) {
358 return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
359 });
360 break;
361 }
362
363 // max Items
364 if( maxItems > 0 && currentAlbum.length > maxItems ) {
365 currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
366 }
367
368 // remove the albums's items from the global items array
369 this.I.removeIf( function( obj ) {
370 return( obj.albumID == albumID && obj.kind != 'albumUp' );
371 });
372
373 // add the sorted items back to the album
374 this.I.push.apply(this.I, currentAlbum);
375
376 }
377 };
378
379 return NGY2Tools;
380 })();
381
382 // ====================
383 // ===== NGY2Item =====
384 // ====================
385 window.NGY2Item = (function() {
386 var nextId = 1; // private static --> all instances
387
388 // constructor
389 function NGY2Item( itemID ) {
390 //window.NGY2Item = function( itemID ) {
391 var ID = 0; // private
392
393 // public (this instance only)
394 if( itemID === undefined || itemID === null ) {
395 ID = nextId++;
396 }
397 else {
398 ID = itemID;
399 }
400 this.GetID = function () { return ID; };
401
402 // public
403 this.kind = ''; // 'image', 'album' or 'albumUp'
404 this.mediaKind = 'img'; // 'img', 'iframe'
405 this.mediaMarkup = '';
406 this.G = null; // pointer to global instance
407 this.title = ''; // image title
408 this.description = ''; // image description
409 this.albumID = 0; // ID of the parent album
410 this.src = ''; // full sized image URL
411 this.width = 0; // image width
412 this.height = 0; // image height
413 this.destinationURL = ''; // thumbnail destination URL --> open URL instead of displaying image
414 this.downloadURL = ''; // thumbnail download URL --> specify the image for download button
415 this.author = ''; // image/album author
416 this.left= 0; // store position to animate from old to new
417 this.top= 0;
418 this.width= 0; // store size to avoid setting width/height if not required
419 this.height= 0;
420 this.resizedContentWidth= 0; // store size of content (image) to avoid setting width/height if not required
421 this.resizedContentHeight= 0;
422 this.thumbs = { // URLs and sizes for user defined
423 url: { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
424 width: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0 , sm: 0, me: 0, la: 0, xl: 0 } },
425 height: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0, sm: 0, me: 0, la: 0, xl: 0 } }
426 };
427 this.thumbnailImgRevealed = false; // thumbnail image already revealed
428 this.imageDominantColors = null; // base64 GIF
429 this.imageDominantColor = null; // HEX RGB
430 this.featured = false; // featured element
431 this.flickrThumbSizes = {}; // store URLs for all available thumbnail sizes (flickr)
432 this.picasaThumbs = null; // store URLs and sizes
433 this.hovered = false; // is the thumbnail currently hovered?
434 this.hoverInitDone = false;
435 this.contentIsLoaded = false; // album: are items already loaded?
436 this.contentLength = 0; // album: number of items (real number of items in memory)
437 this.numberItems = 0; // album: number of items (value returned by data source)
438 this.mediaNumber = 0; // media number in the album
439 this.imageCounter = 0; // number of images in an album
440 this.eltTransform = []; // store the CSS transformations
441 this.eltFilter = []; // store the CSS filters
442 this.eltEffect = []; // store data about hover effects animations
443 this.authkey = ''; // for Google Photos private (hidden) albums
444 this.paginationLastPage = 0; // for albums
445 this.paginationLastWidth = 0; // for albums
446 this.customData = {};
447 this.selected = false;
448 this.imageWidth = 0; // image natural (real) width
449 this.imageHeight = 0; // image natural (real) height
450 this.$elt = null; // pointer to the corresponding DOM element
451 this.$Elts = []; // cached pointers to the thumbnail content -> to avoid jQuery().find()
452 this.tags = []; // list of tags of the current item
453 this.albumTagList = []; // list of all the tags of the items contained in the current album
454 this.albumTagListSel = []; // list of currently selected tags (only for albums)
455 this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
456 this.deleted = false; // item is deleted -> do not display anymore
457 }
458
459 // public static
460
461 NGY2Item.Get = function( instance, ID ) {
462 var l = instance.I.length;
463 for( var i = 0; i < l; i++ ) {
464 if( instance.I[i].GetID() == ID ) {
465 return instance.I[i];
466 }
467 }
468 return null;
469 };
470
471 NGY2Item.GetIdx = function( instance, ID ) {
472 var l = instance.I.length;
473 for( var i = 0; i < l; i++ ) {
474 if( instance.I[i].GetID() == ID ) {
475 return i;
476 }
477 }
478 return -1;
479 };
480
481 // create new item (image, album or albumUp)
482 NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
483 var album = NGY2Item.Get( instance, albumID );
484
485 if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]' ) {
486 if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
487 // add navigation thumbnail (album up)
488 var item = new NGY2Item('0');
489 instance.I.push( item );
490 album.contentLength += 1;
491 item.title = 'UP';
492 item.albumID = albumID;
493 item.kind = 'albumUp';
494 item.G = instance;
495
496 jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
497 jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
498 }
499 }
500
501 var item = NGY2Item.Get(instance, ID);
502 if( item === null ){
503 // create a new item (otherwise, just update the existing one)
504 item = new NGY2Item(ID);
505 instance.I.push(item);
506 if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
507 album.contentLength+=1;
508 }
509 }
510 item.G = instance;
511
512 item.albumID = albumID;
513 item.kind = kind;
514 if( kind == 'image' ) {
515 album.imageCounter += 1;
516 item.mediaNumber = album.imageCounter;
517 }
518
519 // check keyword to find features images/albums
520 var kw = instance.O.thumbnailFeaturedKeyword;
521 if( kw != '' ) {
522 // check if item featured based on a keyword in the title or in the description
523 kw = kw.toUpperCase();
524 var p = title.toUpperCase().indexOf(kw);
525 if( p > -1) {
526 item.featured = true;
527 // remove keyword case unsensitive
528 title = title.substring(0, p) + title.substring(p+kw.length, title.length);
529 }
530 p = description.toUpperCase().indexOf(kw);
531 if( p > -1) {
532 item.featured=true;
533 // remove keyword case unsensitive
534 description=description.substring(0, p) + description.substring(p + kw.length, description.length);
535 }
536 }
537
538 // TAGS
539 // if( instance.galleryFilterTags.Get() != false ) {
540 // if( instance.galleryFilterTags.Get() == true ) {
541 // if( tags != '' && tags != undefined ) {
542 // use set tags
543 // item.setTags(tags.split(' '));
544 // }
545 // }
546 // else {
547 // extract tags starting with # (in title)
548 if( typeof instance.galleryFilterTags.Get() == 'string' ) {
549 switch( instance.galleryFilterTags.Get().toUpperCase() ) {
550 case 'TITLE':
551 var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
552 var tags = "";
553 while (match = re.exec(title)) {
554 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
555 }
556 item.setTags(matches); //tags;
557 title = title.split('#').join(''); //replaceall
558 break;
559 case 'DESCRIPTION':
560 var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
561 var tags = "";
562 while (match = re.exec(description)) {
563 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
564 }
565 item.setTags(matches); //tags;
566 description = description.split('#').join(''); //replaceall
567 break;
568 }
569 }
570 else {
571 if( tags != '' && tags != undefined ) {
572 // use set tags
573 item.setTags(tags.split(' '));
574 }
575 }
576 // }
577 // }
578
579 // set (maybe modified) fields title and description
580 item.title = escapeHtml(instance, title);
581 item.description = escapeHtml(instance, description);
582 return item;
583 };
584
585
586 // removes logically current item
587 NGY2Item.prototype.delete = function( ) {
588 this.deleted = true;
589
590 // update content length of parent album
591 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
592 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
593
594 // check if in DOM and removes it
595 var nbTn = this.G.GOM.items.length;
596 var ID = this.GetID();
597 var foundIdx = -1;
598 var foundGOMidx = -1;
599 for( var i = 0; i < nbTn ; i++ ) {
600 var curTn = this.G.GOM.items[i];
601 var item=this.G.I[curTn.thumbnailIdx];
602 if( item.GetID() == ID ) {
603 // FOUND
604 if( !curTn.neverDisplayed ) {
605 foundIdx= curTn.thumbnailIdx;
606 foundGOMidx= i;
607 }
608 }
609 else {
610 if( foundIdx != -1 ) {
611 if( !curTn.neverDisplayed ) {
612 // update index value
613 item.$getElt('.nGY2GThumbnail').data('index', i-1);
614 item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
615 }
616 }
617 }
618 }
619 if( foundIdx != -1 ) {
620 // delete item in GOM and delete thumbnail
621 var G = this.G;
622 if( this.selected == true ) {
623 this.selected = false;
624 G.GOM.nbSelected--; // update the global counter
625 }
626 if( G.I[foundIdx].$elt !== null ) {
627 G.I[foundIdx].$elt.remove(); // delete thumbnail DOM object
628 }
629 G.GOM.items.splice(foundGOMidx, 1); // delete in GOM
630 if( G.GOM.lastDisplayedIdx != -1 ) {
631 G.GOM.lastDisplayedIdx-=1;
632 }
633 }
634 }
635
636 NGY2Item.prototype.addToGOM = function( ) {
637 // retrieve index
638 var ID = this.GetID();
639 var l = this.G.I.length;
640 for( var idx = 0; idx < l; idx++ ) {
641 var item = this.G.I[idx];
642 if( item.GetID() == ID ) {
643 var w = item.thumbImg().width;
644 var h = item.thumbImg().height;
645 // set default size if required
646 if( h == 0 ) {
647 h = this.G.tn.defaultSize.getHeight();
648 }
649 if( w == 0 ) {
650 w = this.G.tn.defaultSize.getWidth();
651 }
652 // add to GOM -> will be displayed on next refresh/resize
653 var tn = new this.G.GOM.GTn(idx, w, h);
654 this.G.GOM.items.push(tn);
655 break;
656 }
657 }
658
659 }
660
661
662 // function to avoid XSS issue - Cross Site Scripting
663 // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
664 var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
665 function escapeHtml (instance, string) {
666 if( instance.O.allowHTMLinData == true ) {
667 return string;
668 }
669 else {
670 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
671 return entityMap[s];
672 });
673 }
674 }
675
676
677 NGY2Item.get_nextId = function () {
678 return nextId;
679 };
680
681 //=== public (shared across instances)
682
683 //--- cached sub elements
684 NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
685 if( this.$elt == null ) { return null; }
686 if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
687 return this.$Elts[elt];
688 }
689 else {
690 if( elt == '.nGY2GThumbnail' ) {
691 this.$Elts[elt]=this.$elt;
692 }
693 else {
694 this.$Elts[elt]=this.$elt.find(elt);
695 }
696 return this.$Elts[elt];
697 }
698 };
699
700 // remove one element (in DOM and in cache)
701 NGY2Item.prototype.removeElt = function( elt ) {
702 if( this.$elt == null ) { return; }
703 if( this.$Elts[elt] == undefined) { return; }
704 this.$Elts[elt].remove();
705 var index = this.$Elts.indexOf(elt);
706 this.$Elts.splice(index, 1);
707 };
708
709 //--- returns the album containing the item
710 NGY2Item.prototype.album = function() {
711 return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
712 };
713
714 //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
715 NGY2Item.prototype.mediaTransition = function( ) {
716 if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
717 return true;
718 }
719 return false;
720 };
721
722 //--- set one image (url and size)
723 NGY2Item.prototype.imageSet = function( src, w, h ) {
724 this.src = src;
725 this.width = w;
726 this.height = h;
727 };
728
729 //--- set one thumbnail (url and size) - screenSize and level are optionnal
730 NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
731 var lst=['xs','sm','me','la','xl'];
732 if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
733 for( var i=0; i< lst.length; i++ ) {
734 if( typeof level === 'undefined' || level == '' ) {
735 this.thumbs.url.l1[lst[i]]=src;
736 this.thumbs.height.l1[lst[i]]=h;
737 this.thumbs.width.l1[lst[i]]=w;
738 this.thumbs.url.lN[lst[i]]=src;
739 this.thumbs.height.lN[lst[i]]=h;
740 this.thumbs.width.lN[lst[i]]=w;
741 }
742 else {
743 this.thumbs.url[level][lst[i]]=src;
744 this.thumbs.height[level][lst[i]]=h;
745 this.thumbs.width[level][lst[i]]=w;
746 }
747 }
748 }
749 else {
750 if( typeof level === 'undefined' || level == '' || level == null ) {
751 this.thumbs.url.l1[screenSize]=src;
752 this.thumbs.height.l1[screenSize]=h;
753 this.thumbs.width.l1[screenSize]=w;
754 this.thumbs.url.lN[screenSize]=src;
755 this.thumbs.height.lN[screenSize]=h;
756 this.thumbs.width.lN[screenSize]=w;
757 }
758 else {
759 this.thumbs.url[level][screenSize]=src;
760 this.thumbs.height[level][screenSize]=h;
761 this.thumbs.width[level][screenSize]=w;
762 }
763 }
764
765 var lst=['xs','sm','me','la','xl'];
766 for( var i=0; i< lst.length; i++ ) {
767 this.thumbs.height.l1[lst[i]]=h;
768 }
769 for( var i=0; i< lst.length; i++ ) {
770 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
771 this.thumbs.height.lN[lst[i]]=h;
772 }
773 }
774 };
775
776 //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
777 NGY2Item.prototype.thumbSetImgHeight = function( h ) {
778 var lst=['xs','sm','me','la','xl'];
779 for( var i=0; i< lst.length; i++ ) {
780 if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
781 this.thumbs.height.l1[lst[i]]=h;
782 }
783 }
784 for( var i=0; i< lst.length; i++ ) {
785 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
786 this.thumbs.height.lN[lst[i]]=h;
787 }
788 }
789 };
790
791 //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
792 NGY2Item.prototype.thumbSetImgWidth = function( w ) {
793 var lst=['xs','sm','me','la','xl'];
794 for( var i=0; i< lst.length; i++ ) {
795 if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
796 this.thumbs.width.l1[lst[i]]=w;
797 }
798 }
799 for( var i=0; i< lst.length; i++ ) {
800 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
801 this.thumbs.width.lN[lst[i]]=w;
802 }
803 }
804 };
805
806 //--- Returns Thumbnail image (depending of the screen resolution)
807 NGY2Item.prototype.thumbImg = function () {
808 var tnImg = { src: '', width: 0, height: 0 };
809
810 if( this.title == 'image gallery by nanogallery2 [build]' ) {
811 tnImg.src = this.G.emptyGif;
812 tnImg.url = this.G.emptyGif;
813 return tnImg;
814 }
815 tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
816 tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
817 tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
818 return tnImg;
819 };
820
821 //--- Set tags to items and add these tags to the album
822 NGY2Item.prototype.setTags = function( tags ) {
823 if( tags.length > 0 ) {
824 this.tags = tags;
825 var lstTags = this.album().albumTagList;
826 for( var i = 0; i < tags.length; i++ ) {
827 var tfound = false;
828 for( var j = 0; j < lstTags.length; j++ ) {
829 if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
830 tfound = true;
831 }
832 }
833 if( tfound == false) {
834 this.album().albumTagList.push(tags[i])
835 this.album().albumTagListSel.push(tags[i])
836 }
837 }
838 }
839 };
840
841 //--- check if 1 of current item's tags is selected (tag filter)
842 NGY2Item.prototype.checkTagFilter = function() {
843 if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
844 if( this.G.O.thumbnailLevelUp && this.kind=='albumUp' ) {
845 return true;
846 }
847 var found = false;
848 var lstTags = this.album().albumTagListSel;
849 for( var i = 0; i < this.tags.length; i++ ) {
850 for( var j = 0; j < lstTags.length; j++ ) {
851 if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
852 found = true;
853 break;
854 }
855 }
856 }
857 return found;
858 }
859 else
860 return true;
861 };
862
863 //--- check if 1 of current item's tags is found using API search
864 NGY2Item.prototype.isSearchTagFound = function() {
865 if( this.G.GOM.albumSearchTags == '' ) { return true; }
866 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
867
868 //var lstTags=this.album().albumTagListSel;
869 for( var i = 0; i < this.tags.length; i++ ) {
870 if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
871 return true;
872 }
873 }
874 return false;
875 };
876
877 //--- set the URL of the media to display in the viewer
878 //--- markup is defined for images
879 NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
880 this.src = url;
881 this.mediaKind = mediaKind;
882 if( mediaKind == 'img' ) {
883 this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
884 }
885 };
886
887
888 //--- check if current item should be displayed
889 NGY2Item.prototype.isToDisplay = function( albumID ) {
890 return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
891 };
892
893
894
895 //--- returns the number of items of the current album
896 //--- count using tags filter
897 NGY2Item.prototype.getContentLength = function( filterTags ) {
898 if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
899 return this.contentLength;
900 }
901 else {
902 var l = this.G.I.length;
903 var cnt = 0;
904 var albumID = this.GetID();
905 for( var idx = 0; idx < l; idx++ ) {
906 var item = this.G.I[idx];
907 if( item.isToDisplay(albumID) ) {
908 cnt++;
909 }
910 }
911 return cnt;
912 }
913 };
914
915 NGY2Item.prototype.isSearchFound = function() {
916 if( this.G.GOM.albumSearch != '' ) {
917 if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
918 return false;
919 }
920 }
921 return true;
922 }
923
924
925 //--- for future use...
926 NGY2Item.prototype.responsiveURL = function () {
927 var url = '';
928 switch(this.G.O.kind) {
929 case '':
930 url = this.src;
931 break;
932 case 'flickr':
933 url = this.src;
934 break;
935 case 'picasa':
936 case 'google':
937 case 'google2':
938 default:
939 url = this.src;
940 break;
941 }
942 return url;
943 };
944
945
946 //--- Reveal the thumbnail image with animation on opacity
947 NGY2Item.prototype.ThumbnailImageReveal = function () {
948
949 if( this.thumbnailImgRevealed == false ) {
950 this.thumbnailImgRevealed = true;
951 new NGTweenable().tween({
952 from: { opacity: 0 },
953 to: { opacity: 1 },
954 attachment: { item: this },
955 delay: 30,
956 duration: 400,
957 easing: 'easeOutQuart',
958 step: function (state, att) {
959 var $e=att.item.$getElt('.nGY2TnImg');
960 if( $e != null ) {
961 $e.css( state );
962 }
963 }
964 });
965 }
966 };
967
968
969 // In case of thumbnails with stacks - apply a percent to a value which include a unit
970 function ValueApplyPercent( str, percent ) {
971 str=String(str);
972 if( str === '0' || percent == 1 ) { return str; }
973 var n = Number(str.replace(/[a-zA-Z]/g, ''));
974 var ar = str.match(/([^\-0-9\.]+)/g);
975 var a = '';
976 if( ar != null && ar.length > 0 ) {
977 a = ar.join();
978 }
979
980 if( isNaN(n) || n == 0 ) {
981 return str;
982 }
983
984 n = n * percent;
985 return n + a;
986 }
987
988 //--- 2D/3D CSS transform - apply the cached value to element
989 NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
990 var obj = this.eltTransform[eltClass];
991
992 if( eltClass == '.nGY2GThumbnail' ) {
993 // thumbnail
994 var nbStacks = obj.$elt.length-1;
995 var pTranslateX = 1;
996 var pTranslateY = 1;
997 var pTranslateZ = 1;
998 var pTranslate = 1;
999 var pRotateX = 1;
1000 var pRotateY = 1;
1001 var pRotateZ = 1;
1002 var pRotate = 1;
1003 var pScale = 1;
1004 for( var n = nbStacks; n >= 0; n-- ) {
1005 // units must be given with
1006 var v = 'translateX(' + ValueApplyPercent(obj.translateX,pTranslateX) + ') translateY(' + ValueApplyPercent(obj.translateY,pTranslateY) + ') translateZ(' + ValueApplyPercent(obj.translateZ,pTranslateZ) + ') scale(' + ValueApplyPercent(obj.scale,pScale) + ') translate(' + ValueApplyPercent(obj.translate,pTranslate) + ')';
1007 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1008 v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
1009 }
1010 else {
1011 v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
1012 }
1013 obj.$elt[n].style[this.G.CSStransformName] = v;
1014
1015 if( nbStacks > 0 ) {
1016 // apply a percent to the stack elements
1017 pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
1018 pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
1019 pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
1020 pRotateX -= this.G.tn.opt.Get('stacksRotateX');
1021 pRotateY -= this.G.tn.opt.Get('stacksRotateY');
1022 pRotateZ -= this.G.tn.opt.Get('stacksRotateZ');
1023 pScale -= this.G.tn.opt.Get('stacksScale');
1024 }
1025 }
1026 }
1027 else {
1028 // thumbnail sub element
1029 if( obj.$elt != null ) {
1030 for( var n = 0; n < obj.$elt.length; n++ ) {
1031 if( obj.$elt[n] != undefined ) {
1032 // units must be given with
1033 var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
1034 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1035 v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
1036 }
1037 else {
1038 v += ' rotate(' + obj.rotateZ + ')';
1039 }
1040 obj.$elt[n].style[this.G.CSStransformName] = v;
1041 }
1042 }
1043 }
1044 }
1045 };
1046
1047 //--- 2D/3D CSS transform - set a value in cache
1048 NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
1049 if( this.eltTransform[eltClass] == undefined ) {
1050 this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
1051 this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
1052 }
1053 this.eltTransform[eltClass][transform] = value;
1054 if( forceRefresh === true ) {
1055 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1056 }
1057 };
1058
1059 //--- CSS Filters - apply the cached value to element
1060 NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
1061 var obj = this.eltFilter[eltClass];
1062 var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
1063 if( obj.$elt != null ) {
1064 for( var n = 0; n < obj.$elt.length; n++ ) {
1065 if( obj.$elt[n] != undefined ) {
1066 obj.$elt[n].style.WebkitFilter = v;
1067 obj.$elt[n].style.filter = v;
1068 }
1069 }
1070 }
1071 };
1072
1073 //--- CSS Filters - set a value in cache
1074 NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
1075 if( this.eltFilter[eltClass] == undefined ) {
1076 this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
1077 this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
1078 }
1079 this.eltFilter[eltClass][filter] = value;
1080 if( forceRefresh === true ) {
1081 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1082 }
1083 };
1084
1085 //--- thumbnail hover animation
1086 NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
1087 if( this.$getElt() == null ) { return; }
1088
1089 var context = {};
1090 context.G = this.G;
1091 context.item = this;
1092 context.effect = effect;
1093 context.hoverIn = hoverIn;
1094 context.cssKind = '';
1095 if( hoverIn ) {
1096 // HOVER IN
1097
1098 if( this.eltEffect[effect.element] == undefined ) {
1099 this.eltEffect[effect.element] = [];
1100 }
1101 if( this.eltEffect[effect.element][effect.type] == undefined ) {
1102 this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
1103 }
1104 if( effect.firstKeyframe ) {
1105 // store initial and current value -> for use in the back animation
1106 this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
1107 }
1108
1109 context.animeFrom = effect.from;
1110 context.animeTo = effect.to;
1111 context.animeDuration = parseInt(effect.duration);
1112 context.animeDelay = 30 + parseInt(effect.delay + delay); // 30ms is a default delay to avoid conflict with other initializations
1113 context.animeEasing = effect.easing;
1114 }
1115 else {
1116 // HOVER OUT
1117 if( effect.firstKeyframe ) {
1118 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1119 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1120 // context.animeTo=effect.from;
1121 }
1122 else {
1123 // context.animeFrom=effect.from;
1124 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1125 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1126 // context.animeTo=effect.to;
1127
1128 }
1129
1130 context.animeDuration = parseInt(effect.durationBack);
1131 context.animeDelay = 30 + parseInt(effect.delayBack + delay); // 30ms is a default delay to avoid conflict with other initializations
1132 context.animeEasing = effect.easingBack;
1133 }
1134
1135
1136 // detect if animation on CSS transform
1137 var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
1138 for( var i = 0; i < transform.length; i++ ) {
1139 if( effect.type == transform[i] ) {
1140 context.cssKind = 'transform';
1141 break;
1142 }
1143 }
1144
1145 // detect if animation on CSS filter
1146 var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
1147 for( var i = 0; i < filter.length; i++ ) {
1148 if( effect.type == filter[i] ) {
1149 context.cssKind = 'filter';
1150 break;
1151 }
1152 }
1153 // handle some special cases
1154 if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
1155 this.G.GOM.lastZIndex++;
1156 this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
1157 // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
1158 }
1159
1160 // animation
1161 var tweenable = new NGTweenable();
1162 context.tweenable=tweenable;
1163 tweenable.tween({
1164 attachment: context,
1165 from: { 'v': context.animeFrom },
1166 to: { 'v': context.animeTo },
1167 duration: context.animeDuration, //parseInt(effect.duration),
1168 delay: context.animeDelay, //parseInt(effect.delay),
1169 easing: context.animeEasing, //'easeOutQuart',
1170
1171 step: function (state, att) {
1172 if( att.item.$getElt() == null ) {
1173 // the thumbnail may be destroyed since the start of the animation
1174 att.tweenable.stop(false);
1175 // att.tweenable.dispose();
1176 return;
1177 }
1178 if( att.hoverIn && !att.item.hovered ) {
1179 // thumbnail no more hovered
1180 att.tweenable.stop(false);
1181 // att.tweenable.dispose();
1182 return;
1183 }
1184
1185 if( att.G.VOM.viewerDisplayed ) {
1186 att.tweenable.stop(false);
1187 // att.tweenable.dispose();
1188 return;
1189 }
1190
1191 // test if in delay phase
1192 if( state.v == att.animeFrom ) { return; }
1193
1194 switch( att.cssKind ) {
1195 case 'transform':
1196 // window.ng_draf( function() {
1197 att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
1198 att.item.CSSTransformApply( att.effect.element );
1199 // });
1200 break;
1201 case 'filter':
1202 // window.ng_draf( function() {
1203 att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
1204 att.item.CSSFilterApply( att.effect.element );
1205 // });
1206 break;
1207 default:
1208 var v=state.v;
1209 if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
1210 // to remove values after the dot (not supported by RGB/RGBA)
1211 // v=ngtinycolor(state.v).toRgbString();
1212 v = ShadeBlendConvert(0, v);
1213 }
1214 // window.ng_draf( function() {
1215 att.item.$getElt( att.effect.element ).css( att.effect.type, v );
1216 // });
1217 break;
1218 }
1219 if( hoverIn ) {
1220 // store value for back animation
1221 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1222 }
1223 },
1224
1225 finish: function (state, att) {
1226 if( hoverIn ) {
1227 // store value for back animation
1228 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1229 }
1230
1231 if( att.item.$getElt() == null ) {
1232 // the thumbnail may be destroyed since the start of the animation
1233 return;
1234 }
1235 if( att.hoverIn && !att.item.hovered ) {
1236 // thumbnail no more hovered
1237 return;
1238 }
1239
1240 if( att.G.VOM.viewerDisplayed ) {
1241 return;
1242 }
1243
1244 // window.ng_draf( function() {
1245 switch( att.cssKind ) {
1246 case 'transform':
1247 att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
1248 att.item.CSSTransformApply(att.effect.element);
1249 break;
1250 case 'filter':
1251 att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
1252 att.item.CSSFilterApply(att.effect.element);
1253 break;
1254 default:
1255 att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
1256 break;
1257 }
1258 // });
1259 }
1260 });
1261 };
1262
1263 return NGY2Item;
1264 })();
1265
1266 }
1267
1268 _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
1269 // Initialization code
1270 _this.nG2 = null;
1271 _this.nG2 = new nanoGALLERY2();
1272 _this.nG2.initiateGallery2(_this.e, _this.options );
1273
1274 };
1275
1276 // PUBLIC EXPOSED METHODS
1277 _this.test = function() {
1278 //alert('test');
1279 // console.dir(_this.nG.G.I.length);
1280 // console.dir(_this.nG);
1281 //privateTest();
1282 }
1283
1284
1285 // Run initializer
1286 _this.init();
1287 };
1288
1289 jQuery.nanogallery2.defaultOptions = {
1290 kind : '',
1291 userID : '',
1292 photoset : '',
1293 album: '',
1294 blackList : 'scrapbook|profil|auto backup',
1295 whiteList : '',
1296 albumList : '',
1297 albumList2 : null,
1298 RTL : false,
1299 poogleplusUseUrlCrossDomain : true,
1300 flickrSkipOriginal : true,
1301 breadcrumbAutoHideTopLevel : true,
1302 displayBreadcrumb : true,
1303 breadcrumbOnlyCurrentLevel : true,
1304 breadcrumbHideIcons : true,
1305 theme : 'nGY2',
1306 galleryTheme : 'dark',
1307 viewerTheme : 'dark',
1308 items : null,
1309 itemsBaseURL : '',
1310 thumbnailSelectable : false,
1311 dataProvider: '',
1312 dataCharset: 'Latin',
1313 allowHTMLinData: false,
1314 locationHash : true,
1315 slideshowDelay : 3000,
1316 slideshowAutoStart : false,
1317
1318 debugMode: false,
1319
1320 galleryDisplayMoreStep : 2,
1321 galleryDisplayMode : 'fullContent',
1322 galleryL1DisplayMode : null,
1323 galleryPaginationMode : 'rectangles', // 'dots', 'rectangles', 'numbers'
1324 // galleryThumbnailsDisplayDelay : 2000,
1325 galleryMaxRows : 2,
1326 galleryL1MaxRows : null,
1327 galleryLastRowFull: false,
1328 galleryLayoutEngine : 'default',
1329 paginationSwipe: true,
1330 paginationVisiblePages : 10,
1331 // paginationSwipeSensibilityVert : 10,
1332 galleryFilterTags : false, // possible values: false, true, 'title', 'description'
1333 galleryL1FilterTags : null, // possible values: false, true, 'title', 'description'
1334 galleryMaxItems : 0, // maximum number of items per album --> only flickr, google+, nano_photos_provider2
1335 galleryL1MaxItems : null, // maximum number of items per gallery page --> only flickr, google+, nano_photos_provider2
1336 gallerySorting : '',
1337 galleryL1Sorting : null,
1338 galleryDisplayTransition : 'none',
1339 galleryL1DisplayTransition : null,
1340 galleryDisplayTransitionDuration : 1000,
1341 galleryL1DisplayTransitionDuration : null,
1342 galleryResizeAnimation : true,
1343 galleryRenderDelay : 60,
1344
1345 thumbnailCrop : true,
1346 thumbnailL1Crop : null,
1347 thumbnailCropScaleFactor : 1.5,
1348 thumbnailLevelUp : false,
1349 thumbnailAlignment : 'fillWidth',
1350 thumbnailWidth : 300,
1351 thumbnailL1Width : null,
1352 thumbnailHeight : 200,
1353 thumbnailL1Height : null,
1354 thumbnailBaseGridHeight : 0,
1355 thumbnailL1BaseGridHeight : null,
1356 thumbnailGutterWidth : 2,
1357 thumbnailL1GutterWidth : null,
1358 thumbnailGutterHeight : 2,
1359 thumbnailL1GutterHeight : null,
1360 thumbnailBorderVertical : 2,
1361 thumbnailBorderHorizontal : 2,
1362 thumbnailFeaturedKeyword : '*featured',
1363 thumbnailAlbumDisplayImage : false,
1364 thumbnailHoverEffect2 : 'toolsAppear',
1365 thumbnailBuildInit2 : '',
1366 thumbnailStacks : 0,
1367 thumbnailL1Stacks : null,
1368 thumbnailStacksTranslateX : 0,
1369 thumbnailL1StacksTranslateX : null,
1370 thumbnailStacksTranslateY : 0,
1371 thumbnailL1StacksTranslateY : null,
1372 thumbnailStacksTranslateZ : 0,
1373 thumbnailL1StacksTranslateZ : null,
1374 thumbnailStacksRotateX : 0,
1375 thumbnailL1StacksRotateX : null,
1376 thumbnailStacksRotateY : 0,
1377 thumbnailL1StacksRotateY : null,
1378 thumbnailStacksRotateZ : 0,
1379 thumbnailL1StacksRotateZ : null,
1380 thumbnailStacksScale : 0,
1381 thumbnailL1StacksScale : null,
1382 thumbnailDisplayOutsideScreen: true,
1383 thumbnailWaitImageLoaded: true,
1384 thumbnailSliderDelay: 2000,
1385 galleryBuildInit2 : '',
1386 portable : false,
1387 eventsDebounceDelay: 50,
1388
1389 touchAnimation : true,
1390 touchAnimationL1 : undefined,
1391 touchAutoOpenDelay : 0,
1392
1393 thumbnailLabel : {
1394 position : 'overImageOnBottom',
1395 align: 'center',
1396 display : true,
1397 displayDescription : false,
1398 titleMaxLength : 0,
1399 titleMultiLine : false,
1400 descriptionMaxLength : 0,
1401 descriptionMultiLine : false,
1402 hideIcons : true,
1403 title : ''
1404 },
1405
1406 thumbnailToolbarImage : { topLeft: 'select', topRight : 'featured' },
1407 thumbnailToolbarAlbum : { topLeft: 'select', topRight : 'counter' },
1408 thumbnailDisplayInterval : 15,
1409 thumbnailL1DisplayInterval : null,
1410 thumbnailDisplayTransition : 'fadeIn',
1411 thumbnailL1DisplayTransition : null,
1412 thumbnailDisplayTransitionDuration: 240,
1413 thumbnailL1DisplayTransitionDuration: null,
1414 thumbnailOpenImage : true,
1415 thumbnailOpenOriginal : false,
1416 thumbnailGlobalImageTitle : '',
1417 thumbnailGlobalAlbumTitle : '',
1418
1419 viewer : 'internal',
1420 viewerFullscreen: false,
1421 viewerDisplayLogo : false,
1422 imageTransition : 'swipe2',
1423 viewerTransitionMediaKind : 'img',
1424 viewerZoom : true,
1425 viewerImageDisplay : '',
1426 openOnStart : '',
1427 viewerHideToolsDelay : 3000,
1428 viewerToolbar : {
1429 display : true,
1430 position : 'bottomOverImage',
1431 fullWidth : true,
1432 align : 'center',
1433 autoMinimize : 0,
1434 standard : 'minimizeButton,label',
1435 minimized : 'minimizeButton,label,infoButton,shareButton,downloadButton,linkOriginalButton,fullscreenButton'
1436 },
1437 viewerTools : {
1438 topLeft : 'pageCounter,playPauseButton',
1439 topRight : 'zoomButton,closeButton'
1440 },
1441
1442 breakpointSizeSM : 480,
1443 breakpointSizeME : 992,
1444 breakpointSizeLA : 1200,
1445 breakpointSizeXL : 1800,
1446
1447 fnThumbnailInit : null,
1448 fnThumbnailHoverInit : null,
1449 fnThumbnailHover : null,
1450 fnThumbnailHoverOut : null,
1451 fnThumbnailDisplayEffect : null,
1452 fnViewerInfo : null,
1453 fnImgToolbarCustInit : null,
1454 fnImgToolbarCustDisplay : null,
1455 fnImgToolbarCustClick : null,
1456 fnProcessData : null,
1457 fnThumbnailSelection : null,
1458 fnGalleryRenderStart : null,
1459 fnGalleryRenderEnd : null,
1460 fnGalleryObjectModelBuilt : null,
1461 fnGalleryLayoutApplied : null,
1462 fnThumbnailClicked : null,
1463 fnShoppingCartUpdated : null,
1464 fnThumbnailToolCustAction : null,
1465 fnThumbnailOpen : null,
1466 fnImgDisplayed : null,
1467
1468 i18n : {
1469 'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
1470 'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
1471 'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
1472 'infoBoxPhoto' : 'Photo', 'infoBoxDate' : 'Date', 'infoBoxAlbum' : 'Album', 'infoBoxDimensions' : 'Dimensions', 'infoBoxFilename' : 'Filename', 'infoBoxFileSize' : 'File size', 'infoBoxCamera' : 'Camera', 'infoBoxFocalLength' : 'Focal length', 'infoBoxExposure' : 'Exposure', 'infoBoxFNumber' : 'F Number', 'infoBoxISO' : 'ISO', 'infoBoxMake' : 'Make', 'infoBoxFlash' : 'Flash', 'infoBoxViews' : 'Views', 'infoBoxComments' : 'Comments'
1473 },
1474 icons : {
1475 // sample for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
1476 thumbnailAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1477 thumbnailImage: '<i class="nGY2Icon-picture"></i>',
1478 breadcrumbAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1479 breadcrumbHome: '<i class="nGY2Icon-home"></i>',
1480 breadcrumbSeparator: '<i class="nGY2Icon-left-open"></i>',
1481 breadcrumbSeparatorRtl: '<i class="nGY2Icon-right-open"></i>',
1482 navigationFilterSelected: '<i style="color:#fff;" class="nGY2Icon-toggle-on"></i>',
1483 navigationFilterUnselected: '<i style="color:#ddd;" class="nGY2Icon-toggle-off"></i>',
1484 navigationFilterSelectedAll: '<i class="nGY2Icon-toggle-on"></i><i class="nGY2Icon-ok"></i>',
1485 thumbnailSelected: '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
1486 thumbnailUnselected: '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
1487 thumbnailFeatured: '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
1488 thumbnailCounter: '<i class="nGY2Icon-picture"></i>',
1489 thumbnailShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1490 thumbnailDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1491 thumbnailInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1492 thumbnailCart: '<i class="nGY2Icon-basket"></i>',
1493 thumbnailDisplay: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1494 thumbnailCustomTool1: 'T1',
1495 thumbnailCustomTool2: 'T2',
1496 thumbnailCustomTool3: 'T3',
1497 thumbnailCustomTool4: 'T4',
1498 thumbnailCustomTool5: 'T5',
1499 thumbnailCustomTool6: 'T6',
1500 thumbnailCustomTool7: 'T7',
1501 thumbnailCustomTool8: 'T8',
1502 thumbnailCustomTool9: 'T9',
1503 thumbnailCustomTool10: 'T10',
1504 thumbnailAlbumUp: '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
1505 paginationNext: '<i class="nGY2Icon-right-open"></i>',
1506 paginationPrevious: '<i class="nGY2Icon-left-open"></i>',
1507 galleryMoreButton: '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
1508 buttonClose: '<i class="nGY2Icon-ngy2_close2"></i>',
1509 viewerPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1510 viewerNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1511 viewerImgPrevious: '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
1512 viewerImgNext: '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
1513 viewerDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1514 viewerToolbarMin: '<i class="nGY2Icon-ellipsis-vert"></i>',
1515 viewerToolbarStd: '<i class="nGY2Icon-menu"></i>',
1516 viewerPlay: '<i class="nGY2Icon-play"></i>',
1517 viewerPause: '<i class="nGY2Icon-pause"></i>',
1518 viewerFullscreenOn: '<i class="nGY2Icon-resize-full"></i>',
1519 viewerFullscreenOff: '<i class="nGY2Icon-resize-small"></i>',
1520 viewerZoomIn: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1521 viewerZoomOut: '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
1522 viewerLinkOriginal: '<i class="nGY2Icon-ngy2_external2"></i>',
1523 viewerInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1524 viewerShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1525 user: '<i class="nGY2Icon-user"></i>',
1526 location: '<i class="nGY2Icon-location"></i>',
1527 config: '<i class="nGY2Icon-wrench"></i>',
1528 shareFacebook: '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
1529 shareTwitter: '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
1530 shareGooglePlus: '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
1531 shareTumblr: '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
1532 sharePinterest: '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
1533 shareVK: '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
1534 shareMail: '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
1535 viewerCustomTool1: 'T1',
1536 viewerCustomTool2: 'T2',
1537 viewerCustomTool3: 'T3',
1538 viewerCustomTool4: 'T4',
1539 viewerCustomTool5: 'T5',
1540 viewerCustomTool6: 'T6',
1541 viewerCustomTool7: 'T7',
1542 viewerCustomTool8: 'T8',
1543 viewerCustomTool9: 'T9',
1544 viewerCustomTool10: 'T10'
1545 }
1546 };
1547
1548 jQuery.fn.nanogallery2 = function (args, option, value) {
1549
1550 if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
1551 if( args == 'destroy' ) {
1552 // command to destroy but no instance yet --> exit
1553 return;
1554 }
1555
1556 return this.each( function(){
1557 (new jQuery.nanogallery2(this, args));
1558 });
1559 }
1560 else {
1561 // no options -->
1562 // This function breaks the chain, but provides some API methods
1563
1564 var nG2=$(this).data('nanogallery2data').nG2;
1565 switch(args){
1566 case 'displayItem':
1567 nG2.DisplayItem(option);
1568 break;
1569
1570 case 'search':
1571 return( nG2.Search(option));
1572 break;
1573
1574 case 'search2':
1575 return nG2.Search2(option, value);
1576 break;
1577
1578 case 'search2Execute':
1579 return nG2.Search2Execute();
1580 break;
1581
1582 case 'refresh':
1583 nG2.Refresh();
1584 break;
1585
1586 case 'resize':
1587 nG2.Resize();
1588 break;
1589
1590 case 'instance':
1591 return nG2;
1592 break;
1593
1594 case 'data':
1595 nG2.data= {
1596 items: nG2.I,
1597 gallery: nG2.GOM,
1598 lightbox: nG2.VOM
1599 };
1600 return nG2.data;
1601 break;
1602
1603 case 'reload':
1604 nG2.ReloadAlbum();
1605 return $(this);
1606 break;
1607
1608 case 'itemsSelectedGet':
1609 return nG2.ItemsSelectedGet();
1610 break;
1611
1612 case 'itemsSetSelectedValue':
1613 nG2.ItemsSetSelectedValue(option, value);
1614 break;
1615
1616 case 'option':
1617 if(typeof value === 'undefined'){
1618 return nG2.Get(option);
1619 }else{
1620 nG2.Set(option,value);
1621 if( option == 'demoViewportWidth' ) {
1622 // force resize event -> for demo purposes
1623 $(window).trigger('resize');
1624 }
1625 }
1626 break;
1627
1628 case 'destroy':
1629 nG2.Destroy();
1630 $(this).removeData('nanogallery2data');
1631 break;
1632
1633 case 'shoppingCartGet':
1634 return nG2.shoppingCart;
1635 break;
1636
1637 case 'shoppingCartUpdate':
1638 if( typeof value === 'undefined' || typeof option === 'undefined' ){
1639 return false;
1640 }
1641 var ID=option;
1642 var cnt=value;
1643 for( var i=0; i<nG2.shoppingCart.length; i++) {
1644 if( nG2.shoppingCart[i].ID=ID ) {
1645 nG2.shoppingCart[i].cnt=cnt;
1646 }
1647 }
1648 var fu=G.O.fnShoppingCartUpdated;
1649 if( fu !== null ) {
1650 typeof fu == 'function' ? fu(nG2.shoppingCart) : window[fu](nG2.shoppingCart);
1651 }
1652 return nG2.shoppingCart;
1653 break;
1654
1655 case 'shoppingCartRemove':
1656 if( typeof option === 'undefined' ){
1657 return false;
1658 }
1659 var ID=option;
1660 for( var i=0; i<nG2.shoppingCart.length; i++) {
1661 if( nG2.shoppingCart[i].ID=ID ) {
1662 nG2.shoppingCart.splice(i,1);
1663 break;
1664 }
1665 }
1666 var fu=G.O.fnShoppingCartUpdated;
1667 if( fu !== null ) {
1668 typeof fu == 'function' ? fu(nG2.shoppingCart) : window[fu](nG2.shoppingCart);
1669 }
1670 return nG2.shoppingCart;
1671 break;
1672
1673 case 'closeViewer':
1674 nG2.CloseViewer();
1675 break;
1676 case 'minimizeToolbar':
1677 nG2.MinimizeToolbar();
1678 break;
1679 case 'maximizeToolbar':
1680 nG2.MaximizeToolbar();
1681 break;
1682 case 'paginationPreviousPage':
1683 nG2.PaginationPreviousPage();
1684 break;
1685 case 'paginationNextPage':
1686 nG2.paginationNextPage();
1687 break;
1688 case 'paginationGotoPage':
1689 nG2.PaginationGotoPage( option );
1690 break;
1691 case 'paginationCountPages':
1692 nG2.PaginationCountPages();
1693 break;
1694
1695 }
1696 return $(this);
1697
1698 }
1699 };
1700
1701
1702 // ###############################
1703 // ##### nanogallery2 script #####
1704 // ###############################
1705
1706 /** @function nanoGALLERY2 */
1707 function nanoGALLERY2() {
1708 "use strict";
1709
1710 /**
1711 * Force reload the current album, if provided by Json
1712 */
1713 this.ReloadAlbum = function(){
1714 if( G.O.kind === '' ) {
1715 throw 'Not supported for this content source:' + G.O.kind;
1716 }
1717
1718 var albumIdx=G.GOM.albumIdx;
1719 if( albumIdx == -1 ) {
1720 throw ('Current album not found.');
1721 }
1722
1723 var albumID = G.I[albumIdx].GetID();
1724
1725 // unselect everything & remove link to album (=logical delete)
1726 var l = G.I.length;
1727 for( var i = 0; i < l ; i++ ) {
1728 var item = G.I[i];
1729 if( item.albumID == albumID ) {
1730 item.selected = false;
1731 }
1732 }
1733
1734 G.I[albumIdx].contentIsLoaded = false;
1735
1736 DisplayAlbum('-1', albumID);
1737 };
1738
1739 /**
1740 * Set one or several items selected/unselected
1741 * @param {array} items
1742 */
1743 this.ItemsSetSelectedValue = function(items, value){
1744 var l = items.length;
1745 for( var j = 0; j < l ; j++) {
1746 ThumbnailSelectionSet(items[j], value);
1747 }
1748 };
1749
1750 /**
1751 * Returns an array of selected items
1752 * @returns {Array}
1753 */
1754 this.ItemsSelectedGet = function(){
1755 var selectedItems = [];
1756 var l = G.I.length;
1757 for( var i = 0; i < l ; i++ ) {
1758 if( G.I[i].selected == true ) {
1759 selectedItems.push(G.I[i]);
1760 }
1761 }
1762 return selectedItems;
1763 };
1764
1765 /**
1766 * Returns the value of an option
1767 * @param {string} option
1768 * @returns {nanoGALLERY.G.O}
1769 */
1770 this.Get = function(option){
1771 return G.O[option];
1772 };
1773
1774 /**
1775 * Set a new value for a defined option
1776 * @param {string} option
1777 */
1778 this.Set = function(option, value){
1779 G.O[option] = value;
1780 switch( option ) {
1781 case 'thumbnailSelectable':
1782 ThumbnailSelectionClear();
1783 // refresh the displayed gallery
1784 GalleryRender( G.GOM.albumIdx );
1785 break;
1786 }
1787 };
1788
1789 /**
1790 * refresh the current gallery
1791 */
1792 this.Refresh = function() {
1793 // refresh the displayed gallery
1794 GalleryRender( G.GOM.albumIdx );
1795 };
1796 /**
1797 * resize the current gallery
1798 */
1799 this.Resize = function() {
1800 // resize the displayed gallery
1801 GalleryResize();
1802 };
1803
1804 /**
1805 * display one item (image or gallery)
1806 * itemID syntax:
1807 * - albumID --> display one album
1808 * - albumID/imageID --> display one image
1809 */
1810 this.DisplayItem = function( itemID ) {
1811 var IDs=parseIDs( itemID );
1812 if( IDs.imageID != '0' ) {
1813 DisplayPhoto( IDs.imageID, IDs.albumID );
1814 }
1815 else {
1816 DisplayAlbum( '-1', IDs.albumID );
1817 }
1818 };
1819
1820
1821
1822 var CountItemsToDisplay = function( gIdx ) {
1823 if( G.I[gIdx] == undefined ) { return 0; }
1824 var albumID = G.I[gIdx].GetID();
1825 var l = G.I.length;
1826 var cnt = 0;
1827 for( var idx = 0; idx < l; idx++ ) {
1828 var item = G.I[idx];
1829 if( item.isToDisplay(albumID) ) {
1830 cnt++;
1831 }
1832 }
1833 return cnt;
1834 }
1835 /**
1836 * Search in the displayed gallery (in thumbnails title)
1837 */
1838 this.Search = function( search ) {
1839 G.GOM.albumSearch = search.toUpperCase();
1840 var gIdx = G.GOM.albumIdx;
1841 GalleryRender( G.GOM.albumIdx );
1842 return CountItemsToDisplay( gIdx );
1843 };
1844
1845 /**
1846 * Search2 in title and tags - set search values
1847 */
1848 this.Search2 = function( searchTitle, searchTags ) {
1849 if( searchTitle != null && searchTitle != undefined ) {
1850 G.GOM.albumSearch = searchTitle.toUpperCase();
1851 }
1852 else {
1853 G.GOM.albumSearch = '';
1854 }
1855
1856 if( searchTags != null && searchTags != undefined ) {
1857 G.GOM.albumSearchTags = searchTags.toUpperCase();
1858 }
1859 else {
1860 G.GOM.albumSearchTags = '';
1861 }
1862 return CountItemsToDisplay( G.GOM.albumIdx );
1863 };
1864
1865 /**
1866 * Search2 - execute the search on title and tags
1867 */
1868 this.Search2Execute = function() {
1869 var gIdx = G.GOM.albumIdx;
1870 GalleryRender( G.GOM.albumIdx );
1871 return CountItemsToDisplay( gIdx );
1872 };
1873
1874
1875 /**
1876 * Destroy the current gallery
1877 */
1878 this.Destroy = function(){
1879 // alert('destroy');
1880 // var event = new Event('build');
1881 if( G.GOM.hammertime != null ) {
1882 G.GOM.hammertime.destroy();
1883 G.GOM.hammertime = null;
1884 }
1885 // G.GOM.userEvents.RemoveEvtListener();
1886 // G.GOM.userEvents=null;
1887 // G.VOM.userEvents.RemoveEvtListener();
1888 // G.VOM.userEvents=null;
1889 if( G.VOM.hammertime != null ) {
1890 G.VOM.hammertime.destroy();
1891 G.VOM.hammertime = null;
1892 }
1893 //ThumbnailHoverReInitAll();
1894
1895 // color scheme
1896 $('#ngycs_' + G.baseEltID).remove()
1897
1898 G.GOM.items = [];
1899 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
1900 G.GOM.navigationBar.$newContent = null;
1901 G.$E.base.empty();
1902 G.$E.base.removeData();
1903 if( G.O.locationHash ) {
1904 jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
1905 }
1906 jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
1907 jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
1908 jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
1909 G.GOM.firstDisplay = false;
1910 };
1911
1912 /**
1913 * CloseViewer - close the media viewer
1914 */
1915 this.CloseViewer = function() {
1916 CloseInternalViewer(null);
1917 return false;
1918 };
1919
1920 /**
1921 * MinimizeToolbar - display the minimized lightbox main toolbar
1922 */
1923 this.MinimizeToolbar = function() {
1924 ViewerToolbarForVisibilityMin();
1925 return false;
1926 };
1927
1928 /**
1929 * MaximizeToolbar - display the maximized/standard lightbox main toolbar
1930 */
1931 this.MaximizeToolbar = function() {
1932 ViewerToolbarForVisibilityStd();
1933 return false;
1934 };
1935
1936 /**
1937 * PaginationPreviousPage - gallery paginate to previous page
1938 */
1939 this.PaginationPreviousPage = function() {
1940 paginationPreviousPage();
1941 return false;
1942 };
1943
1944
1945 /**
1946 * PaginationNextPage - gallery paginate to next page
1947 */
1948 this.PaginationNextPage = function() {
1949 paginationNextPage();
1950 return false;
1951 };
1952
1953
1954 /**
1955 * PaginationGotoPage - gallery paginate to specific page
1956 */
1957 this.PaginationGotoPage = function( page ) {
1958 var aIdx = G.$E.conPagin.data('galleryIdx');
1959 // if( !inViewportVert(G.$E.base, 0) ) {
1960 // $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
1961 // }
1962 if( page > 1 ) { page--; }
1963 G.GOM.pagination.currentPage = page;
1964 GalleryDisplayPart1( true );
1965 GalleryDisplayPart2( true );
1966 return false;
1967 };
1968
1969 /**
1970 * PaginationCountPages - gallery pagination - returns the number of pages
1971 */
1972 this.PaginationCountPages = function() {
1973 if( G.GOM.items.length == 0 ) { return 0; } // no thumbnail to display
1974
1975 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
1976 return nbPages;
1977 };
1978
1979
1980
1981
1982 // throttle()
1983 // author: underscore.js - http://underscorejs.org/docs/underscore.html
1984 // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
1985 // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
1986 // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
1987 // To disable execution on the trailing edge, ditto.
1988 var throttle = function(func, wait, options) {
1989 var context, args, result;
1990 var timeout = null;
1991 var previous = 0;
1992 if (!options) options = {};
1993 var later = function() {
1994 previous = options.leading === false ? 0 : new Date().getTime();
1995 timeout = null;
1996 result = func.apply(context, args);
1997 if (!timeout) context = args = null;
1998 };
1999 return function() {
2000 var now = new Date().getTime();
2001 if (!previous && options.leading === false) previous = now;
2002 var remaining = wait - (now - previous);
2003 context = this;
2004 args = arguments;
2005 if (remaining <= 0 || remaining > wait) {
2006 if (timeout) {
2007 clearTimeout(timeout);
2008 timeout = null;
2009 }
2010 previous = now;
2011 result = func.apply(context, args);
2012 if (!timeout) context = args = null;
2013 } else if (!timeout && options.trailing !== false) {
2014 timeout = setTimeout(later, remaining);
2015 }
2016 return result;
2017 };
2018 };
2019
2020
2021 // DEBOUNCE
2022 // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
2023 // execAsap - false means executing at the end of the detection period
2024 var debounce = function (func, threshold, execAsap) {
2025 var timeout;
2026 return function debounced () {
2027 var obj = this, args = arguments;
2028 function delayed () {
2029 if (!execAsap)
2030 func.apply(obj, args);
2031 timeout = null;
2032 };
2033
2034 if (timeout)
2035 clearTimeout(timeout);
2036 // clearRequestTimeout(timeout);
2037 else if (execAsap)
2038 func.apply(obj, args);
2039 timeout = setTimeout(delayed, threshold || 100);
2040 // timeout = requestTimeout(delayed, threshold || 100);
2041 };
2042 }
2043
2044 // Double requestAnimationFrame
2045 window.ng_draf = function (cb) {
2046 return requestAnimationFrame(function() {
2047 window.requestAnimationFrame(cb)
2048 })
2049 }
2050
2051 // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
2052 /**
2053 * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
2054 * @param {function} fn The callback function
2055 * @param {int} delay The delay in milliseconds
2056 */
2057
2058 window.requestTimeout = function(fn, delay) {
2059 // if( !window.requestAnimationFrame &&
2060 // !window.webkitRequestAnimationFrame &&
2061 // !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
2062 // !window.oRequestAnimationFrame &&
2063 // !window.msRequestAnimationFrame)
2064 return window.setTimeout(fn, delay);
2065
2066 var start = new Date().getTime(),
2067 handle = new Object();
2068
2069 function loop(){
2070 var current = new Date().getTime(),
2071 delta = current - start;
2072 delta = delay;
2073
2074 delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
2075 };
2076
2077 handle.value = requestAnimFrame(loop);
2078 return handle;
2079 };
2080
2081
2082 // requestAnimationFrame() shim by Paul Irish
2083 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2084 window.requestAnimFrame = (function() {
2085 return window.requestAnimationFrame ||
2086 window.webkitRequestAnimationFrame ||
2087 window.mozRequestAnimationFrame ||
2088 window.oRequestAnimationFrame ||
2089 window.msRequestAnimationFrame ||
2090 function(/* function */ callback, /* DOMElement */ element){
2091 window.setTimeout(callback, 1000 / 60);
2092 };
2093 })();
2094
2095
2096 // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
2097 /**
2098 * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
2099 * @param {int|object} fn The callback function
2100 */
2101 window.clearRequestTimeout = function(handle) {
2102 window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
2103 window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
2104 window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
2105 window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
2106 window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
2107 window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
2108 clearTimeout(handle);
2109 };
2110
2111
2112
2113 /*
2114 ** Global data for this nanogallery2 instance
2115 **/
2116 var G=this;
2117 G.I = []; // gallery items
2118 G.Id = []; // gallery items
2119 G.O = null; // user options
2120 G.baseEltID = null; // ID of the base element
2121 G.$E = {
2122 base: null, // base element
2123 conTnParent: null, // $g_containerThumbnailsParent
2124 conLoadingB: null, // loading bar - nanoGalleryLBarOff
2125 conConsole: null, // console for error messages
2126 conNavigationBar: null, // gallery navigation bar
2127 conTnBottom: null // container on the bottom of the gallery
2128 };
2129 G.shoppingCart = [];
2130 G.layout = { // Layout informations
2131 internal : true,
2132 engine : '',
2133 support : { rows: false },
2134 prerequisite : { imageSize: false },
2135 SetEngine: function() {
2136
2137 if( G.layout.internal ) {
2138 if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2139 // do not use getH() / getW() here!
2140 G.layout.engine = 'JUSTIFIED';
2141 G.layout.support.rows = true;
2142 G.layout.prerequisite.imageSize = true;
2143 return;
2144 }
2145 if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2146 // do not use getH() / getW() here!
2147 G.layout.engine = 'CASCADING';
2148 G.layout.support.rows = false;
2149 G.layout.prerequisite.imageSize = true;
2150 return;
2151 }
2152
2153 if( G.tn.settings.getMosaic() != null ) {
2154 G.layout.engine = 'MOSAIC';
2155 G.layout.support.rows = true;
2156 G.layout.prerequisite.imageSize = false;
2157 return;
2158 }
2159
2160 G.layout.engine='GRID';
2161 G.layout.support.rows=true;
2162 // if( G.tn.opt.Get('crop') === true ) {
2163 // G.layout.prerequisite.imageSize = true;
2164 // }
2165 // else {
2166 G.layout.prerequisite.imageSize = false;
2167 // }
2168 }
2169 }
2170 };
2171 G.galleryResizeEventEnabled = false;
2172 G.galleryMaxRows = { l1: 0, lN: 0,
2173 Get: function() {
2174 return G.galleryMaxRows[G.GOM.curNavLevel];
2175 }
2176 };
2177 G.galleryMaxItems = { l1: 0, lN: 0,
2178 Get: function() {
2179 return G.galleryMaxItems[G.GOM.curNavLevel];
2180 }
2181 };
2182 G.galleryFilterTags = { l1: 0, lN: 0,
2183 Get: function() {
2184 return G.galleryFilterTags[G.GOM.curNavLevel];
2185 }
2186 };
2187 G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
2188 Get: function() {
2189 return G.galleryDisplayMode[G.GOM.curNavLevel];
2190 }
2191 };
2192 G.galleryLastRowFull = { l1: false, lN: false,
2193 Get: function() {
2194 return G.galleryLastRowFull[G.GOM.curNavLevel];
2195 }
2196 };
2197 G.gallerySorting = { l1: '', lN: '',
2198 Get: function() {
2199 return G.gallerySorting[G.GOM.curNavLevel];
2200 }
2201 };
2202 G.galleryDisplayTransition = { l1: 'none', lN: 'none',
2203 Get: function() {
2204 return G.galleryDisplayTransition[G.GOM.curNavLevel];
2205 }
2206 };
2207 G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
2208 Get: function() {
2209 return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
2210 }
2211 };
2212 G.$currentTouchedThumbnail = null;
2213
2214 // ##### GENERAL THUMBNAILS PROPERTIES -->
2215 G.tn = {
2216 // levell specific options
2217 opt: {
2218 l1: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, gutterHeight: 0, gutterWidth: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
2219 lN: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, gutterHeight: 0, gutterWidth: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
2220 Get: function(opt) {
2221 return G.tn.opt[G.GOM.curNavLevel][opt];
2222 }
2223 },
2224 scale: 1, // image scale depending of the hover effect
2225 borderWidth: 0, // thumbnail container border width
2226 borderHeight: 0, // thumbnail container border height
2227 labelHeight: { // in case label on bottom, otherwise always=0
2228 l1: 0, lN: 0,
2229 get: function() {
2230 return G.tn.labelHeight[G.GOM.curNavLevel];
2231 }
2232 },
2233 defaultSize: { // default thumbnail size
2234 // label height is not included
2235 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2236 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2237 getWidth: function() {
2238 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2239 },
2240 getOuterWidth: function() { // width border included
2241 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.borderWidth*2;
2242 },
2243 getHeight: function() {
2244 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
2245 },
2246 getOuterHeight: function() { // height, border included
2247 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.borderHeight*2;
2248 }
2249 },
2250 settings: { // user defined width/height of the image to display depending on the screen size
2251 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2252 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2253 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2254 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2255 getH: function(l, w) {
2256 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2257 var cw = (w == undefined ? G.GOM.curWidth : w);
2258 if( G.layout.engine == 'MOSAIC' ) {
2259 return G.tn.settings.height[cl][cw] * G.tn.settings.mosaic[cl+'Factor']['h'][cw];
2260 }
2261 else {
2262 return G.tn.settings.height[cl][cw];
2263 }
2264 },
2265 getW: function(l, w) {
2266 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2267 var cw = (w == undefined ? G.GOM.curWidth : w);
2268 if( G.layout.engine == 'MOSAIC' ) {
2269 return G.tn.settings.width[cl][cw] * G.tn.settings.mosaic[cl+'Factor']['w'][cw];
2270 }
2271 else {
2272 return G.tn.settings.width[cl][cw];
2273 // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
2274 }
2275 },
2276 mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
2277 lN : { xs: null, sm: null, me: null, la: null, xl: null },
2278 l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
2279 lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
2280 },
2281 getMosaic: function() {
2282 return G.tn.settings.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
2283 },
2284 mosaicCalcFactor: function(l, w) {
2285 // retrieve max size multiplicator
2286 var maxW = 1;
2287 var maxH = 1;
2288 for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
2289 maxW = Math.max(maxW, G.tn.settings.mosaic[l][w][n]['w']);
2290 maxH = Math.max(maxH, G.tn.settings.mosaic[l][w][n]['h']);
2291 }
2292 G.tn.settings.mosaic[l + 'Factor']['h'][w] = maxH;
2293 G.tn.settings.mosaic[l + 'Factor']['w'][w] = maxW;
2294 }
2295 },
2296 // thumbnail hover effects
2297 hoverEffects : {
2298 std : [],
2299 level1: [],
2300 get: function() {
2301 if( G.GOM.curNavLevel == 'l1' && G.tn.hoverEffects.level1.length !== 0 ) {
2302 return G.tn.hoverEffects.level1;
2303 }
2304 else {
2305 return G.tn.hoverEffects.std;
2306 }
2307 }
2308 },
2309 // thumbnail init
2310 buildInit : {
2311 std : [],
2312 level1: [],
2313 get: function() {
2314 if( G.GOM.curNavLevel == 'l1' && G.tn.buildInit.level1.length !== 0 ) {
2315 return G.tn.buildInit.level1;
2316 }
2317 else {
2318 return G.tn.buildInit.std;
2319 }
2320 }
2321 },
2322 // thumbnail toolbars
2323 toolbar: {
2324 album : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2325 image : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2326 albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2327 get: function( item ) {
2328 return G.tn.toolbar[item.kind];
2329 },
2330 },
2331 style: {
2332 // inline CSS
2333 l1 : { label: '', title: '', desc: '' },
2334 lN : { label: '', title: '', desc: '' },
2335 getTitle : function() {
2336 return ('style="' + G.tn.style[G.GOM.curNavLevel].title + '"');
2337 },
2338 getDesc : function() {
2339 return ('style="' + G.tn.style[G.GOM.curNavLevel].desc + '"');
2340 },
2341 getLabel: function() {
2342 var s='style="'+ G.tn.style[G.GOM.curNavLevel].label;
2343 s+= (G.O.RTL ? '"direction:RTL;"' :'');
2344 s+='"';
2345 return s;
2346 }
2347 }
2348 };
2349 G.scrollTimeOut = 0;
2350 G.i18nTranslations = {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
2351 G.emptyGif = 'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
2352 G.CSStransformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
2353 // G.CSSfilterName = FirstSupportedPropertyName(["filter", "WebkitFilter"]);
2354 G.CSStransformStyle = FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
2355 G.CSSperspective = FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
2356 G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
2357 G.CSStransitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
2358 G.CSSanimationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
2359 G.GalleryResizeThrottled = throttle(GalleryResize, 100, {leading: false});
2360
2361 G.blackList = null; // album white list
2362 G.whiteList = null; // album black list
2363 G.albumList = []; // album list
2364 G.albumListHidden = []; // for Google Photos -> hidden albums with private key
2365 G.locationHashLastUsed = '';
2366 G.custGlobals = {};
2367 G.touchAutoOpenDelayTimerID = 0;
2368 G.i18nLang = '';
2369 G.timeLastTouchStart = 0;
2370 G.custGlobals = {};
2371 G.markupOrApiProcessed = false;
2372
2373 //------------------------
2374 //--- Gallery Object Model
2375 G.GOM = {
2376 albumIdx : -1, // index (in G.I) of the currently displayed album
2377 clipArea : { top: 0, height: 0 }, // area of the GOM to display on screen
2378 displayArea : { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
2379 displayAreaLast : { width: 0 , height: 0 }, // previous size of the GOM area
2380 displayedMoreSteps : 0, // current number of displayed steps (moreButton mode)
2381 items: [], // current items of the GOMS
2382 $imgPreloader: [],
2383 thumbnails2Display: [],
2384 itemsDisplayed : 0, // number of currently displayed thumbnails
2385 firstDisplay : true,
2386 firstDisplayTime : 0, // in conjunction with galleryRenderDelay
2387 navigationBar : { // content of the navigation bar (for breadcrumb and filter tags)
2388 displayed: false,
2389 $newContent: ''
2390 },
2391 cache : { // cached data
2392 viewport: null,
2393 containerOffset: null,
2394 areaWidth: 100 // available area width
2395 },
2396 nbSelected : 0, // number of selected items
2397 pagination : { currentPage: 0 }, // pagination data
2398 lastFullRow : -1, // number of the last row without holes
2399 lastDisplayedIdx: -1, // used to display the counter of not displayed items
2400 displayInterval : { from: 0, len: 0 },
2401 userEvents: null,
2402 hammertime: null,
2403 curNavLevel: 'l1', // current navigation level (l1 or LN)
2404 curWidth: 'me',
2405 albumSearch: '', // current search string -> title (used to filter the thumbnails on screen)
2406 albumSearchTags: '', // current search string -> tags
2407 lastZIndex: 0, // used to put a thumbnail on top of all others (for exemple for scale hover effect)
2408 lastRandomValue: 0,
2409 slider : { // slider on last thumbnail
2410 hostIdx: -1, // idx of the thumbnail hosting the slider
2411 hostItem: null, // item hosting the slider
2412 currentIdx: 0, // idx of the current displayed item
2413 nextIdx: 0, // idx of the next item to display in the slider
2414 timerID: 0,
2415 tween: null // tranistion tween instance
2416 },
2417 NGY2Item: function( idx ) { // returns a NGY2Item or null if it does not exist
2418 if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
2419 var i = G.GOM.items[idx].thumbnailIdx;
2420 return G.I[i];
2421 },
2422 // One GOM item (thumbnail)
2423 // function GTn(index, width, height) {
2424 GTn: function(index, width, height) {
2425 this.thumbnailIdx = index;
2426 this.width = 0; // thumbnail width
2427 this.height = 0; // thumbnail height
2428 this.top = 0; // position: top
2429 this.left = 0; // position: left
2430 this.row = 0; // position: row number
2431 this.imageWidth = width; // image width
2432 this.imageHeight = height; // image height
2433 this.resizedContentWidth = 0;
2434 this.resizedContentHeight = 0;
2435 this.displayed = false;
2436 this.neverDisplayed = true;
2437 this.inDisplayArea = false;
2438 }
2439 };
2440
2441
2442 //------------------------
2443 //--- Viewer Object Model
2444
2445 G.VOM = {
2446 viewerDisplayed: false, // is the viewer currently displayed
2447 viewerIsFullscreen: false, // viewer in fullscreen mode
2448 infoDisplayed: false, // is the info box displayed
2449 toolbarsDisplayed: true, // the toolbars are displayed
2450 toolsHide: null,
2451 saveOverflowX: 'visible', // store the value to restore it back after viewer is closed
2452 saveOverflowY: 'visible',
2453 zoom : {
2454 posX: 0, // position to center zoom in/out
2455 posY: 0,
2456 userFactor: 1, // user zoom factor (applied to the baseZoom factor)
2457 isZooming: false
2458 },
2459 padding: { H: 0, V: 0 }, // padding for the image
2460 window: { lastWidth: 0, lastHeight: 0 },
2461 $cont: null, // viewer container
2462 $viewer: null,
2463 $toolbar: null, // viewerToolbar
2464 $toolbarTL: null, // viewer toolbar on top left
2465 $toolbarTR: null, // viewer toolbar on top right
2466 $content: null, // viewer content
2467
2468 $mediaPrevious: null, // previous image
2469 $mediaCurrent: null, // current image
2470 $mediaNext: null, // next image
2471 toolbarMode: 'std', // current toolbar mode (standard, minimized)
2472 playSlideshow : false, // slide show mode status
2473 playSlideshowTimerID: 0, // slideshow mode time
2474 slideshowDelay: 3000, // slideshow mode - delay before next image
2475 albumID: -1,
2476 currItemIdx: -1,
2477 viewerMediaIsChanged: false, // media display is currently modified
2478 items: [], // current list of images to be managed by the viewer
2479 NGY2Item: function( n ) { // returns a NGY2Item
2480 switch( n ) {
2481 case -1: // previous
2482 var idx=this.IdxPrevious();
2483 return G.I[this.items[idx].ngy2ItemIdx]
2484 break;
2485 case 1: // next
2486 var idx=this.IdxNext();
2487 return G.I[this.items[idx].ngy2ItemIdx]
2488 break;
2489 case 0: // current
2490 default:
2491 return G.I[this.items[G.VOM.currItemIdx].ngy2ItemIdx];
2492 break;
2493 }
2494 },
2495 IdxNext: function() {
2496 var n = 0;
2497 if( G.VOM.currItemIdx != (G.VOM.items.length-1) ) {
2498 n = G.VOM.currItemIdx + 1;
2499 }
2500 return n;
2501 },
2502 IdxPrevious: function() {
2503 var n = G.VOM.currItemIdx-1;
2504 if( G.VOM.currItemIdx == 0 ) {
2505 n = G.VOM.items.length - 1;
2506 }
2507 return n;
2508 },
2509 userEvents: null, // user events management
2510 hammertime: null, // hammer.js manager
2511 swipePosX: 0, // current horizontal swip position
2512 panPosX: 0, // position for manual pan
2513 panPosY: 0,
2514 viewerTheme: '',
2515 timeImgChanged: 0,
2516 ImageLoader: {
2517 // fires a callback when image size is know (during download)
2518 // inspired by ROB - http://stackoverflow.com/users/226507/rob
2519 maxChecks: 1000,
2520 list: [],
2521 intervalHandle : null,
2522
2523 loadImage : function (callback, ngitem) {
2524 if( ngitem.mediaKind != 'img' ) { return; } // ignore - only for images
2525 var img = new Image ();
2526 img.src = ngitem.responsiveURL();
2527 if (img.width && img.height) {
2528 callback (img.width, img.height, ngitem, 0);
2529 }
2530 else {
2531 var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
2532 var i;
2533 for (i=0; i < this.list.length; i++) {
2534 if (this.list[i] == null)
2535 break;
2536 }
2537 this.list[i] = obj;
2538 if (!this.intervalHandle)
2539 this.intervalHandle = setInterval(this.interval, 50);
2540 }
2541 },
2542
2543 // called by setInterval
2544 interval : function () {
2545 var count = 0;
2546 var list = G.VOM.ImageLoader.list, item;
2547 for (var i=0; i<list.length; i++) {
2548 item = list[i];
2549 if (item != null) {
2550 if (item.image.width && item.image.height) {
2551 G.VOM.ImageLoader.list[i] = null;
2552 item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
2553 }
2554 else if (item.checks > G.VOM.ImageLoader.maxChecks) {
2555 G.VOM.ImageLoader.list[i] = null;
2556 item.callback (0, 0, item.ngitem, item.checks);
2557 }
2558 else {
2559 count++;
2560 item.checks++;
2561 }
2562 }
2563 }
2564 if (count == 0) {
2565 G.VOM.ImageLoader.list = [];
2566 clearInterval (G.VOM.ImageLoader.intervalHandle);
2567 delete G.VOM.ImageLoader.intervalHandle;
2568 }
2569 }
2570 }
2571 }
2572 // One VOM item (image)
2573 function VImg(index) {
2574 this.$e = null;
2575 this.ngy2ItemIdx = index;
2576 this.mediaNumber = 0;
2577 this.posX = 0; // to center the element
2578 this.posY = 0;
2579 }
2580
2581
2582 //------------------------
2583 //--- popup
2584 G.popup = {
2585 isDisplayed: false,
2586 $elt: null,
2587 close: function() {
2588 if( this.$elt != null ) {
2589 var tweenable = new NGTweenable();
2590 tweenable.tween({
2591 from: { opacity:1 },
2592 to: { opacity:0 },
2593 attachment: { t: this },
2594 easing: 'easeInOutSine',
2595 duration: 100,
2596 step: function (state, att) {
2597 if( att.t.$elt != null ) {
2598 att.t.$elt.css('opacity',state.opacity);
2599 }
2600 },
2601 finish: function (state, att) {
2602 if( att.t.$elt != null ) {
2603 att.t.$elt.remove();
2604 att.t.$elt=null;
2605 }
2606 att.t.isDisplayed=false;
2607 }
2608 });
2609 }
2610 }
2611 }
2612
2613
2614 // Color schemes - Gallery
2615 // gadrient generator: https://www.grabient.com/
2616 G.galleryTheme_dark = {
2617 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2618 navigationBreadcrumb : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2619 navigationFilter : { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
2620 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
2621 thumbnailIcon : { padding: '5px', color: '#fff' },
2622 pagination : { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2623 };
2624
2625 G.galleryTheme_light = {
2626 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2627 navigationBreadcrumb : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2628 navigationFilter : { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
2629 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#888' },
2630 thumbnailIcon : { padding: '5px', color: '#fff' },
2631 pagination : { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2632 };
2633
2634 // Color schemes - lightbox
2635 G.viewerTheme_dark = {
2636 background: '#000',
2637 imageBorder: 'none',
2638 imageBoxShadow: 'none',
2639 barBackground: 'rgba(4, 4, 4, 0.7)',
2640 barBorder: '0px solid #111',
2641 barColor: '#eee',
2642 barDescriptionColor: '#aaa'
2643 };
2644 G.viewerTheme_border = {
2645 background: 'rgba(1, 1, 1, 0.75)',
2646 imageBorder: '4px solid #f8f8f8',
2647 imageBoxShadow: '#888 0px 0px 20px',
2648 barBackground: 'rgba(4, 4, 4, 0.7)',
2649 barBorder: '0px solid #111',
2650 barColor: '#eee',
2651 barDescriptionColor: '#aaa'
2652 };
2653 G.viewerTheme_light = {
2654 background: '#f8f8f8',
2655 imageBorder: 'none',
2656 imageBoxShadow: 'none',
2657 barBackground: 'rgba(4, 4, 4, 0.7)',
2658 barBorder: '0px solid #111',
2659 barColor: '#eee',
2660 barDescriptionColor: '#aaa'
2661 };
2662
2663
2664
2665 // shortcut with G context to NGY2TOOLS
2666 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
2667 // var NanoConsoleLog = NGY2Tools.NanoConsoleLog.bind(G);
2668 var NanoAlert = NGY2Tools.NanoAlert;
2669 var NanoConsoleLog = NGY2Tools.NanoConsoleLog;
2670
2671
2672 /** @function initiateGallery2 */
2673 this.initiateGallery2 = function( element, params ) {
2674
2675 // GLOBAL OPTIONS
2676 G.O = params;
2677 // Base element
2678 G.$E.base = jQuery(element);
2679 G.baseEltID = G.$E.base.attr('id');
2680 if( G.baseEltID == undefined ) {
2681 // set a default ID to the root container
2682 G.baseEltID='my_nanogallery';
2683 G.$E.base.attr('id', G.baseEltID)
2684 }
2685 G.O.$markup = [];
2686 DefineVariables();
2687 SetPolyFills();
2688 BuildSkeleton();
2689 G.GOM.firstDisplayTime=Date.now();
2690
2691 SetGlobalEvents();
2692
2693 // check if only one specific album will be used
2694 var albumToDisplay = G.O.album;
2695 if( albumToDisplay == '' && G.O.photoset != '' ) {
2696 albumToDisplay = G.O.photoset;
2697 G.O.album = G.O.photoset;
2698 }
2699 if( albumToDisplay != '' ) {
2700 G.O.displayBreadcrumb = false; // no breadcrumb since only 1 album
2701 if( albumToDisplay.toUpperCase() != 'NONE' ) {
2702 // open specific album
2703
2704 var p=albumToDisplay.indexOf('&authkey=');
2705 if( p == -1 ) {
2706 p=albumToDisplay.indexOf('?authkey=');
2707 }
2708 if( p > 0 ) {
2709 // privat album with authkey
2710 G.O.locationHash=false; // disable hash location for hidden/privat albums --> impossible to handle
2711 var albumID=albumToDisplay.substring(0,p);
2712 var opt=albumToDisplay.substring(p);
2713 if( opt.indexOf('Gv1sRg') == -1 ) {
2714 opt = '&authkey=Gv1sRg'+opt.substring(9);
2715 }
2716 var newItem = NGY2Item.New( G, '', '', albumID, '-1', 'album' );
2717 newItem.authkey = opt;
2718 DisplayAlbum('-1', albumID);
2719 }
2720 else {
2721 // open a public album
2722 if( G.O.kind == "nano_photos_provider2") {
2723 if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
2724 // album ID must be encoded
2725 albumToDisplay = encodeURIComponent(albumToDisplay);
2726 G.O.album = albumToDisplay;
2727 }
2728 }
2729 NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
2730 if( !ProcessLocationHash() ) {
2731 DisplayAlbum('-1', albumToDisplay);
2732 }
2733 }
2734 return;
2735 }
2736 }
2737
2738 // use full content
2739 // add base album
2740 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2741
2742 processStartOptions();
2743
2744 }
2745
2746
2747 /** @function processStartOptions */
2748 function processStartOptions() {
2749 // open image or album
2750 // 1. load hidden albums
2751 // 2. check if location hash set (deep linking)
2752 // 3. check openOnStart parameter
2753 // 4. open root album (ID=-1)
2754
2755 // hidden/private albums are loaded on plugin start
2756 if( G.albumListHidden.length > 0 ) {
2757 jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
2758 return;
2759 }
2760
2761 if( !ProcessLocationHash() ) {
2762 processStartOptionsPart2();
2763 }
2764 }
2765
2766 /** @function processStartOptionsPart2 */
2767 function processStartOptionsPart2() {
2768
2769 // Check location hash + start parameters -> determine what to do on start
2770 // openOnStart parameter
2771 if( G.O.openOnStart != '' ) {
2772 var IDs = parseIDs(G.O.openOnStart);
2773 if( IDs.imageID != '0' ) {
2774 DisplayPhoto(IDs.imageID, IDs.albumID);
2775 }
2776 else {
2777 DisplayAlbum('-1', IDs.albumID);
2778 }
2779 }
2780 else {
2781 // open root album (ID = -1)
2782 DisplayAlbum('-1', 0);
2783 }
2784 }
2785
2786 // Parse string to extract albumID and imageID (format albumID/imageID)
2787 function parseIDs( IDs ) {
2788 var r={ albumID: '0', imageID: '0' };
2789
2790 var t=IDs.split('/');
2791 if( t.length > 0 ) {
2792 r.albumID = t[0];
2793 if( t.length > 1 ) {
2794 r.imageID = t[1];
2795 }
2796 }
2797 return r;
2798 }
2799
2800
2801 /** @function DisplayAlbum */
2802 function DisplayAlbum( imageID, albumID ) {
2803 // close viewer if already displayed
2804 if( G.VOM.viewerDisplayed ) {
2805 CloseInternalViewer(null);
2806 }
2807
2808 // set current navigation level (l1 or lN)
2809 var albumIdx = NGY2Item.GetIdx(G, albumID);
2810 G.GOM.curNavLevel = 'lN';
2811 if( albumIdx == 0 ) {
2812 G.GOM.curNavLevel = 'l1';
2813 }
2814 G.layout.SetEngine();
2815 G.galleryResizeEventEnabled = false;
2816
2817 if( albumIdx == -1 ) {
2818 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
2819 albumIdx = G.I.length - 1;
2820 }
2821
2822 if( !G.I[albumIdx].contentIsLoaded ) {
2823 // get content of the album if not already loaded
2824 AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
2825 return;
2826 }
2827
2828 ThumbnailSelectionClear();
2829
2830 G.GOM.pagination.currentPage = 0;
2831 SetLocationHash( albumID, '' );
2832 GalleryRender( albumIdx );
2833
2834 }
2835
2836
2837 //----- manage the bottom area of the gallery -> "pagination" or "more button"
2838 function GalleryBottomManage() {
2839
2840 switch( G.galleryDisplayMode.Get() ) {
2841 case 'PAGINATION':
2842 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
2843 ManagePagination( G.GOM.albumIdx );
2844 }
2845 break;
2846 case 'MOREBUTTON':
2847 G.$E.conTnBottom.off('click');
2848 var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
2849 if( nb == 0 ) {
2850 G.$E.conTnBottom.empty();
2851 }
2852 else {
2853 G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
2854 G.$E.conTnBottom.on('click', function(e) {
2855 G.GOM.displayedMoreSteps++;
2856 GalleryResize();
2857 });
2858 }
2859 break;
2860 case 'FULLCONTENT':
2861 default:
2862 break;
2863 }
2864 }
2865
2866
2867 // add one album/folder to the breadcrumb
2868 function breadcrumbAdd( albumIdx ) {
2869
2870 var ic='';
2871 if( !G.O.breadcrumbHideIcons ) {
2872 ic=G.O.icons.breadcrumbAlbum;
2873 if( albumIdx == 0 ) {
2874 ic=G.O.icons.breadcrumbHome;
2875 }
2876 }
2877 var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
2878 if( G.O.breadcrumbOnlyCurrentLevel ) {
2879 // link to parent folder (only 1 level is displayed in the breadcrumb)
2880 if( albumIdx == 0 ) {
2881 // no parent level -> stay on current one
2882 jQuery($newDiv).data('albumID','0');
2883 }
2884 else {
2885 jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
2886 }
2887 }
2888 else {
2889 // link to current folder
2890 jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
2891 }
2892 $newDiv.click(function() {
2893 var cAlbumID=jQuery(this).data('albumID');
2894 DisplayAlbum('-1', cAlbumID);
2895 return;
2896 });
2897 }
2898
2899 // add one separator to breadcrumb
2900 function breadcrumbAddSeparator( lastAlbumID ) {
2901 var $newSep=jQuery('<div class="oneItem">'+(G.O.RTL ? G.O.icons.breadcrumbSeparatorRtl : G.O.icons.breadcrumbSeparator)+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
2902 jQuery($newSep).data('albumIdx',lastAlbumID);
2903 $newSep.click(function() {
2904 var sepAlbumIdx=jQuery(this).data('albumIdx');
2905 DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
2906 return;
2907 });
2908 }
2909
2910
2911
2912 // Manage the gallery toolbar (breadcrumb + tag filter)
2913 function GalleryNavigationBar( albumIdx ) {
2914
2915 // Title + background image
2916 // var bgImage='';
2917 // var l=G.I.length;
2918 // var albumID = G.I[albumIdx].GetID();
2919 // for( var idx=0; idx<l ; idx++) {
2920 // var item=G.I[idx];
2921 // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
2922 // bgImage='<div id="pipo" class="pipo" style="height: 150px; width:100%; background-image: url("' + item.responsiveURL() + '"); background-size: cover; background-position: center center; filter:blur(2px)">pipo</div>';
2923 // break;
2924 // }
2925 // }
2926
2927 //console.log(bgImage);
2928
2929 // new navigation bar items are not build in the DOM, but in memory
2930 G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
2931 //G.GOM.navigationBar.$newContent = jQuery(bgImage );
2932 //console.log(G.GOM.navigationBar.$newContent);
2933
2934 //-- manage breadcrumb
2935 if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
2936 // retrieve new folder level
2937 var newLevel = 0,
2938 lstItems=[];
2939 if( albumIdx != 0 ) {
2940 var l=G.I.length,
2941 parentID=0;
2942
2943 lstItems.push(albumIdx);
2944 var curIdx=albumIdx;
2945 newLevel++;
2946
2947 while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
2948 for(var i=1; i < l; i++ ) {
2949 if( G.I[i].GetID() == G.I[curIdx].albumID ) {
2950 curIdx=i;
2951 lstItems.push(curIdx);
2952 newLevel++;
2953 break;
2954 }
2955 }
2956 }
2957 }
2958
2959 // build breadcrumb
2960 if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
2961 BreadcrumbBuild( lstItems );
2962 }
2963 }
2964
2965
2966 //-- manage and build tag filters
2967 if( G.galleryFilterTags.Get() != false ) {
2968 var nTags=G.I[albumIdx].albumTagList.length;
2969 if( nTags > 0 ) {
2970 for(var i=0; i < nTags; i++ ) {
2971 var s=G.I[albumIdx].albumTagList[i];
2972 var ic=G.O.icons.navigationFilterUnselected;
2973 var tagClass='Unselected';
2974 if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
2975 tagClass='Selected';
2976 ic=G.O.icons.navigationFilterSelected;
2977 }
2978 var $newTag=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter'+tagClass+'">'+ic+' '+s+'</div>').appendTo(G.GOM.navigationBar.$newContent);
2979 $newTag.click(function() {
2980 var $this=jQuery(this);
2981 var tag=$this.text().replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
2982 // if( $this.hasClass('oneTagUnselected') ){
2983 if( $this.hasClass('nGY2NavFilterUnselected') ){
2984 G.I[albumIdx].albumTagListSel.push(tag);
2985 }
2986 else {
2987 var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
2988 if( tidx != -1 ) {
2989 G.I[albumIdx].albumTagListSel.splice(tidx,1);
2990 }
2991 }
2992 $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
2993 DisplayAlbum('-1', G.I[albumIdx].GetID());
2994 });
2995 }
2996 var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+G.O.icons.navigationFilterSelectedAll+'</div>').appendTo(G.GOM.navigationBar.$newContent);
2997 $newClearFilter.click(function() {
2998 var nTags=G.I[albumIdx].albumTagList.length;
2999 G.I[albumIdx].albumTagListSel=[];
3000 for(var i=0; i <nTags; i++ ) {
3001 var s=G.I[albumIdx].albumTagList[i];
3002 G.I[albumIdx].albumTagListSel.push(s);
3003 }
3004 DisplayAlbum('-1', G.I[albumIdx].GetID());
3005 });
3006 }
3007 }
3008
3009 }
3010
3011 function BreadcrumbBuild(lstItems) {
3012
3013 // console.log(G.GOM.navigationBar.$newContent);
3014 jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
3015 // console.log(G.GOM.navigationBar.$newContent);
3016
3017 if( G.O.breadcrumbOnlyCurrentLevel ) {
3018 // display only 1 separator and the current folder level
3019 if( lstItems.length == 0 ) {
3020 breadcrumbAdd(0);
3021 }
3022 else {
3023 var last=lstItems.length-1;
3024 if( lstItems.length == 1 ) {
3025 breadcrumbAddSeparator(0); // root level
3026 }
3027 else {
3028 breadcrumbAddSeparator(lstItems[0]);
3029 }
3030 breadcrumbAdd(lstItems[0]);
3031 }
3032 }
3033 else {
3034 // display the full breadcrum (full folder levels including root level)
3035 breadcrumbAdd(0);
3036 if( lstItems.length > 0 ) {
3037 breadcrumbAddSeparator(0);
3038 for(var i=lstItems.length-1; i>=0 ; i-- ) {
3039 breadcrumbAdd(lstItems[i]);
3040 if( i > 0 ) {
3041 breadcrumbAddSeparator(lstItems[i-1]);
3042 }
3043 }
3044 }
3045 }
3046
3047 }
3048
3049
3050 // Display gallery pagination
3051 function ManagePagination( albumIdx ) {
3052
3053 G.$E.conTnBottom.css('opacity', 0);
3054 G.$E.conTnBottom.children().remove();
3055
3056 if( G.GOM.items.length == 0 ) { return; } // no thumbnail to display
3057
3058 // calculate the number of pages
3059 var nbPages=Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
3060
3061 // only one page -> do not display pagination
3062 if( nbPages == 1 ) { return; }
3063
3064 // check if current page still exists (for example after a resize)
3065 if( G.GOM.pagination.currentPage > (nbPages-1) ) {
3066 G.GOM.pagination.currentPage = nbPages-1;
3067 }
3068
3069 GalleryRenderGetInterval();
3070 // nothing to display --> exit
3071 if( G.GOM.displayInterval.len == 0 ) { return; }
3072
3073 // display "previous"
3074 if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
3075 var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
3076 $eltPrev.click(function(e) {
3077 paginationPreviousPage();
3078 });
3079 }
3080
3081 var firstPage = 0;
3082 var lastPage = nbPages;
3083 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
3084 // no 'previous'/'next' and no max number of pagination items
3085 firstPage = 0;
3086 }
3087 else {
3088 // display pagination numbers and previous/next
3089 var vp = G.O.paginationVisiblePages;
3090 var numberOfPagesToDisplay = G.O.paginationVisiblePages;
3091 if( numberOfPagesToDisplay >= nbPages ) {
3092 firstPage = 0;
3093 }
3094 else {
3095 // we have more pages than we want to display
3096 var nbBeforeAfter = 0;
3097 if( isOdd(numberOfPagesToDisplay) ) {
3098 nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
3099 }
3100 else {
3101 nbBeforeAfter = numberOfPagesToDisplay / 2;
3102 }
3103
3104 if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
3105 firstPage = 0;
3106 lastPage = numberOfPagesToDisplay - 1;
3107 if( lastPage > nbPages ) {
3108 lastPage = nbPages - 1;
3109 }
3110 }
3111 else {
3112 firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
3113 lastPage = firstPage + numberOfPagesToDisplay;
3114 if( lastPage > nbPages ) {
3115 lastPage = nbPages - 1;
3116 }
3117 }
3118
3119 if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
3120 firstPage = lastPage - numberOfPagesToDisplay;
3121 if( firstPage < 0 ) {
3122 firstPage = 0;
3123 }
3124 }
3125
3126 }
3127 }
3128
3129 // render pagination items
3130 for(var i = firstPage; i < lastPage; i++ ) {
3131 var c = '';
3132 var p = '';
3133
3134 switch( G.O.galleryPaginationMode ) {
3135 case 'NUMBERS':
3136 c = 'nGY2paginationItem';
3137 p = i + 1;
3138 break;
3139 case 'DOTS':
3140 c = 'nGY2paginationDot';
3141 break;
3142 case 'RECTANGLES':
3143 c = 'nGY2paginationRectangle';
3144 break;
3145 }
3146 if( i == G.GOM.pagination.currentPage ) {
3147 c += 'CurrentPage';
3148 }
3149
3150 var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
3151 elt$.data('pageNumber', i );
3152 elt$.click( function(e) {
3153 G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
3154 TriggerCustomEvent('pageChanged');
3155 GalleryDisplayPart1( true );
3156 GalleryDisplayPart2( true );
3157 });
3158
3159 }
3160
3161 // display "next"
3162 if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
3163 var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
3164 $eltNext.click( function(e) {
3165 paginationNextPage();
3166 });
3167 }
3168
3169 G.$E.conTnBottom.css('opacity', 1);
3170
3171 }
3172 function isOdd(num) { return (num % 2) == 1;}
3173
3174 // pagination - next page
3175 function paginationNextPage() {
3176 var aIdx = G.GOM.albumIdx,
3177 n1 = 0;
3178 ThumbnailHoverOutAll();
3179
3180 // pagination - max lines per page mode
3181 if( G.galleryMaxRows.Get() > 0 ) {
3182 // number of pages
3183 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3184 }
3185 var n2 = Math.ceil(n1);
3186 var pn = G.GOM.pagination.currentPage;
3187 if( pn < (n2-1) ) {
3188 pn++;
3189 }
3190 else {
3191 pn = 0;
3192 }
3193
3194 G.GOM.pagination.currentPage = pn;
3195 TriggerCustomEvent('pageChanged');
3196
3197 GalleryDisplayPart1( true );
3198 GalleryDisplayPart2( true );
3199 }
3200
3201 // pagination - previous page
3202 function paginationPreviousPage() {
3203 // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
3204 var aIdx = G.GOM.albumIdx,
3205 n1 = 0;
3206
3207 ThumbnailHoverOutAll();
3208
3209 // pagination - max lines per page mode
3210 if( G.galleryMaxRows.Get() > 0 ) {
3211 // number of pages
3212 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3213 }
3214 var n2 = Math.ceil(n1);
3215
3216 // var pn=G.$E.conTnBottom.data('currentPageNumber');
3217 var pn = G.GOM.pagination.currentPage;
3218 if( pn > 0 ) {
3219 pn--;
3220 }
3221 else {
3222 pn = n2 - 1;
3223 }
3224
3225 G.GOM.pagination.currentPage = pn;
3226 TriggerCustomEvent('pageChanged');
3227 GalleryDisplayPart1( true );
3228 GalleryDisplayPart2( true );
3229 }
3230
3231 // retrieve the from/to intervall for gallery thumbnail render
3232 function GalleryRenderGetInterval() {
3233 G.GOM.displayInterval.from = 0;
3234 G.GOM.displayInterval.len = G.I.length;
3235
3236 switch( G.galleryDisplayMode.Get() ) {
3237 case 'PAGINATION':
3238 if( G.layout.support.rows ) {
3239 var nbTn = G.GOM.items.length;
3240 var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
3241 var lastRow = firstRow + G.galleryMaxRows.Get();
3242 var firstTn = -1;
3243 G.GOM.displayInterval.len = 0;
3244 for( var i = 0; i < nbTn ; i++ ) {
3245 var curTn = G.GOM.items[i];
3246 if( curTn.row >= firstRow && curTn.row < lastRow ) {
3247 if( firstTn == -1 ) {
3248 G.GOM.displayInterval.from = i;
3249 firstTn = i;
3250 }
3251 G.GOM.displayInterval.len++;
3252 }
3253 }
3254 }
3255 break;
3256 case 'MOREBUTTON':
3257 if( G.layout.support.rows ) {
3258 var nbTn = G.GOM.items.length;
3259 var lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
3260 G.GOM.displayInterval.len = 0;
3261 for( var i = 0; i < nbTn ; i++ ) {
3262 var curTn = G.GOM.items[i];
3263 if( curTn.row < lastRow ) {
3264 G.GOM.displayInterval.len++;
3265 }
3266 }
3267 }
3268 break;
3269 case 'ROWS':
3270 if( G.layout.support.rows ) {
3271 var nbTn = G.GOM.items.length;
3272 var lastRow = G.galleryMaxRows.Get();
3273 if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3274 if( lastRow > (G.GOM.lastFullRow + 1) ) {
3275 lastRow = G.GOM.lastFullRow + 1;
3276 }
3277 }
3278 G.GOM.displayInterval.len = 0;
3279 for( var i = 0; i < nbTn ; i++ ) {
3280 var curTn = G.GOM.items[i];
3281 if( curTn.row < lastRow ) {
3282 G.GOM.displayInterval.len++;
3283 }
3284 }
3285 }
3286 break;
3287 default:
3288 case 'FULLCONTENT':
3289 if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3290 var nbTn = G.GOM.items.length;
3291 var lastRow = G.GOM.lastFullRow + 1;
3292 G.GOM.displayInterval.len = 0;
3293 for( var i = 0; i < nbTn ; i++ ) {
3294 var curTn = G.GOM.items[i];
3295 if( curTn.row < lastRow ) {
3296 G.GOM.displayInterval.len++;
3297 }
3298 }
3299 }
3300 break;
3301 }
3302 }
3303
3304
3305 // RENDER THE GALLERY
3306 function GalleryRender( albumIdx ) {
3307 TriggerCustomEvent('galleryRenderStart');
3308
3309 clearTimeout(G.GOM.slider.timerID);
3310 G.GOM.slider.hostIdx = -1; // disabled slider on thumbnail
3311
3312 var fu=G.O.fnGalleryRenderStart;
3313 if( fu !== null ) {
3314 typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3315 }
3316
3317 G.layout.SetEngine();
3318 G.galleryResizeEventEnabled=false;
3319 G.GOM.albumIdx = -1;
3320 G.GOM.lastDisplayedIdx = -1;
3321
3322 // pagination
3323 if( G.$E.conTnBottom !== undefined ) {
3324 // G.$E.conTnBottom.children().remove();
3325 G.$E.conTnBottom.empty();
3326 }
3327
3328 // navigation toolbar (breadcrumb + tag filters)
3329 GalleryNavigationBar(albumIdx);
3330
3331 if( G.GOM.firstDisplay ) {
3332 G.GOM.firstDisplay = false;
3333 var d = Date.now()-G.GOM.firstDisplayTime;
3334 if( d < G.O.galleryRenderDelay ) {
3335 // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3336 requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3337 }
3338 else {
3339 GalleryRenderPart1( albumIdx );
3340 }
3341 G.O.galleryRenderDelay = 0;
3342
3343 }
3344 else {
3345 var hideNavigationBar = false;
3346 if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
3347 hideNavigationBar = true;
3348 }
3349
3350 // hide everything
3351 var tweenable = new NGTweenable();
3352 tweenable.tween({
3353 from: { 'opacity': 1 },
3354 to: { 'opacity': 0 },
3355 duration: 300,
3356 easing: 'easeInQuart',
3357 attachment: { h: hideNavigationBar },
3358 step: function (state, att) {
3359 // window.ng_draf( function() {
3360 G.$E.conTnParent.css({'opacity': state.opacity });
3361 if( att.h ) {
3362 G.$E.conNavigationBar.css({ 'opacity': state.opacity });
3363 }
3364 // });
3365 },
3366 finish: function (state, att) {
3367 // window.ng_draf( function() {
3368 if( att.h ) {
3369 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
3370 }
3371 // scroll to top of the gallery if needed
3372 var galleryOTop = G.$E.base.offset().top;
3373 if( galleryOTop < G.GOM.cache.viewport.t ) {
3374 // jQuery('html, body').animate({scrollTop: galleryOTop}, 200);
3375 jQuery('html, body').animate({scrollTop: galleryOTop}, 500, "linear", function() {
3376 GalleryRenderPart1( albumIdx );
3377 });
3378 }
3379 else {
3380 GalleryRenderPart1( albumIdx );
3381 }
3382 // });
3383 }
3384 });
3385 }
3386 }
3387
3388
3389 function GalleryRenderPart1( albumIdx ) {
3390 // display new navigation bar
3391 var oldN = G.$E.conNavigationBar.children().length;
3392 G.$E.conNavigationBar.empty();
3393 G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
3394 // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
3395 if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
3396 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
3397 var tweenable = new NGTweenable();
3398 tweenable.tween({
3399 from: { opacity: 0 },
3400 to: { opacity: 1 },
3401 duration: 200,
3402 easing: 'easeInQuart',
3403 step: function (state) {
3404 // window.ng_draf( function() {
3405 G.$E.conNavigationBar.css( state );
3406 // });
3407 },
3408 finish: function (state) {
3409 // window.ng_draf( function() {
3410 G.$E.conNavigationBar.css({ 'opacity': 1 });
3411 // display gallery
3412 // GalleryRenderPart2( albumIdx );
3413 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3414 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3415 // });
3416 }
3417 });
3418 }
3419 else {
3420 // display gallery
3421 // GalleryRenderPart2( albumIdx );
3422 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3423 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3424 }
3425
3426 }
3427
3428 // Gallery render part 2 -> remove all thumbnails
3429 function GalleryRenderPart2(albumIdx) {
3430 G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
3431 if( isNaN(G.GOM.lastZIndex) ) {
3432 G.GOM.lastZIndex=0;
3433 }
3434 G.$E.conTnParent.css({ 'opacity': 0 });
3435 G.$E.conTn.off().empty();
3436 var l = G.I.length;
3437 for( var i = 0; i < l ; i++ ) {
3438 // reset each item
3439 var item = G.I[i];
3440 item.hovered = false;
3441 item.$elt = null;
3442 item.$Elts = [];
3443 item.eltTransform = [];
3444 item.eltFilter = [];
3445 item.width = 0;
3446 item.height = 0;
3447 item.left = 0;
3448 item.top = 0;
3449 item.resizedContentWidth = 0;
3450 item.resizedContentHeight = 0;
3451 item.thumbnailImgRevealed = false;
3452 }
3453
3454 if( G.CSStransformName == null ) {
3455 G.$E.conTn.css('left', '0px');
3456 }
3457 else {
3458 // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
3459 G.$E.conTn.css( G.CSStransformName, 'none');
3460 }
3461
3462 // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3463 requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3464 // GalleryRenderPart3(albumIdx);
3465
3466 }
3467
3468 // Gallery render part 2 -> start building the new gallery
3469 function GalleryRenderPart3(albumIdx) {
3470 var d = new Date();
3471
3472 G.$E.conTnParent.css( 'opacity', 1);
3473
3474 G.GOM.items = [];
3475 G.GOM.displayedMoreSteps = 0;
3476 // retrieve label height
3477 if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
3478 // retrieve height each time because size can change depending on thumbnail's settings
3479 G.tn.labelHeight[G.GOM.curNavLevel]=ThumbnailGetLabelHeight();
3480 }
3481 else {
3482 G.tn.labelHeight[G.GOM.curNavLevel]=0;
3483 }
3484 G.GOM.albumIdx=albumIdx;
3485
3486 TriggerCustomEvent('galleryRenderEnd');
3487 var fu=G.O.fnGalleryRenderEnd;
3488 if( fu !== null ) {
3489 typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3490 }
3491
3492 // Step 1: populate GOM
3493 if( GalleryPopulateGOM() ) {
3494
3495 // step 2: calculate layout
3496 GallerySetLayout();
3497
3498 // step 3: display gallery
3499 GalleryAppear();
3500 // GalleryDisplay( false );
3501 GalleryDisplayPart1( false );
3502 // setTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
3503 requestTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
3504 }
3505 else {
3506 G.galleryResizeEventEnabled=true;
3507 }
3508
3509 if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
3510
3511 }
3512
3513
3514 // Resize the gallery
3515 function GalleryResize() {
3516 var d = new Date();
3517 G.galleryResizeEventEnabled = false;
3518 // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
3519 if( GallerySetLayout() == false ) {
3520 G.galleryResizeEventEnabled = true;
3521 if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
3522 return;
3523 }
3524 if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
3525
3526 GalleryDisplayPart1( false );
3527 GalleryDisplayPart2( false );
3528
3529 if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
3530 }
3531
3532
3533
3534 // copy items (album content) to GOM
3535 function GalleryPopulateGOM() {
3536
3537 var preloadImages = '';
3538 var imageSizeRequested = false;
3539 var albumID = G.I[G.GOM.albumIdx].GetID();
3540 var l = G.I.length;
3541 var cnt = 0;
3542
3543 for( var idx = 0; idx < l; idx++ ) {
3544 var item = G.I[idx];
3545 // check album
3546 if( item.isToDisplay(albumID) ) {
3547 var w = item.thumbImg().width;
3548 var h = item.thumbImg().height;
3549 // if unknown image size and layout is not grid --> we need to retrieve the size of the images
3550 if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
3551 // if( true ) {
3552 imageSizeRequested = true;
3553 preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
3554 }
3555
3556 // set default size if required
3557 if( h == 0 ) {
3558 h = G.tn.defaultSize.getHeight();
3559 }
3560 if( w == 0 ) {
3561 w = G.tn.defaultSize.getWidth();
3562 }
3563 var tn = new G.GOM.GTn(idx, w, h);
3564 G.GOM.items.push(tn);
3565 cnt++;
3566 }
3567 }
3568
3569 TriggerCustomEvent('galleryObjectModelBuilt');
3570 var fu = G.O.fnGalleryObjectModelBuilt;
3571 if( fu !== null ) {
3572 typeof fu == 'function' ? fu() : window[fu]();
3573 }
3574
3575 if( imageSizeRequested ) {
3576 // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
3577 var $newImg = jQuery(preloadImages);
3578 var gi_imgLoad = ngimagesLoaded( $newImg );
3579 $newImg = null;
3580 gi_imgLoad.on( 'progress', function( instance, image ) {
3581
3582 if( image.isLoaded ) {
3583 var idx = image.img.getAttribute('data-idx');
3584 var albumIdx = image.img.getAttribute('data-albumidx');
3585 if( albumIdx == G.GOM.albumIdx ) {
3586 // ignore event if not on current album
3587 var curTn = G.GOM.items[idx];
3588 curTn.imageWidth = image.img.naturalWidth;
3589 curTn.imageHeight = image.img.naturalHeight;
3590 var item = G.I[curTn.thumbnailIdx];
3591 item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
3592 item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
3593
3594 // resize the gallery
3595 G.GalleryResizeThrottled();
3596
3597 // set the retrieved size to all levels with same configuration
3598 var object = item.thumbs.width.l1;
3599 for (var property in object) {
3600 if (object.hasOwnProperty(property)) {
3601 if( property != G.GOM.curWidth ) {
3602 if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
3603 item.thumbs.width.l1[property] = curTn.imageWidth;
3604 item.thumbs.height.l1[property] = curTn.imageHeight;
3605 }
3606 }
3607 }
3608 }
3609 object = item.thumbs.width.lN;
3610 for (var property in object) {
3611 if (object.hasOwnProperty(property)) {
3612 if( property != G.GOM.curWidth ) {
3613 if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
3614 item.thumbs.width.lN[property] = curTn.imageWidth;
3615 item.thumbs.height.lN[property] = curTn.imageHeight;
3616 }
3617 }
3618 }
3619 }
3620 }
3621 }
3622 });
3623 G.galleryResizeEventEnabled = true;
3624 return false;
3625 }
3626 else {
3627 return true;
3628 }
3629
3630 }
3631
3632 //----- Calculate the layout of the thumbnails
3633 function GallerySetLayout() {
3634 var r = true;
3635 // width of the available area
3636 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3637 G.GOM.displayArea = { width:0, height:0 };
3638
3639 switch( G.layout.engine ) {
3640 case 'JUSTIFIED':
3641 r = GallerySetLayoutWidthtAuto();
3642 break;
3643 case 'CASCADING':
3644 r = GallerySetLayoutHeightAuto();
3645 break;
3646 case 'MOSAIC':
3647 r = GallerySetLayoutMosaic();
3648 break;
3649 case 'GRID':
3650 default:
3651 r = GallerySetLayoutGrid();
3652 break;
3653 }
3654
3655 TriggerCustomEvent('galleryLayoutApplied');
3656 var fu = G.O.fnGalleryLayoutApplied;
3657 if( fu !== null ) {
3658 typeof fu == 'function' ? fu() : window[fu]();
3659 }
3660 return r;
3661
3662 }
3663
3664
3665 //----- CASCADING LAYOUT
3666 function GallerySetLayoutHeightAuto() {
3667 var curCol = 0,
3668 areaWidth = G.GOM.cache.areaWidth,
3669 curRow = 0,
3670 colHeight = [],
3671 maxCol = NbThumbnailsPerRow(areaWidth),
3672 gutterWidth = 0,
3673 gutterHeight = G.tn.opt.Get('gutterHeight');
3674 var w = 0;
3675 var scaleFactor = 1;
3676 var tnWidth = G.tn.defaultSize.getOuterWidth();
3677 var nbTn = G.GOM.items.length;
3678 var curPosY = 0;
3679
3680 if( G.O.thumbnailAlignment == 'justified' ) {
3681 maxCol = Math.min(maxCol, nbTn);
3682 gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
3683 }
3684 else {
3685 gutterWidth=G.tn.opt.Get('gutterWidth');
3686 }
3687
3688
3689 var borderWidth = G.tn.borderWidth * 2;
3690 var borderHeight = G.tn.borderHeight * 2;
3691
3692 G.GOM.lastFullRow=-1; // feature disabled
3693
3694 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
3695 if( G.O.thumbnailAlignment == 'fillWidth' ) {
3696 // fillWidth --> evaluate scale factor and number of columns
3697 var totalGutterWidth = (maxCol - 1) * gutterWidth;
3698 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
3699 if( scaleFactor > 1 ) {
3700 maxCol++; // add one column and re-evaluate the scale factor
3701 }
3702 totalGutterWidth = (maxCol - 1) * gutterWidth;
3703 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
3704 }
3705
3706
3707 tnWidth = tnWidth * scaleFactor;
3708 var contentWidth = tnWidth - borderWidth;
3709
3710 // loop to position the thumbnails, and set their size
3711 var baseHeight = G.tn.opt.Get('baseGridHeight') * scaleFactor;
3712 for( var i = 0; i < nbTn ; i++ ) {
3713 var curTn = G.GOM.items[i];
3714 if( curTn.deleted == true ) { break; } // item is logically deleted
3715 if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
3716 var curPosX = 0,
3717 curPosY = 0;
3718 var imageRatio = curTn.imageHeight / curTn.imageWidth;
3719 // curTn.resizedContentWidth = tnWidth - borderWidth;
3720 curTn.resizedContentWidth = contentWidth;
3721 curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
3722 if( baseHeight > 0 ) {
3723 // grid based vertical position
3724 var t= Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
3725 curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
3726 }
3727
3728 curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
3729 curTn.width = tnWidth;
3730 curTn.row = 0;
3731
3732 if( curRow == 0 ) {
3733 // first row
3734 curPosX = curCol * (tnWidth + gutterWidth);
3735 colHeight[curCol] = curTn.height + gutterHeight;
3736
3737 curCol++;
3738 if( curCol >= maxCol ) {
3739 curCol = 0;
3740 curRow++;
3741 }
3742 }
3743 else {
3744 var c=0,
3745 minColHeight=colHeight[0];
3746 for( var j = 1; j < maxCol; j++) {
3747 if( (colHeight[j] + 5) < minColHeight ) { // +5 --> threshold
3748 minColHeight = colHeight[j];
3749 c = j;
3750 //break;
3751 }
3752 }
3753 curPosY = colHeight[c];
3754 curPosX = c * (tnWidth + gutterWidth);
3755 colHeight[c] = curPosY + curTn.height + gutterHeight;
3756 }
3757
3758 var x = curPosX;
3759 if( G.O.RTL) {
3760 x= w - curPosX - tnWidth;
3761 }
3762
3763 curTn.left = x;
3764 curTn.top = curPosY;
3765 }
3766 }
3767
3768 G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
3769 return true;
3770 }
3771
3772
3773 //----- JUSTIFIED LAYOUT
3774 function GallerySetLayoutWidthtAuto() {
3775 var curWidth = 0,
3776 areaWidth = G.GOM.cache.areaWidth,
3777 lastPosX = 0,
3778 curPosY = 0,
3779 rowLastItem = [],
3780 rowNum = 0,
3781 rowHeight = [],
3782 bNewRow = false,
3783 cnt = 0,
3784 gutterWidth = G.tn.opt.Get('gutterWidth'),
3785 gutterHeight = G.tn.opt.Get('gutterHeight');
3786 // by grief-of-these-days
3787 var maxRowHeightVertical = 0; // max height of a row with vertical thumbs
3788 var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
3789 var rowHasVertical = false; // current row has vertical thumbs
3790 var rowHasHorizontal = false; // current row has horizontal thumbs
3791
3792 var tnHeight = G.tn.defaultSize.getOuterHeight();
3793 var borderWidth = G.tn.borderWidth * 2;
3794 var borderHeight = G.tn.borderHeight * 2;
3795 var nbTnInCurrRow = 1;
3796 var nbTn = G.GOM.items.length;
3797
3798 // first loop --> retrieve each row image height
3799 for( var i = 0; i < nbTn ; i++ ) {
3800 var curTn = G.GOM.items[i];
3801 if( curTn.deleted == true ) { break; } // item is logically deleted
3802 if( curTn.imageWidth > 0 ) {
3803 var imageRatio = curTn.imageWidth / curTn.imageHeight;
3804 var imageWidth = Math.floor( tnHeight * imageRatio );
3805
3806 if( bNewRow ) {
3807 bNewRow = false;
3808 rowNum++;
3809 curWidth = 0;
3810 rowHasVertical = false;
3811 rowHasHorizontal = false;
3812 nbTnInCurrRow = 1;
3813 }
3814 // by grief-of-these-days
3815 if( curTn.imageHeight > curTn.imageWidth ) {
3816 rowHasVertical = true;
3817 }
3818 else {
3819 rowHasHorizontal = true;
3820 }
3821
3822 if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
3823 // enough place left in the current row
3824 curWidth += imageWidth + gutterWidth;
3825 rowHeight[rowNum] = tnHeight;
3826
3827 // prevent incomplete row from being heigher than the previous ones.
3828 // by grief-of-these-days
3829 var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
3830 if( rowHeightLimit > 0 ) {
3831 rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
3832 }
3833
3834 rowLastItem[rowNum] = i;
3835 }
3836 else {
3837 // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
3838 curWidth += gutterWidth+imageWidth;
3839 var ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
3840 var rH = Math.floor(tnHeight * ratio);
3841 rowHeight[rowNum] = rH;
3842
3843 // save the max row height for each thumb orientation.
3844 // by grief-of-these-days
3845 if( rowHasVertical ) {
3846 maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
3847 }
3848 if( rowHasHorizontal ) {
3849 maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
3850 }
3851
3852 rowLastItem[rowNum] = i;
3853 bNewRow = true;
3854 }
3855 cnt++;
3856 nbTnInCurrRow++;
3857 }
3858 }
3859
3860 rowNum = 0;
3861 curPosY = 0;
3862 lastPosX = 0;
3863 cnt = 0;
3864
3865 G.GOM.lastFullRow = 0; // display at leat 1 row (even if not full)
3866
3867 // second loop --> calculate each thumbnail size
3868 for( var i = 0; i < nbTn ; i++ ) {
3869 var curTn = G.GOM.items[i];
3870 if( curTn.imageWidth > 0 ) {
3871 var imageRatio = curTn.imageWidth / curTn.imageHeight;
3872 var imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
3873
3874 if( i == rowLastItem[rowNum] ) {
3875 // row last item --> adjust image width because of rounding problems
3876 if( rowLastItem.length != (rowNum+1) ) {
3877 // last item in current row -> use the full remaining width
3878 imageWidth = areaWidth - lastPosX - borderWidth;
3879 }
3880 else {
3881 // very last item (on the last row)
3882 if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
3883 // reduce size if image is wider as the remaining space
3884 imageWidth = areaWidth - lastPosX - borderWidth;
3885 }
3886 }
3887 }
3888
3889 var rh = parseInt( rowHeight[rowNum] );
3890 imageWidth = parseInt( imageWidth );
3891
3892 // thumbnail image size
3893 curTn.resizedContentWidth = imageWidth;
3894 curTn.resizedContentHeight = rh;
3895 // thumbnail position and size
3896 curTn.width = imageWidth + borderWidth;
3897 curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
3898 curTn.row = rowNum;
3899
3900 curTn.top = curPosY;
3901 var x = lastPosX;
3902 if( G.O.RTL) {
3903 x = areaWidth - lastPosX - curTn.width ;
3904 }
3905 curTn.left = x;
3906
3907 lastPosX += curTn.width + gutterWidth;
3908
3909 if( i == rowLastItem[rowNum] ) {
3910 // start a new row
3911 curPosY += curTn.height + gutterHeight;
3912 G.GOM.lastFullRow = rowNum - 1;
3913 rowNum++;
3914 lastPosX = 0;
3915 }
3916 cnt++;
3917 }
3918 else {
3919 return false;
3920 }
3921 }
3922
3923 if( false ) {
3924 var newTop = 0;
3925 if( typeof GOMidx !== 'undefined' ) {
3926 // hover effect on gallery (vs on thumbnail) --> experimental / not used
3927 if( G.GOM.albumIdx != -1 ) {
3928 var hoveredTn = G.GOM.items[GOMidx];
3929 var item = G.I[hoveredTn.thumbnailIdx];
3930
3931 // hovered thumbnail
3932 hoveredTn.width += 40;
3933 hoveredTn.height += 40;
3934 // todo : left
3935
3936 for( var i = 0; i < nbTn ; i++ ) {
3937 var curTn = G.GOM.items[i];
3938 if( curTn.imageWidth > 0 ) {
3939 if( curTn.row == hoveredTn.row ) {
3940 // hovered row
3941 newTop = 40;
3942 if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
3943 // not hovered thumbnail
3944 // curTn.resizedContentWidth+=10;
3945 // curTn.resizedContentHeight+=20;
3946 // curTn.width+=10;
3947 curTn.top += 30;
3948 curTn.width -= 20;
3949 curTn.height -= 20;
3950 }
3951 }
3952 else {
3953 // not hovered row
3954 if( curTn.row == 0 ) {
3955 // first row
3956 }
3957 else {
3958 curTn.top += newTop;
3959 }
3960 }
3961 }
3962 }
3963 }
3964 }
3965 }
3966
3967 G.GOM.displayArea.width = areaWidth;
3968 return true;
3969 }
3970
3971
3972 //----- MOSAIC LAYOUT
3973 // Grid using a user defined pattern layout
3974 // With this layout, a pattern definition is handeld a row
3975 function GallerySetLayoutMosaic() {
3976 var areaWidth = G.GOM.cache.areaWidth;
3977 var gutterHeight = G.tn.opt.Get('gutterHeight');
3978 var gutterWidth = G.tn.opt.Get('gutterWidth');
3979 var borderWidth = G.tn.borderWidth * 2;
3980 var borderHeight = G.tn.borderHeight * 2;
3981
3982 var nbTn = G.GOM.items.length;
3983 var row = 0;
3984 var h = 0;
3985 var n = 0;
3986
3987
3988 // first loop: evaluate the gallery width based on the first row
3989 var nbCols = 0;
3990 var maxW = 0;
3991 var mosaicPattern = G.tn.settings.getMosaic();
3992 for( var i = 0; i < nbTn ; i++ ) {
3993 var curPatternElt = mosaicPattern[n];
3994
3995 var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
3996 var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
3997
3998 maxW=Math.max(maxW, cLeft + cWidth );
3999
4000 nbCols=Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
4001
4002 n++;
4003 if( n >= mosaicPattern.length ) {
4004 // end of pattern
4005 break;
4006 }
4007 }
4008 var totalGutterWidth = (nbCols - 1) * gutterWidth;
4009 var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
4010
4011 // second loop: position all the thumbnails based on the layout pattern
4012 row = 0;
4013 n = 0;
4014 var mosaicPattern = G.tn.settings.getMosaic();
4015 for( var i = 0; i < nbTn ; i++ ) {
4016 var curTn = G.GOM.items[i];
4017 var curPatternElt = mosaicPattern[n];
4018
4019 curTn.top = (curPatternElt.r - 1) * G.tn.defaultSize.getOuterHeight()*scaleFactor + (curPatternElt.r - 1) * gutterHeight + row * h + (G.tn.labelHeight.get()*(curPatternElt.r-1)) ;
4020 if( row > 0 ) {
4021 curTn.top += gutterHeight;
4022 }
4023
4024 curTn.left = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth()*scaleFactor + (curPatternElt.c - 1) * gutterWidth;
4025
4026 curTn.height = curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
4027 curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
4028
4029 curTn.width = curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor + (curPatternElt.w - 1) * gutterWidth;
4030 curTn.resizedContentWidth = curTn.width - borderWidth ;
4031
4032 curTn.row = row;
4033 if( row == 0 ) {
4034 h=Math.max(h, curTn.top + curTn.height);
4035 }
4036
4037 n++;
4038 if( n >= mosaicPattern.length ) {
4039 // end pattern -> new line
4040 n = 0;
4041 row++;
4042 }
4043 }
4044
4045 G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
4046 return true;
4047 }
4048
4049
4050
4051 // --- GRID LAYOUT
4052 function GallerySetLayoutGrid() {
4053 var curPosX= 0,
4054 curPosY= 0,
4055 areaWidth= G.GOM.cache.areaWidth,
4056 gutterWidth= 0,
4057 gutterHeight= G.tn.opt.Get('gutterHeight'),
4058 maxCol= NbThumbnailsPerRow(areaWidth),
4059 w= 0,
4060 cols= [],
4061 curCol= 0,
4062 newAreaWidth = areaWidth,
4063 tnWidth= G.tn.defaultSize.getOuterWidth();
4064 var scaleFactor = 1;
4065 var nbTn= G.GOM.items.length;
4066 var borderWidth = G.tn.borderWidth * 2;
4067 var borderHeight =G.tn.borderHeight * 2;
4068
4069 // retrieve gutter width
4070 if( G.O.thumbnailAlignment == 'justified' ) {
4071 maxCol = Math.min( maxCol, nbTn);
4072 gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
4073 }
4074 else {
4075 gutterWidth = G.tn.opt.Get('gutterWidth');
4076 }
4077
4078 // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
4079 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4080 if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
4081 // scaled --> evaluate scale factor and number of columns
4082 var totalGutterWidth = (maxCol-1) * gutterWidth;
4083 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
4084 if( scaleFactor > 1 ) {
4085 maxCol++; // add one column and re-evaluate the scale factor
4086 }
4087 totalGutterWidth = (maxCol-1) * gutterWidth;
4088 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4089 newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
4090 }
4091
4092
4093 G.GOM.lastFullRow = 0 ; // display at leat 1 row (even if not full)
4094 var lastPosY = 0;
4095 var row = 0;
4096
4097 tnWidth = tnWidth * scaleFactor;
4098 var contentWidth = tnWidth - borderWidth;
4099 var tnHeight = G.tn.defaultSize.getOuterHeight() * scaleFactor + G.tn.labelHeight.get();
4100 var contentHeight = G.tn.defaultSize.getOuterHeight() * scaleFactor - borderHeight;
4101
4102 // loop to position and to set size of all thumbnails
4103 for( var i = 0; i < nbTn ; i++ ) {
4104 if( curPosY == 0 ) {
4105 curPosX = curCol * (tnWidth + gutterWidth)
4106 cols[curCol] = curPosX;
4107 w = curPosX + tnWidth;
4108 }
4109 else {
4110 curPosX = cols[curCol];
4111 }
4112
4113 var x = curPosX;
4114 if( G.O.RTL ) {
4115 x = parseInt(newAreaWidth) - curPosX - tnWidth;
4116 }
4117
4118 // MANDATORY : set thumbnail position AND size
4119 var curTn=G.GOM.items[i];
4120 curTn.top = curPosY;
4121 curTn.left = x;
4122 curTn.height = tnHeight;
4123 curTn.width = tnWidth;
4124 // image size
4125 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4126 curTn.resizedContentWidth = contentWidth;
4127 curTn.resizedContentHeight = contentHeight;
4128 }
4129 curTn.row = row;
4130 lastPosY = curPosY;
4131
4132 curCol++;
4133 if( curCol >= maxCol ){
4134 // new line
4135 curCol = 0;
4136 curPosY += tnHeight + gutterHeight;
4137 G.GOM.lastFullRow = row;
4138 row++;
4139 }
4140 }
4141 G.GOM.displayArea.width = w;
4142 return true;
4143 }
4144
4145
4146 //----- Display the thumbnails according to the calculated layout
4147 function GalleryDisplayPart1( forceTransition ) {
4148 if( G.CSStransformName == null ) {
4149 G.$E.conTn.css( 'left' , '0px');
4150 }
4151 else {
4152 G.$E.conTn.css( G.CSStransformName , 'none');
4153 }
4154 CacheViewport();
4155 }
4156
4157 function CacheViewport() {
4158 G.GOM.cache.viewport = getViewport();
4159 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4160 G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
4161 }
4162
4163
4164
4165 function GalleryDisplayPart2( forceTransition ) {
4166
4167 var nbTn = G.GOM.items.length;
4168 G.GOM.itemsDisplayed = 0;
4169 var threshold = 50;
4170 var cnt = 0; // counter for delay between each thumbnail display
4171
4172
4173 GalleryRenderGetInterval();
4174
4175 for( var i = 0; i < nbTn ; i++ ) {
4176 var curTn = G.GOM.items[i];
4177 if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
4178 curTn.inDisplayArea = true;
4179 if( forceTransition ) {
4180 curTn.neverDisplayed = true;
4181 }
4182 G.GOM.itemsDisplayed++;
4183 cnt++;
4184 }
4185 else{
4186 curTn.inDisplayArea = false;
4187 }
4188 }
4189
4190 // bottom of the gallery (pagination, more button...)
4191 GalleryBottomManage();
4192
4193 var tnToDisplay = [];
4194 var tnToReDisplay = [];
4195
4196 G.GOM.clipArea.top = -1;
4197 cnt = 0 ;
4198 var lastTnIdx = -1;
4199 G.GOM.clipArea.height = 0;
4200 // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
4201 for( var i = 0; i < nbTn ; i++ ) {
4202 var curTn = G.GOM.items[i];
4203 if( curTn.inDisplayArea ) {
4204 if( G.GOM.clipArea.top == -1 ) {
4205 G.GOM.clipArea.top = curTn.top;
4206 }
4207 if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
4208 // with mosaic layout, the first thumbnail may not give the top position
4209 G.GOM.clipArea.top = curTn.top;
4210 }
4211
4212 G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
4213
4214 if( curTn.neverDisplayed ) {
4215 // thumbnail is not displayed -> check if in viewport to display or not
4216 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4217 // var left=containerOffset.left+curTn.left;
4218 if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
4219 // build thumbnail
4220 var item = G.I[curTn.thumbnailIdx];
4221 if( item.$elt == null ) {
4222 ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
4223 }
4224 tnToDisplay.push({idx:i, delay:cnt});
4225 cnt++;
4226 }
4227 }
4228 else {
4229 tnToReDisplay.push({idx: i, delay: 0});
4230 }
4231 // G.GOM.itemsDisplayed++;
4232 lastTnIdx = i;
4233 }
4234 else {
4235 curTn.displayed = false;
4236 var item = G.I[curTn.thumbnailIdx];
4237 if( item.$elt != null ){
4238 item.$elt.css({ opacity: 0, display: 'none' });
4239 }
4240 }
4241 }
4242
4243 var areaWidth = G.$E.conTnParent.width();
4244
4245 // set gallery area really used size
4246 // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
4247 if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
4248 G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
4249 G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
4250 G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
4251 // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
4252 }
4253
4254 if( areaWidth != G.$E.conTnParent.width() ) {
4255 // gallery area width changed since layout calculation (for example when a scrollbar appeared)
4256 // so we need re-calculate the layout before displaying the thumbnails
4257 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4258 GallerySetLayout();
4259 GalleryDisplayPart1( forceTransition );
4260 GalleryDisplayPart2( forceTransition );
4261 return;
4262 }
4263
4264 // counter of not displayed images (is displayed on the last thumbnail)
4265 if( G.layout.support.rows ) {
4266 if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4267 if( lastTnIdx < (nbTn - 1) ) {
4268 G.GOM.lastDisplayedIdxNew = lastTnIdx;
4269 }
4270 else {
4271 G.GOM.lastDisplayedIdxNew =- 1;
4272 }
4273 // remove last displayed counter
4274 if( G.GOM.lastDisplayedIdx != -1 ) {
4275 var item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
4276 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
4277 }
4278 }
4279 }
4280
4281
4282 // batch set position (and display animation) to all thumbnails
4283 // first display newly built thumbnails
4284 var nbBuild = tnToDisplay.length;
4285 G.GOM.thumbnails2Display=[];
4286 for( var i = 0; i < nbBuild ; i++ ) {
4287 // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
4288 ThumbnailSetPosition(tnToDisplay[i].idx, i);
4289 }
4290
4291 // then re-position already displayed thumbnails
4292 var n = tnToReDisplay.length;
4293 for( var i = 0; i < n ; i++ ) {
4294 // ThumbnailSetPosition(tnToReDisplay[i].idx, nbBuild+1);
4295 ThumbnailSetPosition(tnToReDisplay[i].idx, i);
4296 }
4297
4298 ThumbnailDisplayAnimBatch();
4299
4300 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
4301 G.galleryResizeEventEnabled = true;
4302 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4303 TriggerCustomEvent('galleryDisplayed');
4304 }
4305 else {
4306 // setTimeout(function() {
4307 requestTimeout( function() {
4308 // change value after the end of the display transistion of the newly built thumbnails
4309 G.galleryResizeEventEnabled = true;
4310 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4311 TriggerCustomEvent('galleryDisplayed');
4312 }, nbBuild * G.tn.opt.Get('displayInterval'));
4313 }
4314
4315 }
4316
4317
4318 // Thumbnail: set the new position
4319 function ThumbnailSetPosition( GOMidx, cnt ) {
4320 var newTop= 0;
4321 var curTn= G.GOM.items[GOMidx];
4322 var idx= G.GOM.items[GOMidx].thumbnailIdx;
4323 var item= G.I[idx];
4324
4325 if( curTn.neverDisplayed ) {
4326 // thumbnail is built but has never been displayed (=first display)
4327 var top = curTn.top - G.GOM.clipArea.top;
4328 if( G.tn.opt.Get('stacks') > 0 ) {
4329 // we have stacks -> do not display them here. They will be displayed at the end of the display animation
4330 item.$elt.last().css({ display: 'block'});
4331 item.$elt.css({ top: top , left: curTn.left });
4332 }
4333 else {
4334 item.$elt.css({ display: 'block', top: top , left: curTn.left });
4335 }
4336 newTop=top;
4337
4338 // display the image of the thumbnail when fully loaded
4339 if( G.O.thumbnailWaitImageLoaded === true ) {
4340 var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
4341 gi_imgLoad.on( 'progress', function( instance, image ) {
4342 if( image.isLoaded ) {
4343 var albumIdx = image.img.getAttribute('data-albumidx');
4344 if( albumIdx == G.GOM.albumIdx ) {
4345 // ignore event if not on current album
4346 var idx = image.img.getAttribute('data-idx');
4347 G.I[idx].ThumbnailImageReveal();
4348 }
4349 }
4350 });
4351 }
4352 // display the thumbnail
4353 ThumbnailAppear(GOMidx, cnt);
4354
4355 curTn.displayed = true;
4356 curTn.neverDisplayed = false;
4357 }
4358 else {
4359 var topOld = G.GOM.cache.containerOffset.top + item.top;
4360 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4361 newTop = curTn.top - G.GOM.clipArea.top;
4362 var vp = G.GOM.cache.viewport;
4363 if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
4364 ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) ) ) {
4365 // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
4366 if( curTn.displayed ) {
4367 // thumbnail is displayed
4368 if( item.top != curTn.top || item.left != curTn.left ) {
4369 // set position
4370 if( G.O.galleryResizeAnimation == true ) {
4371 // with transition
4372 var tweenable = new NGTweenable();
4373 tweenable.tween({
4374 from: { top: item.top, left: item.left, height: item.height, width: item.width },
4375 to: { top: newTop, left: curTn.left, height: curTn.height, width: curTn.width },
4376 attachment: { $e: item.$elt },
4377 duration: 100,
4378 delay: cnt * G.tn.opt.Get('displayInterval') / 5,
4379 // easing: 'easeInOutQuad',
4380 easing: 'easeOutQuart',
4381 step: function (state, att) {
4382 // window.ng_draf( function() {
4383 att.$e.css(state);
4384 // });
4385 },
4386 finish: function (state, att) {
4387 var _this=this;
4388 // window.ng_draf( function() {
4389 _this.dispose();
4390 // });
4391 }
4392 });
4393 }
4394 else {
4395 // set position without transition
4396 // item.$elt.css({ top: curTn.top , left: curTn.left });
4397 item.$elt.css({ top: newTop , left: curTn.left });
4398 }
4399 }
4400 }
4401 else {
4402 // re-display thumbnail
4403 curTn.displayed = true;
4404 // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
4405 item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
4406 ThumbnailAppearFinish(item);
4407 }
4408 }
4409 else {
4410 // undisplay thumbnail if not in viewport+margin --> performance gain
4411 curTn.displayed = false;
4412 item.$elt.css({ display: 'none'});
4413 }
4414 }
4415 item.left = curTn.left;
4416 item.top = newTop;
4417
4418 // set new size if changed
4419 if( item.width != curTn.width || item.height != curTn.height ) {
4420 item.$elt.css({ width: curTn.width , height: curTn.height });
4421 item.width = curTn.width;
4422 item.height = curTn.height;
4423
4424 // if( curTn.resizedContentWidth > 0 ) {
4425 // resize also the content (=image)
4426 if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
4427 if( item.kind == 'albumUp' ) {
4428 // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4429 }
4430 else {
4431 item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4432
4433 if( G.layout.engine == 'JUSTIFIED' ) {
4434 item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4435 }
4436 }
4437 item.resizedContentWidth = curTn.resizedContentWidth;
4438 item.resizedContentHeight = curTn.resizedContentHeight;
4439 }
4440 }
4441
4442
4443 // add counter of remaining (not displayed) images
4444 if( G.GOM.lastDisplayedIdxNew == GOMidx && G.layout.support.rows ) {
4445 if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4446 // number of items
4447 var nb = G.GOM.items.length - GOMidx - 1;
4448 if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
4449 nb--;
4450 }
4451
4452 if( nb > 0 ) {
4453 // display counter
4454 if( G.O.thumbnailOpenImage || G.O.thumbnailSliderDelay > 0 ) {
4455 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
4456 }
4457
4458 // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4459 // image slider on last displayed thumbnail
4460 if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4461
4462 // set current slider back to initial content
4463 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4464 // new slider
4465 G.GOM.slider.hostIdx = GOMidx;
4466 G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
4467 G.GOM.slider.nextIdx = GOMidx;
4468 G.GOM.slider.currentIdx = GOMidx;
4469 GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4470 // GalleryThumbnailSliderSetNextContent();
4471 }
4472 }
4473 else {
4474 // reset slider content to initial content because all thumbnails are displayed
4475 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4476 G.GOM.slider.hostIdx = -1;
4477 }
4478
4479 G.GOM.lastDisplayedIdx = GOMidx;
4480 }
4481 }
4482
4483 }
4484
4485 // ---------------------
4486 // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
4487 // function GalleryLastThumbnailSlideImage() {
4488 function GalleryThumbnailSliderBuildAndStart() {
4489
4490 if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
4491 return;
4492 }
4493 clearTimeout(G.GOM.slider.timerID);
4494
4495 var item = G.GOM.slider.hostItem;
4496
4497 // dupplicate image layer -> for the next image
4498 if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
4499 item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
4500 item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
4501 item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
4502 item.$getElt('.nGY2GThumbnailImg', true); // important -> refresh the cache
4503 }
4504
4505 item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4506 item.CSSTransformApply( '.nGY2TnImgNext' );
4507 item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4508 item.CSSTransformApply( '.nGY2TnImgBackNext' );
4509
4510 GalleryThumbnailSliderSetNextContent();
4511
4512 // clearTimeout(G.GOM.slider.timerID);
4513 // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4514 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4515 }
4516
4517
4518 function GalleryThumbnailSliderSetNextContent() {
4519
4520 G.GOM.slider.nextIdx++;
4521 if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
4522 G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
4523 }
4524
4525 // new image
4526 var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
4527 var imgBlurred = G.emptyGif;
4528 var bgImg = "url('" + G.emptyGif + "')";
4529 if( newItem.imageDominantColors != null ) {
4530 imgBlurred = newItem.imageDominantColors;
4531 bgImg = "url('" + newItem.imageDominantColors + "')";
4532 }
4533 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
4534 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
4535 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
4536
4537
4538 }
4539
4540 // thumbnail slider - transition from one image to the next one
4541 function GalleryThumbnailSliderStartTransition() {
4542
4543 if( G.GOM.slider.hostItem.$getElt() != null ) {
4544
4545 // slider transition
4546 var tweenable = new NGTweenable();
4547 G.GOM.slider.tween = tweenable;
4548 tweenable.tween({
4549 from: { 'left': 100 },
4550 to: { 'left': 0 },
4551 duration: 800,
4552 delay: 0,
4553 // easing: 'easeInOutQuad',
4554 easing: 'easeOutQuart',
4555
4556 step: function (state) {
4557 if( G.GOM.slider.hostItem.$getElt() == null ) {
4558 // the thumbnail may have been destroyed since the start of the animation
4559 G.GOM.slider.tween.stop(false);
4560 return;
4561 }
4562
4563 // window.ng_draf( function() {
4564 // slide current content
4565 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
4566 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4567 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
4568 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4569
4570 // slide new content
4571 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
4572 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4573 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
4574 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4575 // });
4576
4577
4578 },
4579 finish: function (state) {
4580 if( G.GOM.slider.hostItem.$getElt() == null ) {
4581 // the thumbnail may be destroyed since the start of the animation
4582 return;
4583 }
4584
4585 if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
4586
4587 // window.ng_draf( function() {
4588 // set new content as current content
4589 GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
4590 G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
4591 GalleryThumbnailSliderSetNextContent();
4592
4593 clearTimeout(G.GOM.slider.timerID);
4594 // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4595 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4596 // });
4597 }
4598 });
4599 }
4600 }
4601
4602 // set main content of the thumbnail hosting the slider
4603 // hide the elements for the next content of the slider
4604 function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
4605 if( G.GOM.slider.hostIdx == -1 ) { return; }
4606
4607 if( G.GOM.slider.tween != null ) {
4608 if( G.GOM.slider.tween._isTweening == true ) {
4609 G.GOM.slider.tween.stop(false);
4610 }
4611 }
4612
4613 var bgImg = "url('" + G.emptyGif + "')";
4614 if( ngy2itemContent.imageDominantColors != null ) {
4615 bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
4616 }
4617 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
4618 G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
4619 G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
4620
4621 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
4622 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4623 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
4624 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4625
4626 // place the containers for the next image slider outside of the thumbnail (=hidden)
4627 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4628 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4629 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4630 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4631
4632 // set new title and description
4633 if( G.O.thumbnailLabel.get('display') == true ) {
4634 var icons = G.O.icons.thumbnailAlbum;
4635 if( ngy2itemContent.kind != 'album' ) {
4636 icons = G.O.icons.thumbnailImage;
4637 }
4638 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
4639 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
4640 }
4641 }
4642
4643
4644
4645 // Compute the height of the label part of a thumbnail (title+description, both single line)
4646 function ThumbnailGetLabelHeight() {
4647 var newElt = [],
4648 newEltIdx = 0;
4649
4650 // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
4651 if( G.O.thumbnailLabel.get('display') == false ) {
4652 return 0;
4653 }
4654
4655 var desc='';
4656 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4657 desc = 'aAzZjJ';
4658 }
4659
4660 // visibility set to hidden
4661 newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
4662 if( G.O.thumbnailLabel.get('display') == true ) {
4663 // Labels: title and description
4664 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
4665 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
4666 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4667 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
4668 }
4669 newElt[newEltIdx++] = ' </div>';
4670 }
4671
4672 newElt[newEltIdx++] = '</div></div>';
4673
4674 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
4675 var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
4676 $newDiv.remove();
4677
4678 return h;
4679 }
4680
4681 function ThumbnailBuildStacks( bgColor ) {
4682 var ns=G.tn.opt.Get('stacks');
4683 if( ns == 0 ) { return ''; }
4684
4685 var s='';
4686 for( var i=0; i<ns; i++ ) {
4687 s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
4688 }
4689 return s;
4690 }
4691
4692 //----- Build one UP thumbnail (=navigation thumbnail)
4693 function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
4694 var newElt = [],
4695 newEltIdx = 0;
4696
4697 var mp = '';
4698 if( G.O.thumbnailOpenImage === false ) {
4699 mp = 'cursor:default;'
4700 }
4701
4702 newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
4703 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailSub">';
4704
4705 var h=G.tn.defaultSize.getHeight(),
4706 w=G.tn.defaultSize.getWidth();
4707
4708 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailImage" style="width:'+w+'px;height:'+h+'px;"><img class="nGY2GThumbnailImg" src="'+G.emptyGif+'" alt="" style="max-width:'+w+'px;max-height:'+h+'px;" ></div>';
4709 // newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
4710 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
4711 newElt[newEltIdx++] = ' </div>';
4712 newElt[newEltIdx++] = '</div>';
4713
4714 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing'); //.show('slow'); //.fadeIn('slow').slideDown('slow');
4715
4716 item.$elt = $newDiv;
4717 $newDiv.data('index', GOMidx);
4718 item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
4719
4720 return;
4721 }
4722
4723
4724 //----- Build one thumbnail
4725 function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
4726 item.eltTransform = [];
4727 item.eltFilter = [];
4728 item.hoverInitDone = false;
4729 item.$Elts = [];
4730
4731 if( item.kind == 'albumUp' ) {
4732 ThumbnailBuildAlbumpUp( item, idx, GOMidx);
4733 return;
4734 }
4735
4736 var newElt = [],
4737 newEltIdx = 0;
4738
4739 var mp = '';
4740 if( G.O.thumbnailOpenImage === false ) {
4741 mp = 'cursor:default;'
4742 }
4743
4744 // var src = encodeURI(item.thumbImg().src),
4745 var src = (item.thumbImg().src).replace(/'/g, "%27"), // replace single quote with %27
4746 sTitle = getThumbnailTitle(item);
4747
4748 // image background -> visible during image download
4749 var bg = '';
4750 var bgImg = "background-image: url('" + G.emptyGif + "');";
4751 if( item.imageDominantColors != null ) {
4752 // dominant colorS (blurred preview image)
4753 bgImg = "background-image: url('" + item.imageDominantColors + "');";
4754 }
4755 else {
4756 // dominant color -> background color
4757 if( item.imageDominantColor != null ) {
4758 bg = 'background-color:' + item.imageDominantColor + ';';
4759 }
4760 else {
4761 bgImg = '';
4762 }
4763 }
4764
4765 var op = 'opacity:1;';
4766 if( G.O.thumbnailWaitImageLoaded == true ) {
4767 op = 'opacity:0;';
4768 }
4769
4770 // ##### thumbnail containers (with stacks)
4771 newElt[newEltIdx++] = ThumbnailBuildStacks(bg) + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '"><div class="nGY2GThumbnailSub ' + ( G.O.thumbnailSelectable && item.selected ? "nGY2GThumbnailSubSelected" : "" ) + '">';
4772
4773
4774 // image size
4775 var w = G.tn.settings.getW();
4776 var h = G.tn.settings.getH();
4777 if( G.tn.settings.getMosaic() !== null ) {
4778 // mosaic layout ->
4779 w = G.GOM.items[GOMidx].width;
4780 h = G.GOM.items[GOMidx].height;
4781 }
4782
4783 var bgSize = 'contain';
4784 if( G.tn.opt.Get('crop') ) {
4785 bgSize = 'cover';
4786 }
4787
4788 // ##### layer for image background (color, dominant color, blurred preview)
4789 var s1 = "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px;"+ bg + bgImg + " background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
4790 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
4791
4792 // #### layer for image
4793 var s2 = op + "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px; background-image: url('" + src + "'); background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
4794 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
4795 newElt[newEltIdx++]=' <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
4796 newElt[newEltIdx++]='</div>';
4797
4798 // ##### layer for user customization purposes
4799 newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
4800
4801 // ##### layer for labels (title + description and their icons)
4802 if( G.O.thumbnailLabel.get('display') == true ) {
4803 // Labels: title and description
4804 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
4805 if( item.kind == 'album' ) {
4806 // album kind
4807 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
4808 }
4809 else {
4810 // image/media kind
4811 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
4812 }
4813 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
4814 newElt[newEltIdx++]= ' </div>';
4815 }
4816
4817 // ##### layer for tools
4818 newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
4819
4820 // close containers
4821 newElt[newEltIdx++]='</div></div>';
4822
4823 var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
4824
4825 item.$elt=$newDiv;
4826 $newDiv.data('index',GOMidx);
4827 item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
4828
4829 // Custom init function
4830 var fu=G.O.fnThumbnailInit;
4831 if( fu !== null ) {
4832 typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
4833 }
4834
4835 if( item.title != 'image gallery by nanogallery2 [build]' ) {
4836 ThumbnailOverInit(GOMidx);
4837 }
4838
4839 return ;
4840 }
4841
4842
4843 // Thumbnail layer for tools (toolbars and counter)
4844 function ThumbnailBuildTools( item, lastThumbnail ) {
4845
4846 // toolbars
4847 var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
4848
4849 // counter of not displayed images
4850 tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
4851
4852 return tb;
4853 }
4854
4855 function ThumbnailBuildToolbarOne( item, position ) {
4856 var toolbar = '';
4857 var tb = G.tn.toolbar.get(item);
4858 var width = { xs:0, sm:1, me:2, la:3, xl:4 };
4859 var cnt = 0;
4860
4861 if( tb[position] != '' ) {
4862 var pos='top: 0; right: 0; text-align: right;'; // 'topRight' and default
4863 switch( position ) {
4864 case 'topLeft':
4865 pos = 'top: 0; left: 0; text-align: left;';
4866 break;
4867 case 'bottomRight':
4868 pos = 'bottom: 0; right: 0; text-align: right;';
4869 break;
4870 case 'bottomLeft':
4871 pos = 'bottom: 0; left: 0; text-align: left;';
4872 break;
4873 }
4874
4875 toolbar += ' <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
4876
4877 var icons = tb[position].split(',');
4878 var nb = icons.length;
4879 for( var i = 0; i < nb; i++ ) {
4880 var icon = icons[i].replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
4881
4882 var minWidth = icon.substring(0,2).toLowerCase();
4883 var tIcon = icon;
4884 var display = true;
4885 if( /xs|sm|me|la|xl/i.test(minWidth) ) {
4886 // check visbility (depending on screen width)
4887 if( width[minWidth] > width[G.GOM.curWidth] ) {
4888 display = false;
4889 }
4890 tIcon = icon.substring(2);
4891 }
4892
4893 if( display ) {
4894 var sp=(i+1<nb ? '&nbsp;' :'');
4895 switch( tIcon ) {
4896 case 'COUNTER':
4897 if( item.kind == 'album' ) {
4898 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4899 toolbar += ' <div class="nGY2GThumbnailIconImageCounter"></div>';
4900 toolbar += ' <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
4901 toolbar += ' </li>';
4902 cnt++;
4903 }
4904 break;
4905 case 'COUNTER2':
4906 if( item.kind == 'album' ) {
4907 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4908 toolbar += ' <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
4909 toolbar += ' </li>';
4910 cnt++;
4911 }
4912 break;
4913 case 'SHARE':
4914 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4915 toolbar += ' <div>' + G.O.icons.thumbnailShare + '</div>';
4916 toolbar += ' </li>';
4917 cnt++;
4918 break;
4919 case 'DOWNLOAD':
4920 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4921 toolbar += ' <div>' + G.O.icons.thumbnailDownload + '</div>';
4922 toolbar += ' </li>';
4923 cnt++;
4924 break;
4925 case 'INFO':
4926 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4927 toolbar += ' <div>' + G.O.icons.thumbnailInfo + '</div>';
4928 toolbar += ' </li>';
4929 cnt++;
4930 break;
4931 case 'CART':
4932 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4933 toolbar += ' <div>' + G.O.icons.thumbnailCart + '</div>';
4934 toolbar += ' </li>';
4935 cnt++;
4936 break;
4937 case 'DISPLAY':
4938 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
4939 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
4940 toolbar += ' </li>';
4941 cnt++;
4942 break;
4943 case 'CUSTOM1':
4944 case 'CUSTOM2':
4945 case 'CUSTOM3':
4946 case 'CUSTOM4':
4947 case 'CUSTOM5':
4948 case 'CUSTOM6':
4949 case 'CUSTOM7':
4950 case 'CUSTOM8':
4951 case 'CUSTOM9':
4952 case 'CUSTOM10':
4953 var cust = tIcon.replace('CUSTOM', '');
4954 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
4955 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
4956 toolbar += ' </li>';
4957 cnt++;
4958 break;
4959 case 'FEATURED':
4960 if( item.featured === true ) {
4961 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4962 toolbar += ' <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
4963 toolbar += ' </li>';
4964 cnt++;
4965 }
4966 break;
4967 case 'SELECT':
4968 if( G.O.thumbnailSelectable == true ) {
4969 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
4970 if( item.selected === true ) {
4971 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
4972 }
4973 else {
4974 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
4975 }
4976 toolbar += ' </li>';
4977 cnt++;
4978 }
4979 break;
4980 }
4981 }
4982 }
4983 toolbar += ' </ul>';
4984 }
4985
4986 if( cnt > 0 ) {
4987 return toolbar;
4988 }
4989 else {
4990 return '';
4991 }
4992 }
4993
4994 function getThumbnailTitle( item ) {
4995
4996 var sTitle = item.title;
4997 if( G.O.thumbnailLabel.get('display') == true ) {
4998 if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
4999
5000 if( G.i18nTranslations.thumbnailImageTitle != '' ) {
5001 sTitle = G.i18nTranslations.thumbnailImageTitle;
5002 }
5003 var ml = G.O.thumbnailLabel.get('titleMaxLength');
5004 if( ml > 3 && sTitle.length > ml ){
5005 sTitle = sTitle.substring(0, ml) + '...';
5006 }
5007 }
5008
5009 return sTitle;
5010 }
5011
5012 function getTumbnailDescription( item ) {
5013 var sDesc = '';
5014 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5015 if( item.kind == 'album' ) {
5016 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5017 sDesc = G.i18nTranslations.thumbnailAlbumDescription;
5018 }
5019 else {
5020 sDesc = item.description;
5021 }
5022 }
5023 else {
5024 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5025 sDesc = G.i18nTranslations.thumbnailImageDescription;
5026 }
5027 else {
5028 sDesc = item.description;
5029 }
5030 }
5031 var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
5032 if( ml > 3 && sDesc.length > ml ){
5033 sDesc = sDesc.substring(0, ml) + '...';
5034 }
5035 if( sDesc.length == 0 ) {
5036 sDesc = '&nbsp;';
5037 }
5038 }
5039
5040 return sDesc;
5041 }
5042
5043
5044
5045 // Retrieve the maximum number of thumbnails that fits in one row
5046 function NbThumbnailsPerRow( areaWidth ) {
5047 var tnW = G.tn.defaultSize.getOuterWidth();
5048
5049 var nbMaxTn = 0;
5050 if( G.O.thumbnailAlignment == 'justified' ) {
5051 nbMaxTn = Math.floor((areaWidth)/(tnW));
5052 }
5053 else {
5054 nbMaxTn = Math.floor((areaWidth + G.tn.opt.Get('gutterWidth'))/(tnW + G.tn.opt.Get('gutterWidth')));
5055 }
5056
5057 if( G.O.maxItemsPerLine >0 && nbMaxTn > G.O.maxItemsPerLine ) {
5058 nbMaxTn = G.O.maxItemsPerLine;
5059 }
5060
5061 if( nbMaxTn < 1 ) { nbMaxTn = 1; }
5062
5063 return nbMaxTn
5064 }
5065
5066 // Thumbnail display animation
5067 function ThumbnailAppear( n, cnt ) {
5068 var curTn = G.GOM.items[n];
5069 var item = G.I[curTn.thumbnailIdx];
5070
5071
5072 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
5073 item.$elt.css({ opacity: 1 });
5074 ThumbnailAppearFinish( item );
5075 }
5076 else {
5077 if( item.$elt == null ) { return; }
5078 var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
5079 var vp = G.GOM.cache.viewport;
5080 if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
5081 // display animation only if in the current viewport
5082 var delay = cnt * G.tn.opt.Get('displayInterval');
5083 if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
5084 if( G.GOM.curNavLevel == 'lN' ) {
5085 G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
5086 }
5087 else {
5088 G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
5089 }
5090 }
5091 else {
5092 G.GOM.thumbnails2Display.push({itm: item, d: delay});
5093 // ThumbnailDisplayAnim2(item, delay);
5094 }
5095 return;
5096 }
5097 else {
5098 item.$elt.css({ opacity: 1 });
5099 ThumbnailAppearFinish(item);
5100 }
5101 }
5102 }
5103
5104
5105 // displays thumbnail stacks at the end of the display animation
5106 function ThumbnailAppearFinish( item ) {
5107
5108 // add stacks
5109 var ns = G.tn.opt.Get('stacks');
5110 if( ns > 0 ) {
5111 // display stacks
5112 item.$elt.css({ display: 'block'});
5113 var o = 0.9;
5114 // set stack opacity
5115 for( var i = ns-1; i>=0; i-- ) {
5116 item.$elt.eq(i).css('opacity', o);
5117 o = o - 0.2;
5118 }
5119
5120 }
5121 }
5122
5123
5124 function ThumbnailDisplayAnim2( item, delay ) {
5125 function randomIntFromInterval(min,max) {
5126 return Math.floor(Math.random()*(max-min+1)+min);
5127 }
5128 var oFrom = {};
5129 var oTo = {};
5130
5131 switch (G.tn.opt.Get('displayTransition')) {
5132 case 'RANDOMSCALE':
5133 var scales = [0.95, 1, 1.05, 1.1];
5134 var zi = [1, 2, 3, 4];
5135
5136 var r = randomIntFromInterval(0,3);
5137 while( r == G.GOM.lastRandomValue ) {
5138 r = randomIntFromInterval(0,3);
5139 }
5140 G.GOM.lastRandomValue = r;
5141 var f = scales[r];
5142 // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
5143 item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
5144
5145 oFrom = { scale: 0.5, opacity:0 };
5146 oTo = { scale: f, opacity:1 };
5147 break;
5148
5149 case 'SCALEUP':
5150 var f = G.tn.opt.Get('displayTransitionStartVal');
5151 if( f == 0 ) { f = 0.6; } // default value
5152 oFrom = { scale: f, opacity: 0 };
5153 oTo = { scale: 1, opacity: 1 };
5154 break;
5155
5156 case 'SCALEDOWN':
5157 var f = G.tn.opt.Get('displayTransitionStartVal');
5158 if( f == 0 ) { f=1.3; } // default value
5159 oFrom = { scale: f, opacity: 0 };
5160 oTo = { scale: 1, opacity: 1 };
5161 break;
5162 case 'SLIDEUP':
5163 var f = G.tn.opt.Get('displayTransitionStartVal');
5164 if( f == 0 ) { f=50; } // default value
5165 oFrom = { opacity: 0, translateY: f };
5166 oTo = { opacity: 1, translateY: 0 };
5167 break;
5168 case 'SLIDEDOWN':
5169 var f=G.tn.opt.Get('displayTransitionStartVal');
5170 if( f == 0 ) { f=-50; } // default value
5171 oFrom = { opacity: 0, translateY: f };
5172 oTo = { opacity: 1, translateY: 0 };
5173 break;
5174 case 'FLIPUP':
5175 var f=G.tn.opt.Get('displayTransitionStartVal');
5176 if( f == 0 ) { f=100; } // default value
5177 oFrom = { opacity: 0, translateY: f, rotateX: 45 };
5178 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5179 break;
5180
5181 case 'FLIPDOWN':
5182 var f=G.tn.opt.Get('displayTransitionStartVal');
5183 if( f == 0 ) { f=-100; } // default value
5184 oFrom = { opacity: 0, translateY: f, rotateX: -45 };
5185 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5186 break;
5187 case 'SLIDEUP2':
5188 var f=G.tn.opt.Get('displayTransitionStartVal');
5189 if( f == 0 ) { f=100; } // default value
5190 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5191 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5192 break;
5193 case 'SLIDEDOWN2':
5194 var f=G.tn.opt.Get('displayTransitionStartVal');
5195 if( f == 0 ) { f=-100; } // default value
5196 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5197 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5198 break;
5199 case 'SLIDERIGHT':
5200 var f=G.tn.opt.Get('displayTransitionStartVal');
5201 if( f == 0 ) { f=-150; } // default value
5202 oFrom = { opacity: 0, translateX: f };
5203 oTo = { opacity: 1, translateX: 0 };
5204 break;
5205
5206 case 'SLIDELEFT':
5207 var f=G.tn.opt.Get('displayTransitionStartVal');
5208 if( f == 0 ) { f=150; } // default value
5209 oFrom = { opacity: 0, translateX: f };
5210 oTo = { opacity: 1, translateX: 0 };
5211 break;
5212
5213 case 'FADEIN':
5214 oFrom = { opacity: 0 };
5215 oTo = { opacity: 1 };
5216 break;
5217
5218
5219 }
5220
5221 var tweenable = new NGTweenable();
5222 tweenable.tween({
5223 from: oFrom,
5224 to: oTo,
5225 attachment: { $e:item.$elt, item: item, tw: tweenable },
5226 delay: delay,
5227 duration: G.tn.opt.Get('displayTransitionDuration'),
5228 easing: G.tn.opt.Get('displayTransitionEasing'),
5229 step: function (state, att) {
5230 window.requestAnimationFrame( function() {
5231 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5232 att.tw.stop(false);
5233 return;
5234 }
5235 switch (G.tn.opt.Get('displayTransition')) {
5236 case 'RANDOMSCALE':
5237 att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
5238 break;
5239 case 'SCALEUP':
5240 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5241 break;
5242 case 'SCALEDOWN':
5243 att.item.$elt.last().css('opacity', state.opacity);
5244 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5245 att.item.CSSTransformApply('.nGY2GThumbnail');
5246 break;
5247 case 'SLIDEUP':
5248 att.item.$elt.css('opacity', state.opacity);
5249 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
5250 att.item.CSSTransformApply('.nGY2GThumbnail');
5251 break;
5252 case 'SLIDEDOWN':
5253 att.item.$elt.css('opacity', state.opacity);
5254 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5255 att.item.CSSTransformApply('.nGY2GThumbnail');
5256 break;
5257 case 'FLIPUP':
5258 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5259 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
5260 att.item.$elt.css('opacity', state.opacity);
5261 att.item.CSSTransformApply('.nGY2GThumbnail');
5262 break;
5263 case 'FLIPDOWN':
5264 att.item.$elt.css('opacity', state.opacity);
5265 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5266 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
5267 att.item.CSSTransformApply('.nGY2GThumbnail');
5268 break;
5269 case 'SLIDEUP2':
5270 att.item.$elt.css('opacity', state.opacity);
5271 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5272 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5273 att.item.CSSTransformApply('.nGY2GThumbnail');
5274 break;
5275 case 'SLIDEDOWN2':
5276 att.item.$elt.css('opacity', state.opacity);
5277 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
5278 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5279 att.item.CSSTransformApply('.nGY2GThumbnail');
5280 break;
5281 case 'SLIDERIGHT':
5282 att.item.$elt.css('opacity', state.opacity);
5283 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5284 att.item.CSSTransformApply('.nGY2GThumbnail');
5285 break;
5286 case 'SLIDELEFT':
5287 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5288 att.item.$elt.css('opacity', state.opacity);
5289 att.item.CSSTransformApply('.nGY2GThumbnail');
5290 break;
5291 case 'FADEIN':
5292 att.$e.css(state);
5293 break;
5294 }
5295 });
5296 // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5297 },
5298 finish: function (state, att) {
5299 window.requestAnimationFrame( function() {
5300 if( att.item.$elt === null ) { return; }
5301
5302 switch (G.tn.opt.Get('displayTransition')) {
5303 case 'RANDOMSCALE':
5304 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
5305 break;
5306 case 'SCALEUP':
5307 att.$e.css( G.CSStransformName , '').css('opacity', '');
5308 break;
5309 case 'SCALEDOWN':
5310 att.item.$elt.last().css('opacity', '');
5311 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5312 att.item.CSSTransformApply('.nGY2GThumbnail');
5313 break;
5314 case 'SLIDEUP':
5315 att.item.$elt.css('opacity', '');
5316 break;
5317 case 'SLIDEDOWN':
5318 att.item.$elt.css('opacity', '');
5319 break;
5320 case 'FLIPUP':
5321 att.item.$elt.css('opacity', '');
5322 break;
5323 case 'FLIPDOWN':
5324 att.item.$elt.css('opacity', '');
5325 break;
5326 case 'SLIDEUP2':
5327 att.item.$elt.css('opacity', '');
5328 break;
5329 case 'SLIDEDOWN2':
5330 att.item.$elt.css('opacity', '');
5331 att.item.CSSTransformApply('.nGY2GThumbnail');
5332 break;
5333 case 'SLIDERIGHT':
5334 att.item.$elt.css('opacity', '');
5335 break;
5336 case 'SLIDELEFT':
5337 att.item.$elt.css('opacity', '');
5338 break;
5339 case 'FADEIN':
5340 att.$e.css('opacity', '');
5341 break;
5342 }
5343 ThumbnailAppearFinish(att.item);
5344 });
5345
5346 }
5347 });
5348
5349 }
5350
5351 // batch display thumbnails with animation
5352 function ThumbnailDisplayAnimBatch() {
5353
5354 G.GOM.thumbnails2Display.forEach( function(one) {
5355 ThumbnailDisplayAnim2(one.itm, one.d);
5356 });
5357 G.GOM.thumbnails2Display=[];
5358 }
5359
5360
5361
5362 // ######################################
5363 // Gallery display animation
5364 function GalleryAppear() {
5365
5366 var d=G.galleryDisplayTransitionDuration.Get();
5367 switch( G.galleryDisplayTransition.Get() ){
5368 case 'ROTATEX':
5369 G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
5370 var tweenable = new NGTweenable();
5371 tweenable.tween({
5372 from: { r: 50 },
5373 to: { r: 0 },
5374 attachment: { orgIdx: G.GOM.albumIdx },
5375 duration: d,
5376 easing: 'easeOutCirc',
5377 step: function (state, att) {
5378 if( att.orgIdx == G.GOM.albumIdx ) {
5379 // window.ng_draf( function() {
5380 G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
5381 // });
5382 }
5383 }
5384 });
5385 break;
5386 case 'SLIDEUP':
5387 G.$E.conTnParent.css({ opacity: 0 });
5388 var tweenable = new NGTweenable();
5389 tweenable.tween({
5390 from: { y: 200, o: 0 },
5391 to: { y: 0, o: 1 },
5392 attachment: { orgIdx: G.GOM.albumIdx },
5393 duration: d,
5394 easing: 'easeOutCirc',
5395 step: function (state, att) {
5396 if( att.orgIdx == G.GOM.albumIdx ) {
5397 // window.ng_draf( function() {
5398 G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
5399 // });
5400 }
5401 }
5402 });
5403 break;
5404 case 'NONE':
5405 default:
5406 break;
5407 }
5408
5409
5410 }
5411
5412 // ######################################
5413 // ##### THUMBNAIL HOVER MANAGEMENT #####
5414 // ######################################
5415
5416 function ThumbnailOverInit( GOMidx ) {
5417 // Over init in 2 step:
5418 // 1) init with thumbnailBuildInit2 parameter
5419 // 2) init with the hover effect parameter
5420
5421
5422 var curTn = G.GOM.items[GOMidx];
5423 var item = G.I[curTn.thumbnailIdx];
5424
5425 if( item.$elt == null ) { return; } // zombie
5426
5427 var fu = G.O.fnThumbnailHoverInit;
5428 if( fu !== null ) {
5429 typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
5430 }
5431
5432 // build initialization
5433 var inits = G.tn.buildInit.get();
5434 for( var j = 0; j < inits.length; j++) {
5435 switch( inits[j].property ) {
5436 // CSS Transform
5437 case 'scale':
5438 case 'rotateX':
5439 case 'rotateY':
5440 case 'rotateZ':
5441 case 'translateX':
5442 case 'translateY':
5443 case 'translateZ':
5444 item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
5445 item.CSSTransformApply(inits[j].element);
5446 break;
5447 // CSS filter
5448 case 'blur':
5449 case 'brightness':
5450 case 'grayscale':
5451 case 'sepia':
5452 case 'contrast':
5453 case 'opacity':
5454 case 'saturate':
5455 item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
5456 item.CSSFilterApply(inits[j].element);
5457 break;
5458 default:
5459 var $t=item.$getElt(inits[j].element);
5460 $t.css( inits[j].property, inits[j].value );
5461 break;
5462 }
5463 }
5464
5465 // hover
5466 var effects = G.tn.hoverEffects.get();
5467 for( var j = 0; j < effects.length; j++) {
5468 if( effects[j].firstKeyframe === true ) {
5469 switch( effects[j].type ) {
5470 case 'scale':
5471 case 'rotateX':
5472 case 'rotateY':
5473 case 'rotateZ':
5474 case 'translateX':
5475 case 'translateY':
5476 case 'translateZ':
5477 item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
5478 item.CSSTransformApply(effects[j].element);
5479 break;
5480 case 'blur':
5481 case 'brightness':
5482 case 'grayscale':
5483 case 'sepia':
5484 case 'contrast':
5485 case 'opacity':
5486 case 'saturate':
5487 item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
5488 item.CSSFilterApply(effects[j].element);
5489 break;
5490 default:
5491 var $t = item.$getElt(effects[j].element);
5492 $t.css( effects[j].type, effects[j].from );
5493 break;
5494
5495 }
5496 }
5497 }
5498 item.hoverInitDone=true;
5499 }
5500
5501 function ThumbnailHoverReInitAll() {
5502 if( G.GOM.albumIdx == -1 ) { return; };
5503 var l = G.GOM.items.length;
5504 for( var i = 0; i < l ; i++ ) {
5505 ThumbnailOverInit(i);
5506 // G.GOM.items[i].hovered=false;
5507 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5508 }
5509 }
5510
5511
5512 function ThumbnailHover( GOMidx ) {
5513 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
5514 if( G.GOM.slider.hostIdx == GOMidx ) {
5515 // slider hosted on thumbnail -> no hover effect
5516 return;
5517 }
5518 var curTn = G.GOM.items[GOMidx];
5519 var item = G.I[curTn.thumbnailIdx];
5520 if( item.kind == 'albumUp' || item.$elt == null ) { return; }
5521
5522 item.hovered = true;
5523
5524 var fu = G.O.fnThumbnailHover;
5525 if( fu !== null ) {
5526 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5527 }
5528 var effects = G.tn.hoverEffects.get();
5529
5530 try {
5531 for( var j = 0; j < effects.length; j++) {
5532 if( effects[j].hoverin === true ) {
5533 //item.animate( effects[j], j*10, true );
5534 item.animate( effects[j], 0, true );
5535 }
5536 }
5537 // effects on whole layout
5538 // GalleryResize( GOMidx );
5539 }
5540 catch (e) {
5541 NanoAlert(G, 'error on hover: ' + e.message );
5542 }
5543
5544 }
5545
5546 function ThumbnailHoverOutAll() {
5547 if( G.GOM.albumIdx == -1 ) { return; };
5548 var l = G.GOM.items.length;
5549 for( var i = 0; i < l ; i++ ) {
5550 if( G.GOM.items[i].inDisplayArea ) {
5551 ThumbnailHoverOut(i);
5552 }
5553 else {
5554 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5555 }
5556 }
5557 }
5558
5559
5560 function ThumbnailHoverOut( GOMidx ) {
5561 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
5562
5563 if( G.GOM.slider.hostIdx == GOMidx ) {
5564 // slider on thumbnail -> no hover effect
5565 return;
5566 }
5567
5568 var curTn = G.GOM.items[GOMidx];
5569 var item = G.I[curTn.thumbnailIdx];
5570 if( item.kind == 'albumUp' || !item.hovered ) { return; }
5571 item.hovered = false;
5572 if( item.$elt == null ) { return; }
5573
5574 var fu = G.O.fnThumbnailHoverOut;
5575 if( fu !== null ) {
5576 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5577 }
5578
5579 var effects = G.tn.hoverEffects.get();
5580 try {
5581 for( var j = 0; j < effects.length; j++) {
5582 if( effects[j].hoverout === true ) {
5583 // item.animate( effects[j], j*10, false );
5584 item.animate( effects[j], 0, false );
5585 }
5586 }
5587 // effects on whole layout
5588 // GalleryResize( );
5589 }
5590 catch (e) {
5591 NanoAlert(G, 'error on hoverOut: ' + e.message );
5592 }
5593
5594 }
5595
5596
5597 /** @function DisplayPhoto */
5598 function DisplayPhoto( imageID, albumID ) {
5599
5600 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ albumID +'-'+ imageID); }
5601 var albumIdx = NGY2Item.GetIdx(G, albumID);
5602 if( albumIdx == 0 ) {
5603 G.GOM.curNavLevel = 'l1';
5604 }
5605 else {
5606 G.GOM.curNavLevel = 'lN';
5607 }
5608
5609 if( albumIdx == -1 ) {
5610 // get content of album on root level
5611 if( G.O.kind != '' ) {
5612 // do not add album if Markup or Javascript data
5613 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
5614 albumIdx = G.I.length - 1;
5615 }
5616 }
5617
5618 var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
5619 if( ngy2ItemIdx == -1 ) {
5620 // get content of the album
5621 AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
5622 return;
5623 }
5624
5625 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ ngy2ItemIdx); }
5626
5627 DisplayPhotoIdx(ngy2ItemIdx);
5628
5629 }
5630
5631
5632 // BETA -> NOT finished and not used at this time
5633 // Retrieve the title+description of ONE album
5634 function albumGetInfo( albumIdx, fnToCall ) {
5635 var url = '';
5636 var kind = 'image';
5637
5638 switch( G.O.kind ) {
5639 case 'json':
5640 // TODO
5641 case 'flickr':
5642 // TODO
5643 case 'picasa':
5644 case 'google':
5645 case 'google2':
5646 default:
5647 url = G.Google.url() + 'user/'+G.O.userID+'/albumid/'+G.I[albumIdx].GetID()+'?alt=json&&max-results=1&fields=title';
5648 break;
5649 }
5650
5651 jQuery.ajaxSetup({ cache: false });
5652 jQuery.support.cors = true;
5653
5654 var tId = setTimeout( function() {
5655 // workaround to handle JSONP (cross-domain) errors
5656 //PreloaderHide();
5657 NanoAlert(G, 'Could not retrieve AJAX data...');
5658 }, 60000 );
5659 jQuery.getJSON(url, function(data, status, xhr) {
5660 clearTimeout(tId);
5661 //PreloaderHide();
5662
5663 fnToCall( G.I[albumIdx].GetID() );
5664
5665 })
5666 .fail( function(jqxhr, textStatus, error) {
5667 clearTimeout(tId);
5668 //PreloaderHide();
5669 var err = textStatus + ', ' + error;
5670 NanoAlert('Could not retrieve ajax data: ' + err);
5671 });
5672
5673 }
5674
5675
5676 // function AlbumGetContent( albumIdx, fnToCall ) {
5677 function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
5678 // var url='';
5679 // var kind='image';
5680 // var albumIdx=NGY2Item.GetIdx(G, albumID);
5681 // var photoIdx=NGY2Item.GetIdx(G, photoID);
5682
5683 switch( G.O.kind ) {
5684 // MARKUP / API
5685 case '':
5686 AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
5687 break;
5688 // JSON, Flickr, Picasa, ...
5689 default:
5690 jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
5691 }
5692
5693 }
5694
5695 var mediaList = {
5696 youtube : {
5697 getID: function( url ) {
5698 // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
5699 var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
5700 return s != null ? s[1] : null;
5701 },
5702 thumbUrl: function( id ) {
5703 return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
5704 },
5705 url: function( id ) {
5706 return 'https://www.youtube.com/embed/' + id;
5707 },
5708 markup: function( id ) {
5709 return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5710 },
5711 kind: 'iframe'
5712 },
5713 vimeo : {
5714 getID: function( url ) {
5715 // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
5716 var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
5717 return s != null ? s[5] : null;
5718 },
5719 url: function( id ) {
5720 return 'https://player.vimeo.com/video/' + id;
5721 },
5722 markup: function( id ) {
5723 return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5724 },
5725 kind: 'iframe'
5726 },
5727 dailymotion : {
5728 getID: function( url ) {
5729 // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
5730 var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
5731 if (m !== null) {
5732 if(m[4] !== undefined) {
5733 return m[4];
5734 }
5735 return m[2];
5736 }
5737 return null;
5738 },
5739 thumbUrl: function( id ) {
5740 return 'https://www.dailymotion.com/thumbnail/video/' + id;
5741 },
5742 url: function( id ) {
5743 return 'https://www.dailymotion.com/embed/video/' + id;
5744 },
5745 markup: function( id ) {
5746 return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5747 },
5748 kind: 'iframe'
5749 }
5750 };
5751
5752 function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
5753
5754 if( G.markupOrApiProcessed === true ) {
5755 // already processed (maybe location hash to unknow reference) -> display root album
5756 DisplayAlbum('-1', 0);
5757 return;
5758 }
5759
5760 if( G.O.items !== undefined && G.O.items !== null ) {
5761 // data defined as an object in an option parameter
5762 GetContentApiObject();
5763 }
5764 else {
5765 if( G.O.$markup.length > 0 ) {
5766 // data defined as markup (href elements)
5767 GetContentMarkup( G.O.$markup );
5768 G.O.$markup=[] ;
5769 }
5770 else {
5771 NanoConsoleLog(G, 'error: no media to process.');
5772 return;
5773 }
5774 }
5775
5776 G.markupOrApiProcessed = true;
5777 if( fnToCall !== null && fnToCall !== undefined) {
5778 fnToCall( fnParam1, fnParam2, null );
5779 }
5780 }
5781
5782 function StartsWithProtocol ( path ) {
5783 if( path == null || path == undefined ) { return false; }
5784
5785 var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
5786 if( !pattern.test(path) ) {
5787 // not a full URL
5788 return false;
5789 }
5790 return true;
5791 }
5792
5793 function GetContentApiObject() {
5794 var foundAlbumID=false;
5795 var nbTitles = 0;
5796 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
5797
5798 G.I[0].contentIsLoaded=true;
5799
5800 jQuery.each(G.O.items, function(i,item){
5801
5802 var title = '';
5803 title=GetI18nItem(item, 'title');
5804 if( title === undefined ) { title=''; }
5805
5806 var src='';
5807 if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
5808 src = item['src'+RetrieveCurWidth().toUpperCase()];
5809 }
5810 else {
5811 src = item.src;
5812 }
5813 if( !StartsWithProtocol(src) ) {
5814 src = G.O.itemsBaseURL + src;
5815 }
5816
5817 var thumbsrc = '';
5818 if( item.srct !== undefined && item.srct.length > 0 ) {
5819 thumbsrc = item.srct;
5820 if( !StartsWithProtocol(thumbsrc) ) {
5821 thumbsrc = G.O.itemsBaseURL + thumbsrc;
5822 }
5823 }
5824 else {
5825 thumbsrc = src;
5826 }
5827
5828 var thumbsrcX2 = '';
5829 if( item.srct2x !== undefined && item.srct2x.length > 0 ) {
5830 thumbsrcX2 = item.srct2x;
5831 if( !StartsWithProtocol(thumbsrcX2) ) {
5832 thumbsrcX2 = G.O.itemsBaseURL + thumbsrcX2;
5833 }
5834 }
5835 else {
5836 if( thumbsrc != '' ) {
5837 thumbsrcX2 = thumbsrc;
5838 }
5839 else {
5840 thumbsrcX2 = src;
5841 }
5842 }
5843
5844 if( G.O.thumbnailLabel.get('title') != '' ) {
5845 title = GetImageTitle(src);
5846 }
5847
5848 var description=''; //'&nbsp;';
5849 description=GetI18nItem(item,'description');
5850 if( description === undefined ) { description=''; }
5851 //if( toType(item.description) == 'string' ) {
5852 // description=item.description;
5853 //}
5854
5855 var tags = GetI18nItem(item, 'tags');
5856 if( tags === undefined ) { tags=''; }
5857
5858 var albumID = 0;
5859 if( item.albumID !== undefined ) {
5860 albumID=item.albumID;
5861 foundAlbumID = true;
5862 }
5863 var ID = null;
5864 if( item.ID !== undefined ) {
5865 ID = item.ID;
5866 }
5867 var kind = 'image';
5868 if( item.kind !== undefined && item.kind.length > 0 ) {
5869 kind = item.kind;
5870 }
5871
5872 var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
5873 if( title != '' ) {
5874 nbTitles++;
5875 }
5876
5877 // media source url - img is the default media kind
5878 newItem.setMediaURL( src, 'img');
5879
5880 // manage media kinds other than IMG
5881 jQuery.each(mediaList, function ( n, media ) {
5882 var id = media.getID(src);
5883 if( id != null ) {
5884 src = media.url(id);
5885 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
5886 newItem.mediaKind = media.kind;
5887 newItem.mediaMarkup = media.markup(id);
5888 return false;
5889 }
5890 });
5891
5892 // image size
5893 if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
5894 if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
5895
5896 // THUMBNAILS
5897
5898 // thumbnail image size
5899 var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
5900 var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
5901
5902 // default thumbnail URL and size
5903 newItem.thumbs = {
5904 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
5905 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
5906 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
5907 };
5908
5909 // default media type -> IMG
5910 if( newItem.mediaKind == 'img' ) {
5911
5912 // responsive thumbnails URL and size
5913 var lst=['xs', 'sm', 'me', 'la', 'xl'];
5914 for( var i=0; i< lst.length; i++ ) {
5915 // url
5916 var turl = item['srct' + lst[i].toUpperCase()];
5917 if( turl !== undefined ) {
5918 if( !StartsWithProtocol(turl) ) {
5919 turl = G.O.itemsBaseURL + turl;
5920 }
5921 newItem.url.l1[lst[i]] = turl;
5922 newItem.url.lN[lst[i]] = turl;
5923 }
5924 // width
5925 var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
5926 if( tw != undefined ) {
5927 newItem.width.l1[lst[i]] = parseInt(tw);
5928 newItem.width.lN[lst[i]] = parseInt(tw);
5929 }
5930 // height
5931 var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
5932 if( th != undefined ) {
5933 newItem.height.l1[lst[i]] = parseInt(th);
5934 newItem.height.lN[lst[i]] = parseInt(th);
5935 }
5936 }
5937 }
5938
5939 // dominant colors (needs to be a base64 gif)
5940 if( item.imageDominantColors !== undefined ) {
5941 newItem.imageDominantColors = item.imageDominantColors;
5942 }
5943 // dominant color (rgb hex)
5944 if( item.imageDominantColor !== undefined ) {
5945 newItem.imageDominantColor = item.imageDominantColor;
5946 }
5947
5948 // dest url
5949 if( item.destURL !== undefined && item.destURL.length>0 ) {
5950 newItem.destinationURL = item.destURL;
5951 }
5952
5953 // download image url
5954 if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
5955 newItem.downloadURL = item.downloadURL;
5956 }
5957
5958 // EXIF DATA
5959 // Exif - model
5960 if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
5961 // Exif - flash
5962 if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
5963 // Exif - focallength
5964 if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
5965 // Exif - fstop
5966 if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
5967 // Exif - exposure
5968 if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
5969 // Exif - time
5970 if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
5971 // Exif - iso
5972 if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
5973 // Exif - location
5974 if( item.exifLocation !== undefined ) { newItem.exif.location = item.exifLocation; }
5975
5976
5977 // custom data
5978 if( item.customData !== null ) {
5979 newItem.customData = cloneJSObject( item.customData );
5980 }
5981
5982 newItem.contentIsLoaded = true;
5983
5984 var fu = G.O.fnProcessData;
5985 if( fu !== null ) {
5986 typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
5987 }
5988
5989 AlbumPostProcess(albumID);
5990 });
5991
5992 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
5993 if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
5994
5995 }
5996
5997
5998 function GetContentMarkup( $elements ) {
5999 var foundAlbumID = false;
6000 var nbTitles = 0;
6001 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6002
6003 G.I[0].contentIsLoaded = true;
6004
6005 jQuery.each($elements, function(i, item){
6006
6007 // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
6008 var data = {
6009 // some default values
6010 'data-ngdesc': '', // item description
6011 'data-ngid': null, // ID
6012 'data-ngkind': 'image', // kind (image, album, albumup)
6013 'data-ngtags': null, // tags
6014 'data-ngdest': '', // destination URL
6015 'data-ngthumbimgwidth': 0, // thumbnail width
6016 'data-ngthumbimgheight': 0, // thumbnail height
6017 'data-ngimagewidth': 0, // image width
6018 'data-ngimageheight': 0, // image height
6019 'data-ngimagedominantcolors': null, // image dominant colors
6020 'data-ngimagedominantcolor': null, // image dominant colors
6021 'data-ngexifmodel': '', // EXIF data
6022 'data-ngexifflash': '',
6023 'data-ngexiffocallength': '',
6024 'data-ngexiffstop': '',
6025 'data-ngexifexposure': '',
6026 'data-ngexifiso': '',
6027 'data-ngexiftime': '',
6028 'data-ngexiflocation': ''
6029 };
6030 [].forEach.call( item.attributes, function(attr) {
6031 data[attr.name.toLowerCase()] = attr.value;
6032 });
6033
6034 // responsive image source
6035 var src = '',
6036 st = RetrieveCurWidth().toUpperCase();
6037 if( data.hasOwnProperty('data-ngsrc'+st) ) {
6038 src = data['data-ngsrc'+st];
6039 }
6040 if( src == '' ) {
6041 src = data['href'];
6042 }
6043 if( !StartsWithProtocol(src) ) {
6044 src = G.O.itemsBaseURL + src;
6045 }
6046
6047 // thumbnail
6048 var thumbsrc = '';
6049 if( data.hasOwnProperty('data-ngthumb') ) {
6050 thumbsrc = data['data-ngthumb'];
6051 if( !StartsWithProtocol(thumbsrc) ) {
6052 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6053 }
6054 }
6055 else {
6056 thumbsrc = src;
6057 }
6058 var thumbsrcX2 = '';
6059 if( data.hasOwnProperty('data-ngthumb2x') ) {
6060 thumbsrcX2 = data['data-ngthumb2x'];
6061 if( !StartsWithProtocol(thumbsrcX2) ) {
6062 thumbsrcX2 = G.O.itemsBaseURL + thumbsrcX2;
6063 }
6064 }
6065
6066 //newObj.description=jQuery(item).attr('data-ngdesc');
6067 var description = data['data-ngdesc'];
6068 var ID = data['id'];
6069 if( ID == undefined ) {
6070 ID = data['data-ngid'];
6071 }
6072 var kind = data['data-ngkind'];
6073 var tags = data['data-ngtags'];
6074
6075 var albumID = '0';
6076 if( data.hasOwnProperty('data-ngalbumid') ) {
6077 albumID = data['data-ngalbumid'];
6078 foundAlbumID = true;
6079 }
6080
6081 var title = jQuery(item).text();
6082 if( !(G.O.thumbnailLabel.get('title') == '' || G.O.thumbnailLabel.get('title') == undefined) ) {
6083 title = GetImageTitle(src);
6084 }
6085
6086
6087 var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6088 if( title != '' ) {
6089 nbTitles++;
6090 }
6091
6092 // media source url - img is the default media kind
6093 newItem.setMediaURL( src, 'img');
6094
6095 // manage media kinds other than IMG
6096 newItem.mediaKind = 'img';
6097 jQuery.each(mediaList, function ( n, media ) {
6098 var id = media.getID(src);
6099 if( id != null ) {
6100 src = media.url(id);
6101 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
6102 newItem.mediaKind = media.kind;
6103 newItem.mediaMarkup = media.markup(id);
6104 return false;
6105 }
6106 });
6107
6108
6109 // image size
6110 newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
6111 newItem.imageHeight = parseInt( data['data-ngimageheight'] );
6112
6113 // default thumbnail image URL and size
6114 var tw = parseInt(data['data-ngthumbimgwidth']);
6115 var th = parseInt(data['data-ngthumbimgheight']);
6116 newItem.thumbs = {
6117 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6118 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6119 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6120 };
6121
6122 // default media type -> IMG
6123 if( newItem.mediaKind == 'img' ) {
6124
6125 // responsive thumbnails URL and size
6126 var lst = ['xs', 'sm', 'me', 'la', 'xl'];
6127 for( var i = 0; i < lst.length; i++ ) {
6128 // url
6129 if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
6130 var turl=data['data-ngthumb' + lst[i]];
6131 if( !StartsWithProtocol(turl) ) {
6132 turl = G.O.itemsBaseURL + turl;
6133 }
6134 newItem.url.l1[lst[i]] = turl;
6135 newItem.url.lN[lst[i]] = turl;
6136 }
6137
6138 // width
6139 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
6140 var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
6141 newItem.width.l1[lst[i]] = tw;
6142 newItem.width.lN[lst[i]] = tw;
6143 }
6144 // height
6145 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
6146 var th=parseInt('data-ngthumb' + lst[i] + 'height');
6147 newItem.height.l1[lst[i]] = th;
6148 newItem.height.lN[lst[i]] = th;
6149 }
6150 }
6151 }
6152
6153
6154 // dominant colorS (needs to be a base64 gif)
6155 newItem.imageDominantColors = data['data-ngimagedominantcolors'];
6156 // dominant color (rgb hex)
6157 newItem.imageDominantColor = data['data-ngimagedominantcolors'];
6158
6159 newItem.destinationURL = data['data-ngdest'];
6160 newItem.downloadURL = data['data-ngdownloadurl'];
6161
6162 // Exif - model
6163 newItem.exif.model=data['data-ngexifmodel'];
6164 // Exif - flash
6165 newItem.exif.flash=data['data-ngexifflash'];
6166 // Exif - focallength
6167 newItem.exif.focallength=data['data-ngexiffocallength'];
6168 // Exif - fstop
6169 newItem.exif.fstop=data['data-ngexiffstop'];
6170 // Exif - exposure
6171 newItem.exif.exposure=data['data-ngexifexposure'];
6172 // Exif - iso
6173 newItem.exif.iso=data['data-ngexifiso'];
6174 // Exif - time
6175 newItem.exif.time=data['data-ngexiftime'];
6176 // Exif - location
6177 newItem.exif.location=data['data-ngexiflocation'];
6178
6179 newItem.contentIsLoaded=true;
6180
6181 // custom data
6182 if( jQuery(item).data('customdata') !== undefined ) {
6183 newItem.customData=cloneJSObject(jQuery(item).data('customdata'));
6184 }
6185 // custom data
6186 if( jQuery(item).data('ngcustomdata') !== undefined ) {
6187 newItem.customData=cloneJSObject(jQuery(item).data('ngcustomdata'));
6188 }
6189
6190 var fu=G.O.fnProcessData;
6191 if( fu !== null ) {
6192 typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
6193 }
6194
6195 AlbumPostProcess(albumID);
6196
6197 });
6198
6199 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6200 if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
6201
6202 }
6203
6204
6205 // ################################
6206 // ##### DEFINE VARIABLES #####
6207 // ################################
6208
6209
6210 /** @function DefineVariables */
6211 function DefineVariables() {
6212
6213 // change 'picasa' to 'google' for compatibility reason
6214 if( G.O.kind.toUpperCase() == 'PICASA' || G.O.kind.toUpperCase() == 'GOOGLE') {
6215 G.O.kind='google2';
6216 }
6217
6218 // management of screen width
6219 G.GOM.cache.viewport = getViewport();
6220 G.GOM.curWidth = RetrieveCurWidth();
6221
6222 // tumbnail toolbar
6223 jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
6224 jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
6225 var t = ['image', 'album'];
6226 var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
6227 for( var i=0; i < t.length ; i++ ) {
6228 for( var j=0; j < pos.length ; j++ ) {
6229 G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
6230 }
6231 }
6232
6233 // thumbnails label - level dependant settings
6234 G.O.thumbnailLabel.get = function( opt ) {
6235 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6236 return G.O.thumbnailL1Label[opt];
6237 }
6238 else {
6239 return G.O.thumbnailLabel[opt];
6240 }
6241 };
6242 G.O.thumbnailLabel.set = function( opt, value ) {
6243 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6244 G.O.thumbnailL1Label[opt]=value;
6245 }
6246 else {
6247 G.O.thumbnailLabel[opt]=value;
6248 }
6249 };
6250
6251 if( G.O.blackList != '' ) { G.blackList=G.O.blackList.toUpperCase().split('|'); }
6252 if( G.O.whiteList != '' ) { G.whiteList=G.O.whiteList.toUpperCase().split('|'); }
6253
6254 if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array ) {
6255 var l=G.O.albumList2.length;
6256 for(var i=0; i< l; i++ ) {
6257 if( G.O.albumList2[i].indexOf('&authkey') !== -1 || G.O.albumList2[i].indexOf('?authkey') !== -1 ) {
6258 // private Google Photos album
6259 G.albumListHidden.push(G.O.albumList2[i]);
6260 }
6261 else {
6262 G.albumList.push(G.O.albumList2[i]);
6263 }
6264 }
6265 // G.albumList=G.O.albumList.toUpperCase().split('|');
6266 }
6267 if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string' ) {
6268 if( G.O.albumList2.indexOf('&authkey') !== -1 ) {
6269 // private Google Photos album
6270 G.albumListHidden.push(G.O.albumList2);
6271 }
6272 else {
6273 G.albumList.push(G.O.albumList2);
6274 }
6275 }
6276 if( G.albumListHidden.length > 0 ) {
6277 G.O.locationHash = false; // disable hash location for hidden/privat albums --> combination is impossible
6278 }
6279
6280
6281 // thumbnail image crop
6282 G.tn.opt.lN.crop = G.O.thumbnailCrop;
6283 G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
6284
6285
6286 function ThumbnailOpt( lN, l1, opt) {
6287 G.tn.opt.lN[opt]=G.O[lN];
6288 G.tn.opt.l1[opt]=G.O[lN];
6289 if( toType(G.O[l1]) == 'number' ) {
6290 G.tn.opt.l1[opt]=G.O[l1];
6291 }
6292 }
6293 // thumbnail stacks
6294 ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
6295 // thumbnail stacks translate X
6296 ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
6297 // thumbnail stacks translate Y
6298 ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
6299 // thumbnail stacks translate Z
6300 ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
6301 // thumbnail stacks rotate X
6302 ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
6303 // thumbnail stacks rotate Y
6304 ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
6305 // thumbnail stacks rotate Z
6306 ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
6307 // thumbnail stacks scale
6308 ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
6309 // thumbnail gutter width
6310 ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
6311 // thumbnail gutter height
6312 ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
6313 // thumbnail grid base height (for cascading layout)
6314 ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
6315
6316 // gallery display mode
6317 G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
6318 G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
6319
6320 // gallery maximum number of lines of thumbnails
6321 G.galleryMaxRows.lN = G.O.galleryMaxRows;
6322 G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
6323
6324 // gallery last row full
6325 G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
6326 G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
6327
6328 // gallery sorting
6329 G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
6330 G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
6331
6332 // gallery display transition
6333 G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
6334 G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
6335
6336 // gallery display transition duration
6337 G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
6338 G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
6339
6340 // gallery max items per album (not for inline/api defined items)
6341 G.galleryMaxItems.lN = G.O.galleryMaxItems;
6342 G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
6343
6344 // gallery filter tags
6345 G.galleryFilterTags.lN = G.O.galleryFilterTags;
6346 G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
6347
6348 // gallery pagination
6349 G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
6350
6351 if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
6352 G.VOM.slideshowDelay = G.O.slideshowDelay;
6353 }
6354 else {
6355 NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
6356 }
6357
6358 // gallery display transition
6359 if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
6360 if( G.O.thumbnailDisplayTransition === true ) {
6361 G.tn.opt.lN.displayTransition = 'FADEIN';
6362 G.tn.opt.l1.displayTransition = 'FADEIN';
6363 }
6364 else {
6365 G.tn.opt.lN.displayTransition = 'NONE';
6366 G.tn.opt.l1.displayTransition = 'NONE';
6367 }
6368 }
6369
6370 if( G.O.fnThumbnailDisplayEffect !== '' ) {
6371 G.tn.opt.lN.displayTransition = 'CUSTOM';
6372 G.tn.opt.l1.displayTransition = 'CUSTOM';
6373 }
6374 if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
6375 G.tn.opt.l1.displayTransition = 'CUSTOM';
6376 }
6377
6378 // parse thumbnail display transition
6379 function thumbnailDisplayTransitionParse( cfg, level ) {
6380 if( typeof cfg == 'string' ) {
6381 var st=cfg.split('_');
6382 if( st.length == 1 ) {
6383 G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
6384 }
6385 if( st.length == 2 ) {
6386 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6387 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6388 }
6389 if( st.length == 3 ) {
6390 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6391 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6392 G.tn.opt[level]['displayTransitionEasing'] = st[2];
6393 }
6394 }
6395 }
6396 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
6397 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
6398 thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
6399
6400
6401 // thumbnail display transition duration
6402 ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
6403 // thumbnail display transition interval duration
6404 ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
6405
6406
6407 // resolution breakpoints --> convert old syntax to new one
6408 if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
6409 if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
6410 if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
6411 if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
6412
6413 // THUMBNAIL BUILD INIT
6414 //level 1
6415 if( G.O.thumbnailL1BuildInit2 !== undefined ) {
6416 var t1 = G.O.thumbnailL1BuildInit2.split('|');
6417 for( var i = 0; i < t1.length; i++ ) {
6418 var o1 = t1[i].trim().split('_');
6419 if( o1.length == 3 ) {
6420 var i1 = NewTBuildInit();
6421 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6422 i1.property = o1[1];
6423 i1.value = o1[2];
6424 G.tn.buildInit.level1.push(i1);
6425 }
6426 }
6427 }
6428 //level N
6429 if( G.O.thumbnailBuildInit2 !== undefined ) {
6430 var t1 = G.O.thumbnailBuildInit2.split('|');
6431 for( var i = 0; i < t1.length; i++ ) {
6432 var o1 = t1[i].trim().split('_');
6433 if( o1.length == 3 ) {
6434 var i1 = NewTBuildInit();
6435 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6436 i1.property = o1[1];
6437 i1.value = o1[2];
6438 G.tn.buildInit.std.push(i1);
6439 }
6440 }
6441 }
6442
6443
6444 // THUMBNAIL HOVER EFFETCS
6445
6446 // thumbnails hover effects - Level1
6447 var tL1HE = G.O.thumbnailL1HoverEffect2;
6448 if( tL1HE !== undefined ) {
6449 switch( toType(tL1HE) ) {
6450 case 'string':
6451 var tmp = tL1HE.split('|');
6452 for(var i = 0; i < tmp.length; i++) {
6453 var oDef = NewTHoverEffect();
6454 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6455 if( oDef != null ) {
6456 G.tn.hoverEffects.level1.push(oDef);
6457 }
6458 }
6459 break;
6460 case 'object':
6461 var oDef = NewTHoverEffect();
6462 oDef = jQuery.extend(oDef,tL1HE);
6463 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6464 if( oDef != null ) {
6465 G.tn.hoverEffects.level1.push(oDef);
6466 }
6467 break;
6468 case 'array':
6469 for(var i = 0; i < tL1HE.length; i++) {
6470 var oDef = NewTHoverEffect();
6471 oDef = jQuery.extend(oDef,tL1HE[i]);
6472 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6473 if( oDef != null ) {
6474 G.tn.hoverEffects.level1.push(oDef);
6475 }
6476 }
6477 break;
6478 case 'null':
6479 break;
6480 default:
6481 NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
6482 }
6483 }
6484 G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
6485
6486 // thumbnails hover effects - other levels
6487 var tHE = G.O.thumbnailHoverEffect2;
6488 switch( toType(tHE) ) {
6489 case 'string':
6490 var tmp = tHE.split('|');
6491 for(var i = 0; i < tmp.length; i++) {
6492 var oDef = NewTHoverEffect();
6493 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6494 if( oDef != null ) {
6495 G.tn.hoverEffects.std.push(oDef);
6496 }
6497 }
6498 break;
6499 case 'object':
6500 var oDef = NewTHoverEffect();
6501 oDef = jQuery.extend(oDef, tHE);
6502 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6503 if( oDef != null ) {
6504 G.tn.hoverEffects.std.push(oDef);
6505 }
6506 break;
6507 case 'array':
6508 for(var i = 0; i < tHE.length; i++) {
6509 var oDef = NewTHoverEffect();
6510 oDef = jQuery.extend(oDef,tHE[i]);
6511 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6512 if( oDef!= null ) {
6513 G.tn.hoverEffects.std.push(oDef);
6514 }
6515 }
6516 break;
6517 case 'null':
6518 break;
6519 default:
6520 NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
6521 }
6522 G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
6523
6524
6525 if( G.O.touchAnimationL1 == undefined ) {
6526 G.O.touchAnimationL1 = G.O.touchAnimation;
6527 }
6528
6529 // disable thumbnail touch animation when no hover effect defined
6530 if( G.tn.hoverEffects.std.length == 0 ) {
6531 if( G.tn.hoverEffects.level1.length == 0 ) {
6532 G.O.touchAnimationL1 = false;
6533 }
6534 G.O.touchAnimation = false;
6535 }
6536
6537
6538 // thumbnail sizes
6539 if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == '' ) { G.O.thumbnailHeight = 'auto'; }
6540 if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
6541 if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
6542 if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
6543
6544 // RETRIEVE ALL THUMBNAIL SIZES
6545 function ThumbnailSizes( srcOpt, onlyl1, opt) {
6546 if( G.O[srcOpt] == null ) { return; }
6547
6548 if( toType(G.O[srcOpt]) == 'number' ) {
6549 ThumbnailsSetSize( opt, 'l1', G.O[srcOpt], 'u');
6550 if( !onlyl1 ) {
6551 ThumbnailsSetSize( opt, 'lN', G.O[srcOpt], 'u');
6552 }
6553 }
6554 else {
6555 var ws=G.O[srcOpt].split(' ');
6556 var v = 'auto';
6557 if( ws[0].substring(0,4) != 'auto' ) { v=parseInt(ws[0]); }
6558 var c = 'u';
6559 if( ws[0].charAt(ws[0].length - 1) == 'C' ) { c='c'; }
6560 ThumbnailsSetSize( opt, 'l1', v, c ); // default value for all resolutions and navigation levels
6561 if( !onlyl1 ) {
6562 ThumbnailsSetSize( opt, 'lN', v, c );
6563 }
6564 for( var i = 1; i < ws.length; i++ ) {
6565 var r = ws[i].substring(0,2).toLowerCase();
6566 if( /xs|sm|me|la|xl/i.test(r) ) {
6567 var w = ws[i].substring(2);
6568 var v = 'auto';
6569 if( w.substring(0,4) != 'auto' ) { v = parseInt(w); }
6570 var c = 'u';
6571 if( w.charAt(w.length - 1) == 'C' ) { c = 'c'; }
6572 G.tn.settings[opt]['l1'][r] = v;
6573 G.tn.settings[opt]['l1'][r + 'c'] = c;
6574 if( !onlyl1 ) {
6575 G.tn.settings[opt]['lN'][r] = v;
6576 G.tn.settings[opt]['lN'][r + 'c'] = c;
6577 }
6578 }
6579 }
6580 }
6581 }
6582 ThumbnailSizes( 'thumbnailWidth', false, 'width');
6583 ThumbnailSizes( 'thumbnailL1Width', true, 'width');
6584
6585 ThumbnailSizes( 'thumbnailHeight', false, 'height');
6586 ThumbnailSizes( 'thumbnailL1Height', true, 'height');
6587
6588
6589 G.O.thumbnailBorderHorizontal = parseInt(G.O.thumbnailBorderHorizontal);
6590 G.O.thumbnailBorderVertical = parseInt(G.O.thumbnailBorderVertical);
6591 G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
6592
6593
6594 // retrieve all mosaic layout patterns
6595 // default pattern
6596 if( G.O.galleryMosaic != undefined ) {
6597 // clone object
6598 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6599 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6600 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6601 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6602 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6603 G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6604 G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6605 G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6606 G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6607 G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6608 G.tn.settings.mosaicCalcFactor('l1', 'xs');
6609 G.tn.settings.mosaicCalcFactor('l1', 'sm');
6610 G.tn.settings.mosaicCalcFactor('l1', 'me');
6611 G.tn.settings.mosaicCalcFactor('l1', 'la');
6612 G.tn.settings.mosaicCalcFactor('l1', 'xl');
6613 G.tn.settings.mosaicCalcFactor('lN', 'xs');
6614 G.tn.settings.mosaicCalcFactor('lN', 'sm');
6615 G.tn.settings.mosaicCalcFactor('lN', 'me');
6616 G.tn.settings.mosaicCalcFactor('lN', 'la');
6617 G.tn.settings.mosaicCalcFactor('lN', 'xl');
6618 }
6619 if( G.O.galleryL1Mosaic != undefined ) {
6620 // default L1 pattern
6621 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6622 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6623 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6624 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6625 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6626 G.tn.settings.mosaicCalcFactor('l1', 'xs');
6627 G.tn.settings.mosaicCalcFactor('l1', 'sm');
6628 G.tn.settings.mosaicCalcFactor('l1', 'me');
6629 G.tn.settings.mosaicCalcFactor('l1', 'la');
6630 G.tn.settings.mosaicCalcFactor('l1', 'xl');
6631 }
6632 for( var w = 0; w < G.tn.settings.mosaic.l1; w++ ) {
6633 if( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] != undefined ) {
6634 G.tn.settings.mosaic.lN[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6635 G.tn.settings.mosaic.l1[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6636 G.tn.settings.mosaicCalcFactor('l1', G.tn.settings.mosaic.l1[w]);
6637 G.tn.settings.mosaicCalcFactor('lN', G.tn.settings.mosaic.l1[w]);
6638 }
6639 }
6640 for( var w = 0; w < G.tn.settings.mosaic.l1; w++ ) {
6641 if( G.O['galleryL1Mosaic'+G.tn.settings.mosaic.l1[w].toUpperCase()] != undefined ) {
6642 G.tn.settings.mosaic.l1[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic'+G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6643 G.tn.settings.mosaicCalcFactor('l1', G.tn.settings.mosaic.l1[w]);
6644 }
6645 }
6646
6647 G.O.imageTransition = G.O.imageTransition.toUpperCase();
6648
6649 G.layout.SetEngine();
6650
6651 // init plugins
6652 switch( G.O.kind ) {
6653 // MARKUP / API
6654 case '':
6655 break;
6656 // JSON, Flickr, Picasa, ...
6657 default:
6658 jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
6659 }
6660
6661 }
6662
6663 // HOVER EFFECTS
6664 function ThumbnailHoverEffectExtract( name, effect) {
6665 var easings = [ 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 'easeOutQuint', 'easeInOutQuint', 'easeInSine', 'easeOutSine', 'easeInOutSine', 'easeInExpo', 'easeOutExpo', 'easeInOutExpo', 'easeInCirc', 'easeOutCirc', 'easeInOutCirc', 'easeOutBounce', 'easeInBack', 'easeOutBack', 'easeInOutBack', 'elastic', 'bounce'];
6666
6667 var sp = name.split('_');
6668 if( sp.length >= 4 ) {
6669 // var oDef=NewTHoverEffect();
6670 effect.name = '';
6671 effect.type = sp[1];
6672 effect.from = sp[2];
6673 effect.to = sp[3];
6674 if( sp.length >= 5 ) {
6675 // effect.duration=sp[4];
6676
6677 for( var n = 4; n < sp.length; n++ ) {
6678 var v = sp[n];
6679
6680 // check if an easing name
6681 var foundEasing = false;
6682 for( var e = 0; e < easings.length; e++) {
6683 if( v == easings[e] ) {
6684 foundEasing = true;
6685 effect.easing = v;
6686 break;
6687 }
6688 }
6689 if( foundEasing === true ) {
6690 continue;
6691 }
6692
6693 v = v.toUpperCase();
6694
6695 if( v == 'HOVERIN' ) {
6696 effect.hoverout = false;
6697 continue;
6698 }
6699 if( v == 'HOVEROUT' ) {
6700 effect.hoverin = false;
6701 continue;
6702 }
6703
6704 if( v == 'KEYFRAME' ) {
6705 effect.firstKeyframe = false;
6706 continue;
6707 }
6708
6709 var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10); // extract a number if one exists
6710
6711 if( num > 0 ) {
6712 // the string contains a numbers > 0
6713 if( v.indexOf('DURATION') >= 0 ) {
6714 effect.duration = num;
6715 continue;
6716 }
6717 if( v.indexOf('DURATIONBACK') >= 0 ) {
6718 effect.durationBack = num;
6719 continue;
6720 }
6721 if( v.indexOf('DELAY') >= 0 ) {
6722 effect.delay = num;
6723 continue;
6724 }
6725 if( v.indexOf('DELAYBACK') >= 0 ) {
6726 effect.delayBack = num;
6727 continue;
6728 }
6729
6730 // no parameter name found -> default is duration
6731 effect.duration = num;
6732 }
6733 }
6734 }
6735 effect.element=ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
6736
6737 }
6738 else {
6739 effect.name = name;
6740 // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
6741 // return null;
6742 }
6743 return effect;
6744 }
6745
6746
6747 function ThumbnailOverEffectsGetCSSElement( element, property ) {
6748 var r = element;
6749
6750 switch ( element ) {
6751 case 'image':
6752 if( property == 'blur' || property == 'brightness' || property == 'grayscale' || property == 'sepia' || property == 'contrast' || property == 'opacity'|| property == 'saturate' ) {
6753 // r='.nGY2GThumbnailImg';
6754 r = '.nGY2GThumbnailImage';
6755 }
6756 else {
6757 r = '.nGY2GThumbnailImage';
6758 }
6759 break;
6760 case 'thumbnail':
6761 r = '.nGY2GThumbnail';
6762 break;
6763 case 'label':
6764 r = '.nGY2GThumbnailLabel';
6765 break;
6766 case 'title':
6767 r = '.nGY2GThumbnailTitle';
6768 break;
6769 case 'description':
6770 r = '.nGY2GThumbnailDescription';
6771 break;
6772 case 'tools':
6773 r = '.nGY2GThumbnailIcons';
6774 break;
6775 case 'customlayer':
6776 r = '.nGY2GThumbnailCustomLayer';
6777 break;
6778 }
6779 return r;
6780 }
6781
6782 // convert preset hover effects to new ones (nanogallery2)
6783 function ThumbnailOverEffectsPreset( effects ) {
6784
6785 // COMPATIBILITY WITH nanoGALLERY
6786 // OK:
6787 // 'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
6788 // 'overScale', 'overScaleOutside', 'descriptionAppear'
6789 // 'slideUp', 'slideDown', 'slideRight', 'slideLeft'
6790 // 'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
6791 // 'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
6792 // KO:
6793 // 'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert'
6794 // TODO:
6795 // 'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
6796
6797
6798
6799 var newEffects=[];
6800 for( var i=0; i< effects.length; i++ ) {
6801 switch( effects[i].name.toUpperCase() ) {
6802 case 'BORDERLIGHTER':
6803 // var color=ngtinycolor(GalleryThemeGetCurrent().thumbnail.borderColor);
6804 // name='thumbnail_borderColor_'+color.toRgbString()+'_'+color.lighten(50).toRgbString();
6805
6806 var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
6807 name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
6808 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
6809 break;
6810 case 'BORDERDARKER':
6811 // var color=ngtinycolor(GalleryThemeGetCurrent().thumbnail.borderColor);
6812 // name='thumbnail_borderColor_'+color.toRgbString()+'_'+color.darken(50).toRgbString();
6813 var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
6814 name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
6815 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
6816 break;
6817 case 'SCALE120':
6818 newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
6819 break;
6820 case 'LABELAPPEAR':
6821 case 'LABELAPPEAR75':
6822 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
6823 break;
6824 case 'TOOLSAPPEAR':
6825 newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
6826 break;
6827 case 'TOOLSSLIDEDOWN':
6828 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
6829 break;
6830 case 'TOOLSSLIDEUP':
6831 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
6832 break;
6833 case 'LABELOPACITY50':
6834 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
6835 break;
6836 case 'LABELSLIDEUPTOP':
6837 case 'LABELSLIDEUP':
6838 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
6839 break;
6840 case 'LABELSLIDEDOWN':
6841 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
6842 break;
6843 case 'SCALELABELOVERIMAGE':
6844 newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
6845 var n = cloneJSObject(effects[i]);
6846 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
6847 break;
6848 case 'OVERSCALE':
6849 case 'OVERSCALEOUTSIDE':
6850 name = 'label_scale_0_100';
6851 newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
6852 var n = cloneJSObject(effects[i]);
6853 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
6854 n = cloneJSObject(effects[i]);
6855 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
6856 n = cloneJSObject(effects[i]);
6857 newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
6858 break;
6859 case 'DESCRIPTIONAPPEAR':
6860 newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
6861 break;
6862 case 'SLIDERIGHT':
6863 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
6864 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
6865 break;
6866 case 'SLIDELEFT':
6867 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
6868 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
6869 break;
6870 case 'SLIDEUP':
6871 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
6872 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
6873 break;
6874 case 'SLIDEDOWN':
6875 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
6876 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
6877 break;
6878 case 'IMAGESCALE150':
6879 case 'IMAGESCALE150OUTSIDE':
6880 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
6881 break;
6882 case 'IMAGESCALEIN80':
6883 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
6884 break;
6885 case 'IMAGESLIDERIGHT':
6886 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
6887 break;
6888 case 'IMAGESLIDELEFT':
6889 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
6890 break;
6891 case 'IMAGESLIDEUP':
6892 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
6893 break;
6894 case 'IMAGESLIDEDOWN':
6895 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
6896 break;
6897 case 'LABELSLIDEUP':
6898 case 'LABELSLIDEUPTOP':
6899 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
6900 break;
6901 case 'LABELSLIDEUPDOWN':
6902 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
6903 break;
6904 case 'DESCRIPTIONSLIDEUP':
6905 newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
6906 break;
6907
6908 case 'IMAGEBLURON':
6909 newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
6910 break;
6911 case 'IMAGEBLUROFF':
6912 newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
6913 break;
6914 case 'IMAGEGRAYON':
6915 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
6916 break;
6917 case 'IMAGEGRAYOFF':
6918 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
6919 break;
6920 case 'IMAGESEPIAON':
6921 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
6922 break;
6923 case 'IMAGESEPIAOFF':
6924 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
6925 break;
6926
6927 default:
6928 newEffects.push(effects[i]);
6929 break;
6930 }
6931 }
6932
6933 return newEffects;
6934 }
6935
6936
6937 // Thumbnail hover effect definition
6938 function NewTHoverEffect() {
6939 var oDef={
6940 name: '',
6941 element: '', // element class
6942 type: '',
6943 from: '', // start value
6944 to: '', // end value
6945 hoverin: true,
6946 hoverout: true,
6947 firstKeyframe: true,
6948 delay: 0,
6949 delayBack: 0,
6950 duration: 400,
6951 durationBack: 300,
6952 easing: 'easeOutQuart',
6953 easingBack: 'easeOutQuart',
6954 animParam: null
6955 };
6956 return oDef;
6957 }
6958
6959 function NewTBuildInit() {
6960 // to set CSS properties
6961 var oDef={ element: '', property: '', value: '' };
6962 return oDef;
6963 }
6964
6965
6966 function ThumbnailStyle( cfg, level) {
6967
6968 switch( cfg.position ){
6969 case 'onBottom' :
6970 G.tn.style[level]['label'] = 'bottom:0; ';
6971 break;
6972 case 'overImageOnTop' :
6973 G.tn.style[level]['label'] = 'top:0; position:absolute;';
6974 break;
6975 case 'overImageOnMiddle' :
6976 G.tn.style[level]['label'] = 'top:0; bottom:0;';
6977 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
6978 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
6979 break;
6980 case 'custom' :
6981 break;
6982 case 'overImageOnBottom' :
6983 default :
6984 G.O.thumbnailLabel.position = 'overImageOnBottom';
6985 G.tn.style[level].label = 'bottom:0; position:absolute;';
6986 break;
6987 }
6988 // if( G.layout.engine != 'CASCADING' ) {
6989 if( cfg.position != 'onBottom' ) {
6990 // multi-line
6991 if( cfg.titleMultiLine ) {
6992 G.tn.style[level]['title'] = 'white-space:normal;';
6993 }
6994 if( cfg.descriptionMultiLine ) {
6995 G.tn.style[level]['desc'] = 'white-space:normal;';
6996 }
6997 }
6998
6999
7000 switch( cfg.align ) {
7001 case 'right':
7002 G.tn.style[level].label += 'text-align:right;';
7003 break;
7004 case 'left':
7005 G.tn.style[level].label += 'text-align:left;';
7006 break;
7007 default:
7008 G.tn.style[level].label += 'text-align:center;';
7009 break;
7010 }
7011 if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
7012 G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
7013 }
7014 if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
7015 G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
7016 }
7017
7018 if( cfg.displayDescription == false ) {
7019 G.tn.style[level].desc += 'display:none;';
7020 }
7021 }
7022
7023
7024 // cache some thumbnail settings
7025 function ThumbnailDefCaches() {
7026 // thumbnail content CSS styles
7027
7028 // settings for level L1 and LN
7029 ThumbnailStyle( G.O.thumbnailLabel, 'lN');
7030 ThumbnailStyle( G.O.thumbnailLabel, 'l1');
7031
7032 if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
7033 // settings for level L1
7034 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7035 }
7036
7037 G.tn.borderWidth = G.O.thumbnailBorderHorizontal;
7038 G.tn.borderHeight = G.O.thumbnailBorderVertical;
7039
7040
7041 // default thumbnail sizes levels l1 and lN
7042 var lst=['xs','sm','me','la','xl'];
7043 for( var i = 0; i < lst.length; i++ ) {
7044 var w = G.tn.settings.width.lN[lst[i]];
7045 if( w != 'auto' ) {
7046 G.tn.defaultSize.width.lN[lst[i]] = w;
7047 G.tn.defaultSize.width.l1[lst[i]] = w;
7048 }
7049 else {
7050 var h = G.tn.settings.height.lN[lst[i]];
7051 G.tn.defaultSize.width.lN[lst[i]] = h; // dynamic width --> set height value as default for the width
7052 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7053 }
7054 }
7055 for( var i = 0; i < lst.length; i++ ) {
7056 var h = G.tn.settings.height.lN[lst[i]];
7057 if( h != 'auto' ) {
7058 // grid or justified layout
7059 G.tn.defaultSize.height.lN[lst[i]] = h; //+G.tn.labelHeight.get();
7060 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7061 }
7062 else {
7063 var w = G.tn.settings.width.lN[lst[i]];
7064 G.tn.defaultSize.height.lN[lst[i]] = w; // dynamic height --> set width value as default for the height
7065 G.tn.defaultSize.height.l1[lst[i]] = w; // dynamic height --> set width value as default
7066 }
7067 }
7068
7069 // default thumbnail sizes levels l1
7070 for( var i = 0; i < lst.length; i++ ) {
7071 var w = G.tn.settings.width.l1[lst[i]];
7072 if( w != 'auto' ) {
7073 G.tn.defaultSize.width.l1[lst[i]] = w;
7074 }
7075 else {
7076 var h = G.tn.settings.height.l1[lst[i]];
7077 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7078 }
7079 }
7080 for( var i = 0; i < lst.length; i++ ) {
7081 var h = G.tn.settings.height.l1[lst[i]];
7082 if( h != 'auto' ) {
7083 // grid or justified layout
7084 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7085 }
7086 else {
7087 var w = G.tn.settings.width.l1[lst[i]];
7088 G.tn.defaultSize.height.l1[lst[i]]= w ; // dynamic height --> set width value as default
7089 }
7090 }
7091
7092 }
7093
7094
7095 // ##### THUMBNAIL SIZE MANAGEMENT
7096 function ThumbnailsSetSize( dir, level, v, crop ) {
7097 G.tn.settings[dir][level]['xs'] = v;
7098 G.tn.settings[dir][level]['sm'] = v;
7099 G.tn.settings[dir][level]['me'] = v;
7100 G.tn.settings[dir][level]['la'] = v;
7101 G.tn.settings[dir][level]['xl'] = v;
7102 G.tn.settings[dir][level]['xsc'] = crop;
7103 G.tn.settings[dir][level]['smc'] = crop;
7104 G.tn.settings[dir][level]['mec'] = crop;
7105 G.tn.settings[dir][level]['lac'] = crop;
7106 G.tn.settings[dir][level]['xlc'] = crop;
7107 }
7108
7109
7110 //
7111 function GalleryThemeGetCurrent() {
7112
7113 var cs=null;
7114 switch(toType(G.O.galleryTheme)) {
7115 case 'object': // user custom color scheme object
7116 cs = G.galleryTheme_dark; // default color scheme
7117 jQuery.extend(true,cs,G.O.galleryTheme);
7118 break;
7119 case 'string': // name of an internal defined color scheme
7120 switch( G.O.galleryTheme ) {
7121 case 'light':
7122 cs = G.galleryTheme_light;
7123 break;
7124 case 'default':
7125 case 'dark':
7126 case 'none':
7127 default:
7128 cs = G.galleryTheme_dark;
7129 }
7130 break;
7131 default:
7132 cs = G.galleryTheme_dark;
7133 }
7134 return cs;
7135 }
7136
7137 // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
7138 function SetGalleryTheme() {
7139
7140 if( typeof G.O.colorScheme !== 'undefined' ) {
7141 G.O.galleryTheme = G.O.colorScheme;
7142 }
7143
7144 var cs = null;
7145 var galleryTheme = '';
7146 switch(toType(G.O.galleryTheme)) {
7147 case 'object': // user custom color scheme object
7148 cs = G.galleryTheme_dark; // default color scheme
7149 jQuery.extend(true,cs,G.O.galleryTheme);
7150 galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
7151 break;
7152 case 'string': // name of an internal defined color scheme
7153 switch( G.O.galleryTheme ) {
7154 case 'light':
7155 cs = G.galleryTheme_light;
7156 galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
7157 break;
7158 case 'default':
7159 case 'dark':
7160 case 'none':
7161 default:
7162 cs = G.galleryTheme_dark;
7163 galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
7164 }
7165 break;
7166 default:
7167 NanoAlert(G, 'Error in galleryTheme parameter.');
7168 return;
7169 }
7170
7171 //var s1='.nanogallery_theme_'+G.O.theme+' ';
7172 var s1='.' + galleryTheme + ' ';
7173
7174 // navigation bar
7175 var s=s1+'.nGY2Navigationbar { background:'+cs.navigationBar.background+'; }'+'\n';
7176 if( cs.navigationBar.border !== undefined && cs.navigationBar.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+cs.navigationBar.border+'; }'+'\n'; }
7177 if( cs.navigationBar.borderTop !== undefined && cs.navigationBar.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+cs.navigationBar.borderTop+'; }'+'\n'; }
7178 if( cs.navigationBar.borderBottom !== undefined && cs.navigationBar.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+cs.navigationBar.borderBottom+'; }'+'\n'; }
7179 if( cs.navigationBar.borderRight !== undefined && cs.navigationBar.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+cs.navigationBar.borderRight+'; }'+'\n'; }
7180 if( cs.navigationBar.borderLeft !== undefined && cs.navigationBar.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+cs.navigationBar.borderLeft+'; }'+'\n'; }
7181
7182 // navigation bar - breadcrumb
7183 s+=s1+'.nGY2Breadcrumb { background:'+cs.navigationBreadcrumb.background+'; border-radius:'+cs.navigationBreadcrumb.borderRadius+'; }'+'\n';
7184 s+=s1+'.nGY2Breadcrumb .oneItem { color:'+cs.navigationBreadcrumb.color+'; }'+'\n';
7185 s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+cs.navigationBreadcrumb.colorHover+'; }'+'\n';
7186
7187 // navigation bar - tag filter
7188 s+=s1+'.nGY2NavFilterUnselected { color:'+cs.navigationFilter.color+'; background:'+cs.navigationFilter.background+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
7189 s+=s1+'.nGY2NavFilterSelected { color:'+cs.navigationFilter.colorSelected+'; background:'+cs.navigationFilter.backgroundSelected+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
7190 s+=s1+'.nGY2NavFilterSelectAll { color:'+cs.navigationFilter.colorSelected+'; background:'+cs.navigationFilter.background+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
7191
7192 // thumbnails
7193 s+=s1+'.nGY2GThumbnail { background:'+cs.thumbnail.background+'; border-color:'+cs.thumbnail.borderColor+'; border-top-width:'+G.O.thumbnailBorderVertical+'px; border-right-width:'+G.O.thumbnailBorderHorizontal+'px; border-bottom-width:'+G.O.thumbnailBorderVertical+'px; border-left-width:'+G.O.thumbnailBorderHorizontal+'px;}'+'\n';
7194 s+=s1+'.nGY2GThumbnailStack { background:'+cs.thumbnail.stackBackground+'; }'+'\n';
7195 // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7196 s+=s1+'.nGY2TnImgBack { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7197 s+=s1+'.nGY2GThumbnailAlbumUp { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
7198 s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+cs.thumbnail.titleColor+'; }\n';
7199 s+=s1+'.nGY2GThumbnailLabel { background:'+cs.thumbnail.labelBackground+'; opacity:'+cs.thumbnail.labelOpacity+'; }'+'\n';
7200 s+=s1+'.nGY2GThumbnailImageTitle { color:'+cs.thumbnail.titleColor+'; background-color:'+cs.thumbnail.titleBgColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow+';')+' }'+'\n';
7201 s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+cs.thumbnail.titleColor+'; background-color:'+cs.thumbnail.titleBgColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow+';')+' }'+'\n';
7202 s+=s1+'.nGY2GThumbnailDescription { color:'+cs.thumbnail.descriptionColor+'; background-color:'+cs.thumbnail.descriptionBgColor+'; '+(cs.thumbnail.descriptionShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.descriptionShadow+';')+' }'+'\n';
7203
7204 // thumbnails - icons
7205 s+=s1+'.nGY2GThumbnailIcons { padding:'+cs.thumbnailIcon.padding+'; }\n';
7206 s+=s1+'.nGY2GThumbnailIcon { color:'+cs.thumbnailIcon.color+'; }\n';
7207 s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+cs.thumbnailIcon.color+'; }\n';
7208
7209 // gallery pagination -> dot/rectangle based
7210 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
7211 s+=s1+'.nGY2paginationDot { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeColor+';}\n';
7212 s+=s1+'.nGY2paginationDotCurrentPage { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeSelectedColor+';}\n';
7213 s+=s1+'.nGY2paginationRectangle { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeColor+';}\n';
7214 s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeSelectedColor+';}\n';
7215 } else {
7216 s+=s1+'.nGY2paginationItem { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7217 s+=s1+'.nGY2paginationItemCurrentPage { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7218 s+=s1+'.nGY2PaginationPrev { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7219 s+=s1+'.nGY2PaginationNext { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7220 s+=s1+'.nGY2paginationItemCurrentPage { background:'+cs.pagination.backgroundSelected+'; }\n';
7221 }
7222
7223 // gallery more button
7224 s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+cs.thumbnail.background+'; border-color:'+cs.thumbnail.borderColor+'; border-top-width:'+G.O.thumbnailBorderVertical+'px; border-right-width:'+G.O.thumbnailBorderHorizontal+'px; border-bottom-width:'+G.O.thumbnailBorderVertical+'px; border-left-width:'+G.O.thumbnailBorderHorizontal+'px;}\n';
7225 s+=s1+'.nGY2GalleryMoreButtonAnnotation { color:'+cs.thumbnail.titleColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow)+'; }\n';
7226
7227 jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
7228 G.$E.base.addClass(galleryTheme);
7229
7230 };
7231
7232 // ##### VIEWER COLOR SCHEME #####
7233 function SetViewerTheme( ) {
7234
7235 if( G.VOM.viewerTheme != '' ) {
7236 G.VOM.$cont.addClass(G.VOM.viewerTheme);
7237 return;
7238 }
7239
7240 if( typeof G.O.colorSchemeViewer !== 'undefined' ) {
7241 G.O.viewerTheme = G.O.colorSchemeViewer;
7242 }
7243
7244 var cs=null;
7245 switch(toType(G.O.viewerTheme)) {
7246 case 'object': // user custom color scheme object
7247 cs = G.viewerTheme_dark;
7248 jQuery.extend(true,cs,G.O.viewerTheme);
7249 G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_'+G.baseEltID;
7250 break;
7251 case 'string': // name of an internal defined color scheme
7252 switch( G.O.viewerTheme ) {
7253 case 'none':
7254 return;
7255 break;
7256 case 'light':
7257 cs = G.viewerTheme_light;
7258 G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
7259 break;
7260 case 'border':
7261 cs = G.viewerTheme_border;
7262 G.VOM.viewerTheme = 'nanogallery_viewertheme_border_' + G.baseEltID;
7263 break;
7264 case 'dark':
7265 case 'default':
7266 cs = G.viewerTheme_dark;
7267 G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
7268 break;
7269 }
7270 break;
7271 default:
7272 NanoAlert(G, 'Error in viewerTheme parameter.');
7273 return;
7274 }
7275
7276 var s1 = '.' + G.VOM.viewerTheme + ' ';
7277 var s = s1 + '.nGY2Viewer { background:'+cs.background+'; }'+'\n';
7278 s += s1 + '.nGY2ViewerMedia { border:'+cs.imageBorder+'; box-shadow:'+cs.imageBoxShadow+'; }'+'\n';
7279 s += s1 + '.nGY2Viewer .toolbarBackground { background:'+cs.barBackground+'; }'+'\n';
7280 s += s1 + '.nGY2Viewer .toolbar { border:'+cs.barBorder+'; color:'+cs.barColor+'; }'+'\n';
7281 s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:'+cs.barColor+'; }'+'\n';
7282 s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:'+cs.barColor+'; }'+'\n';
7283 s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:'+cs.barColor+'; }'+'\n';
7284 s += s1 + '.nGY2Viewer .toolbar .label .title { color:'+cs.barColor+'; }'+'\n';
7285 s += s1 + '.nGY2Viewer .toolbar .label .description { color:'+cs.barDescriptionColor+'; }'+'\n';
7286 jQuery('head').append('<style>' + s + '</style>');
7287 G.VOM.$cont.addClass(G.VOM.viewerTheme);
7288 };
7289
7290
7291
7292 /** @function SetPolyFills */
7293 function SetPolyFills() {
7294
7295 // POLYFILL FOR BIND function --> for older Safari mobile
7296 // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
7297 if (!Function.prototype.bind) {
7298 Function.prototype.bind = function (oThis) {
7299 if (typeof this !== "function") {
7300 // closest thing possible to the ECMAScript 5
7301 // internal IsCallable function
7302 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
7303 }
7304
7305 var aArgs = Array.prototype.slice.call(arguments, 1),
7306 fToBind = this,
7307 fNOP = function () {},
7308 fBound = function () {
7309 return fToBind.apply(this instanceof fNOP && oThis
7310 ? this
7311 : oThis,
7312 aArgs.concat(Array.prototype.slice.call(arguments)));
7313 };
7314
7315 fNOP.prototype = this.prototype;
7316 fBound.prototype = new fNOP();
7317
7318 return fBound;
7319 };
7320 }
7321
7322 // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
7323 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7324 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
7325 // MIT license
7326 (function() {
7327 var lastTime = 0;
7328 var vendors = ['ms', 'moz', 'webkit', 'o'];
7329 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
7330 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
7331 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
7332 }
7333 if (!window.requestAnimationFrame)
7334 window.requestAnimationFrame = function(callback, element) {
7335 var currTime = new Date().getTime();
7336 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
7337 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
7338 lastTime = currTime + timeToCall;
7339 return id;
7340 };
7341
7342 if (!window.cancelAnimationFrame)
7343 window.cancelAnimationFrame = function(id) {
7344 clearTimeout(id);
7345 };
7346 }());
7347
7348 // array.removeIf -> removes items from array base on a function's result
7349 Array.prototype.removeIf = function(callback) {
7350 var i = this.length;
7351 while (i--) {
7352 if (callback(this[i], i)) {
7353 this.splice(i, 1);
7354 }
7355 }
7356 };
7357
7358 }
7359
7360
7361 // Gallery clicked/touched -> retrieve & execute action
7362 function GalleryClicked(e) {
7363
7364 var r = GalleryEventRetrieveElementl(e, false);
7365
7366 if( r.GOMidx == -1 ) { return 'exit'; }
7367
7368 var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
7369 if( G.GOM.slider.hostIdx == r.GOMidx ) {
7370 // slider on thumbnail -> open the displayed image
7371 idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
7372 }
7373 switch( r.action ) {
7374 case 'OPEN':
7375 ThumbnailOpen(idx, false);
7376 return 'exit';
7377 break;
7378 case 'DISPLAY':
7379 // used the display icon (ignore if selection mode)
7380 ThumbnailOpen(idx, true);
7381 return 'exit';
7382 break;
7383 case 'TOGGLESELECT':
7384 ThumbnailSelectionToggle(idx);
7385 return 'exit';
7386 break;
7387 case 'SHARE':
7388 PopupShare(idx);
7389 return 'exit';
7390 break;
7391 case 'DOWNLOAD':
7392 DownloadImage(idx);
7393 return 'exit';
7394 break;
7395 case 'INFO':
7396 ItemDisplayInfo(G.I[idx]);
7397 return 'exit';
7398 break;
7399 case 'CART':
7400 AddToCart(idx);
7401 return 'exit';
7402 break;
7403 default:
7404 // all other actions (custom1..10, or anything else)
7405 var fu = G.O.fnThumbnailToolCustAction;
7406 if( fu !== null ) {
7407 typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
7408 }
7409 break;
7410 }
7411 }
7412
7413 // Download an image
7414 function DownloadImage(idx) {
7415 if( G.I[idx].mediaKind != 'img' ) { return; }
7416
7417
7418 var url = G.I[idx].src;
7419
7420 if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
7421 url = G.I[idx].downloadURL;
7422 }
7423
7424 var a = document.createElement('a');
7425 a.href = url;
7426 // a.download = url.split('.').pop();
7427 a.download = url.split('/').pop();
7428 a.target = '_blank';
7429 a.style.display = 'none';
7430 document.body.appendChild(a);
7431 a.click();
7432 document.body.removeChild(a);
7433
7434 }
7435
7436 // add one image to the shopping cart
7437 function AddToCart( idx ) {
7438 // increment counter if already in shopping cart
7439 var found=false;
7440 for( var i=0; i<G.shoppingCart.length; i++ ) {
7441 if( G.shoppingCart[i].idx == idx ) {
7442 G.shoppingCart[i].cnt++;
7443 var fu=G.O.fnShoppingCartUpdated;
7444 if( fu !== null ) {
7445 typeof fu == 'function' ? fu(G.shoppingCart) : window[fu](G.shoppingCart);
7446 }
7447 TriggerCustomEvent('shoppingCartUpdated');
7448 return;
7449 }
7450 }
7451
7452 // add to shopping cart
7453 if( !found) {
7454 G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), cnt:1} );
7455 var fu=G.O.fnShoppingCartUpdated;
7456 if( fu !== null ) {
7457 typeof fu == 'function' ? fu(G.shoppingCart) : window[fu](G.shoppingCart);
7458 }
7459 TriggerCustomEvent('shoppingCartUpdated');
7460 }
7461 }
7462
7463
7464 // All thumbnails are set to unselected
7465 function ThumbnailSelectionClear() {
7466 G.GOM.nbSelected = 0;
7467 for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
7468 var item = G .I[G.GOM.items[i].thumbnailIdx];
7469 if( item.selected ) {
7470 item.selected = false;
7471 var fu = G.O.fnThumbnailSelection;
7472 if( fu !== null ) {
7473 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7474 }
7475 }
7476 item.selected = false;
7477 }
7478 }
7479
7480 function ThumbnailSelectionToggle( idx ){
7481 var item = G.I[idx];
7482 if( item.selected === true ) {
7483 ThumbnailSelectionSet(item, false);
7484 G.GOM.nbSelected--;
7485 TriggerCustomEvent('itemUnSelected');
7486 }
7487 else {
7488 ThumbnailSelectionSet(item, true);
7489 G.GOM.nbSelected++;
7490 TriggerCustomEvent('itemSelected');
7491 }
7492 }
7493
7494
7495 // this replaces ThumbnailSelection()
7496 function ThumbnailSelectionSet(item, selected ){
7497
7498 item.selected = selected;
7499
7500 ThumbnailSelectionSetIcon( item );
7501
7502 // called when the selection status of an item changed
7503 var fu=G.O.fnThumbnailSelection;
7504 if( fu !== null ) {
7505 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7506 }
7507
7508 }
7509
7510 function ThumbnailSelectionSetIcon( item ) {
7511 if( item.$elt == null ) {
7512 // thumbnail is not built
7513 return;
7514 }
7515 // var $sub = item.$getElt('.nGY2GThumbnailSub');
7516 var $sub = item.$getElt('.nGY2GThumbnail');
7517 var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
7518 if( item.selected === true) {
7519 $sub.addClass('nGY2GThumbnailSubSelected');
7520 $icon.addClass('nGY2ThumbnailSelected');
7521 $icon.removeClass('nGY2ThumbnailUnselected');
7522 $icon.html(G.O.icons.thumbnailSelected);
7523 }
7524 else {
7525 $sub.removeClass('nGY2GThumbnailSubSelected');
7526 $icon.removeClass('nGY2ThumbnailSelected');
7527 $icon.addClass('nGY2ThumbnailUnselected');
7528 $icon.html(G.O.icons.thumbnailUnselected);
7529 }
7530 }
7531
7532
7533 // display a modal popup for sharing image/album
7534 function PopupShare(idx) {
7535
7536 // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
7537 // https://github.com/Julienh/Sharrre
7538
7539 var item=G.I[idx];
7540
7541 var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
7542 var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
7543 if( item.kind == 'image' ) {
7544 newLocationHash += item.albumID + '/' + item.GetID();
7545 }
7546 else {
7547 newLocationHash += item.GetID();
7548 }
7549
7550 var content = '';
7551 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">' + G.O.icons.shareFacebook + '</div>';
7552 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">' + G.O.icons.sharePinterest + '</div>';
7553 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">' + G.O.icons.shareTumblr + '</div>';
7554 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">' + G.O.icons.shareTwitter + '</div>';
7555 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
7556 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">' + G.O.icons.shareVK + '</div>';
7557 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">' + G.O.icons.shareMail + '</div>';
7558 content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
7559 content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
7560 content += '<br>';
7561
7562 currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
7563
7564 var currentTitle = item.title;
7565 var currentTn = item.thumbImg().src;
7566
7567
7568 Popup('Share to:', content, 'Center');
7569
7570 G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
7571 e.stopPropagation();
7572
7573 var shareURL = '';
7574 var found = true;
7575 switch(jQuery(this).attr('data-share').toUpperCase()) {
7576 case 'FACEBOOK':
7577 // <a name="fb_share" type="button" href="http://www.facebook.com/sharer.php?u={$url}&media={$imgPath}&description={$desc}" class="joinFB">Share Your Advertise</a>
7578 //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
7579 shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
7580 break;
7581 case 'VK':
7582 shareURL = 'http://vk.com/share.php?url=' + currentURL;
7583 break;
7584 case 'GOOGLEPLUS':
7585 shareURL = "https://plus.google.com/share?url=" + currentURL;
7586 break;
7587 case 'TWITTER':
7588 // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
7589 shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
7590 break;
7591 case 'PINTEREST':
7592 // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
7593 shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
7594 break;
7595 case 'TUMBLR':
7596 //shareURL='https://www.tumblr.com/widgets/share/tool/preview?caption=<strong>'+currentTitle+'</strong>&tags=nanogallery2&url='+currentURL+'&shareSource=legacy&posttype=photo&content='+currentTn+'&clickthroughUrl='+currentURL;
7597 shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
7598 break;
7599 case 'MAIL':
7600 shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
7601 break;
7602 default:
7603 found = false;
7604 break;
7605 }
7606
7607 if( found ) {
7608 window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
7609 G.popup.close();
7610 // $popup.remove();
7611 }
7612
7613 });
7614 }
7615
7616 // build a modal popup
7617 function Popup(title, content, align) {
7618 var pp = '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
7619 pp += '<div class="nGY2PopupCloseButton">' + G.O.icons.buttonClose + '</div>';
7620 pp += '<div class="nGY2PopupTitle">' + title + '</div>';
7621 pp += content;
7622 pp += '</div></div>';
7623
7624 G.popup.$elt = jQuery(pp).appendTo('body');
7625 setElementOnTop( G.VOM.$viewer, G.popup.$elt);
7626
7627 G.popup.isDisplayed = true;
7628
7629 var tweenable = new NGTweenable();
7630 tweenable.tween({
7631 from: { opacity: 0 },
7632 to: { opacity: 1 },
7633 easing: 'easeInOutSine',
7634 duration: 250,
7635 step: function (state, att) {
7636 G.popup.$elt.css( state );
7637 }
7638 });
7639
7640 G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
7641 e.stopPropagation();
7642 G.popup.close();
7643 });
7644
7645 }
7646
7647
7648 function GalleryMouseEnter(e) {
7649 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
7650 var r = GalleryEventRetrieveElementl(e, true);
7651 // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
7652 if( r.GOMidx != -1 ) {
7653 var target = e.target || e.srcElement;
7654 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
7655 ThumbnailHover(r.GOMidx);
7656 }
7657 }
7658 }
7659
7660 function GalleryMouseLeave(e) {
7661 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
7662 var r = GalleryEventRetrieveElementl(e, true);
7663 if( r.GOMidx != -1 ) {
7664 var target = e.target || e.srcElement;
7665 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
7666 ThumbnailHoverOut(r.GOMidx);
7667 }
7668 }
7669 }
7670
7671 function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
7672 var r = { action: 'NONE', GOMidx: -1 };
7673
7674 if( e == undefined ) {
7675 return r;
7676 }
7677 var target = e.target || e.srcElement;
7678 while( target != G.$E.conTnParent[0] ) { // loop element parent up to find the thumbnail element
7679 if( jQuery(target).hasClass('nGY2GThumbnail') ) {
7680 if( r.action == 'NONE' ) {
7681 r.action = 'OPEN';
7682 }
7683 r.GOMidx = jQuery(target).data('index');
7684 return r;
7685 }
7686 // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
7687 if( !ignoreSubItems ) {
7688 var a = jQuery(target).data('ngy2action');
7689 if( a != '' && a != undefined ) {
7690 r.action = a;
7691 }
7692 }
7693 if( target.parentNode == null ) {
7694 return r;
7695 }
7696 target = target.parentNode;
7697 }
7698 return r;
7699 }
7700
7701
7702 // Open one thumbnail
7703 function ThumbnailOpen( idx, ignoreSelected ) {
7704 var item = G.I[idx];
7705
7706 var fu = G.O.fnThumbnailClicked;
7707 if( fu !== null ) {
7708 typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
7709 }
7710
7711 // open URL
7712 if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
7713 window.location = item.destinationURL;
7714 return;
7715 }
7716
7717 switch( item.kind ) {
7718 case 'image':
7719 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
7720 ThumbnailSelectionToggle(idx);
7721 }
7722 else {
7723 // display image
7724 DisplayPhotoIdx(idx);
7725 }
7726 break;
7727 case 'album':
7728 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
7729 ThumbnailSelectionToggle(idx);
7730 }
7731 else {
7732 if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
7733 // display album content in lightbox
7734 DisplayFirstMediaInAlbum(idx);
7735 return;
7736 }
7737 else {
7738 // display album content in gallery
7739 DisplayAlbum('-1', item.GetID());
7740 }
7741 }
7742 break;
7743 case 'albumUp':
7744 var parent = NGY2Item.Get(G, item.albumID);
7745 DisplayAlbum('-1', parent.albumID);
7746 break;
7747 }
7748 }
7749
7750 function DisplayFirstMediaInAlbum( albumIdx ) {
7751 if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+ albumIdx); }
7752
7753 var item = G.I[albumIdx];
7754
7755 var l = G.I.length;
7756 for( var i = 0; i < l; i++ ) {
7757 if( G.I[i].albumID == item.GetID() ) {
7758 DisplayPhotoIdx( i );
7759 return;
7760 }
7761 }
7762
7763 // load album content
7764 AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
7765
7766 }
7767
7768
7769 // Open link to original image (new window)
7770 function OpenOriginal( item ) {
7771 switch( G.O.kind ) {
7772 case 'flickr':
7773 var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
7774 if( item.albumID != '0' ) {
7775 sU += '/in/album-' + item.albumID + '/';
7776 }
7777 window.open(sU, '_blank');
7778 break;
7779 case 'picasa':
7780 case 'google':
7781 case 'google2':
7782 // no more working since Google changed the access to Google Photos in 2017
7783 // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
7784 // window.open(sU,'_blank');
7785 // break;
7786 default:
7787 var sU = item.responsiveURL();
7788 window.open(sU, '_blank');
7789 break;
7790 }
7791 }
7792
7793 // Display one photo (with internal or external viewer)
7794 function DisplayPhotoIdx( ngy2ItemIdx ) {
7795
7796 if( !G.O.thumbnailOpenImage ) { return; }
7797
7798 if( G.O.thumbnailOpenOriginal ) {
7799 // Open link to original image
7800 OpenOriginal( G.I[ngy2ItemIdx] );
7801 return;
7802 }
7803
7804 var items = [];
7805 G.VOM.currItemIdx = 0;
7806 G.VOM.items = [];
7807 G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
7808
7809 var vimg = new VImg(ngy2ItemIdx);
7810 G.VOM.items.push(vimg);
7811 items.push(G.I[ngy2ItemIdx]);
7812 //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)
7813 var l = G.I.length;
7814 for( var idx = ngy2ItemIdx+1; idx < l ; idx++) {
7815 var item = G.I[idx];
7816 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
7817 var vimg = new VImg(idx);
7818 G.VOM.items.push(vimg);
7819 items.push(item);
7820 }
7821 }
7822 var last = G.VOM.items.length;
7823 var cnt = 1;
7824 for( var idx = 0; idx < ngy2ItemIdx ; idx++) {
7825 var item = G.I[idx];
7826 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
7827 var vimg = new VImg(idx);
7828 vimg.mediaNumber = cnt;
7829 G.VOM.items.push(vimg);
7830 items.push(item);
7831 cnt++;
7832 }
7833 }
7834 for( var i = 0; i < last; i++ ) {
7835 G.VOM.items[i].mediaNumber = cnt;
7836 cnt++;
7837 }
7838
7839 // opens media with external viewer
7840 var fu = G.O.fnThumbnailOpen;
7841 if( fu !== null ) {
7842 typeof fu == 'function' ? fu(items) : window[fu](items);
7843 return;
7844 }
7845
7846 // use internal viewer
7847 if( !G.VOM.viewerDisplayed ) {
7848 // build viewer and display
7849 OpenInternalViewer();
7850 }
7851 else {
7852 // viewer already displayed -> display new media in current viewer
7853 G.VOM.$mediaCurrent.empty();
7854 var item = G.VOM.NGY2Item(0);
7855 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
7856 if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
7857 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
7858 }
7859 G.VOM.$mediaCurrent.append( spreloader + item.mediaMarkup);
7860 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
7861 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
7862 if( item.mediaKind == 'img' ) {
7863 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
7864 }
7865 // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
7866 // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
7867 // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
7868 DisplayInternalViewer(0, '');
7869 }
7870 }
7871
7872 function ViewerZoomStart() {
7873 if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
7874 var item=G.VOM.NGY2Item(0);
7875 if( item.imageHeight > 0 && item.imageWidth > 0 ) {
7876 if( G.VOM.zoom.isZooming === false ) {
7877 // default zoom
7878 G.VOM.zoom.userFactor = 1;
7879 G.VOM.zoom.isZooming = true;
7880 }
7881 return true;
7882 }
7883 }
7884 }
7885
7886 function ViewerZoomIn( zoomIn ) {
7887//console.log(zoomIn);
7888 if( zoomIn ) {
7889 // zoom in
7890 G.VOM.zoom.userFactor+=0.1;
7891 ViewerZoomMax();
7892 }
7893 else {
7894 // zoom out
7895 G.VOM.zoom.userFactor-=0.1;
7896 ViewerZoomMin();
7897 }
7898 ViewerMediaSetPosAndZoom();
7899 }
7900
7901 function ViewerZoomMax() {
7902 if( G.VOM.zoom.userFactor > 3 ) {
7903 G.VOM.zoom.userFactor = 3;
7904 }
7905 }
7906 function ViewerZoomMin() {
7907//console.dir(G.VOM.NGY2Item(0));
7908 // var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
7909 // var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
7910
7911 if( G.VOM.zoom.userFactor < 0.2 ) {
7912 G.VOM.zoom.userFactor = 0.2;
7913 }
7914 }
7915
7916
7917
7918 // Set position and size of all 3 media containers
7919 function ViewerMediaSetPosAndZoom() {
7920
7921 if( !G.VOM.zoom.isZooming ) {
7922 G.VOM.zoom.userFactor = 1;
7923 }
7924 // window.ng_draf( function() {
7925 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, true );
7926 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, false );
7927 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(1), G.VOM.$mediaNext, false );
7928 // });
7929 }
7930
7931
7932
7933 // Media which is not IMG -> center and set size
7934 function ViewerMediaCenterNotImg( $mediaContainer ) {
7935 var $media = $mediaContainer.children().eq(1);
7936 $media.css( {'height': '80%' });
7937 $media.css( {'width': '90%' });
7938 $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
7939 }
7940
7941 // Set position and size of ONE media container
7942 function ViewerMediaSetPosAndZoomOne(item, $img, isCurrent ) {
7943
7944 if( item.mediaKind != 'img' ) {
7945 ViewerMediaCenterNotImg($img);
7946 return;
7947 }
7948
7949 if( item.imageHeight == 0 || item.imageWidth == 0 ) {
7950 ViewerSetMediaVisibility( item, $img, 0 );
7951 return;
7952 }
7953
7954 // part 1: set the image size
7955 var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
7956
7957 var dpr = 1;
7958 if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
7959 dpr = window.devicePixelRatio;
7960 }
7961
7962 // retrieve the base zoom factor (image fill screen)
7963 var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr);
7964 var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
7965 var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
7966 if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
7967 // no upscale
7968 zoomBaseFactor = 1;
7969 }
7970
7971 var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
7972 var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
7973 $img.children().eq(1).css( {'height': imageCurrentHeight });
7974 $img.children().eq(1).css( {'width': imageCurrentWidth });
7975
7976 // retrieve posX/Y to center image
7977 var posX = 0;
7978 if( imageCurrentWidth > G.VOM.window.lastWidth ) {
7979 posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
7980 }
7981 var posY = 0;
7982 if( imageCurrentHeight > G.VOM.window.lastHeight ) {
7983 posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
7984 }
7985 posY = 0; // actually, it seems that the image is always centered vertically -> so no need to to anything
7986
7987 // Part 2: set the X/Y position (for zoom/pan)
7988 if( isCurrent ) {
7989 if( !G.VOM.zoom.isZooming ) {
7990 G.VOM.panPosX = 0;
7991 G.VOM.panPosY = 0;
7992 }
7993 G.VOM.zoom.posX = posX;
7994 G.VOM.zoom.posY = posY;
7995 ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
7996 }
7997 // else {
7998 //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
7999 // }
8000 else {
8001 // set the pan position of each media container
8002 ViewerMediaPanX( G.VOM.swipePosX );
8003 }
8004
8005 }
8006
8007 // position the image depending on the zoom factor and the pan X/Y position
8008 // IMG is the only media supporting zoom/pan
8009 function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
8010 if( savePosition ) {
8011 G.VOM.panPosX = posX;
8012 G.VOM.panPosY = posY;
8013 }
8014
8015 posX += G.VOM.zoom.posX;
8016 posY += G.VOM.zoom.posY;
8017 imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) ';
8018
8019
8020 }
8021
8022
8023 // display media with internal viewer
8024 function OpenInternalViewer( ) {
8025
8026 G.VOM.viewerDisplayed = true;
8027 G.GOM.firstDisplay = false;
8028
8029 G.VOM.saveOverflowX = window.getComputedStyle(document.body)['overflow-x'];
8030 G.VOM.saveOverflowY = window.getComputedStyle(document.body)['overflow-y'];
8031 jQuery('body').css({ overflow: 'hidden' }); //avoid scrollbars
8032
8033 G.VOM.$cont = jQuery('<div class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
8034
8035 SetViewerTheme();
8036
8037 G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$cont );
8038 G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' }); // avoid pinch zoom
8039
8040 G.VOM.currItemIdx = 0;
8041 var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.NGY2Item(-1).mediaMarkup + '</div>'; // previous media
8042 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.NGY2Item(0).mediaMarkup + '</div>'; // current media
8043 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.NGY2Item(1).mediaMarkup + '</div>'; // next media
8044
8045 var sNav = '';
8046 var iconP = G.O.icons.viewerImgPrevious;
8047 if( iconP != undefined && iconP != '') {
8048 sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
8049 }
8050 var iconN = G.O.icons.viewerImgNext;
8051 if( iconN != undefined && iconN != '') {
8052 sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
8053 }
8054
8055 G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
8056
8057 var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
8058 G.VOM.$mediaPrevious = $mediaPan.eq(0); // pointer to previous media container
8059 G.VOM.$mediaCurrent = $mediaPan.eq(1); // pointer to current media container
8060 G.VOM.$mediaNext = $mediaPan.eq(2); // pointer to next media container
8061
8062 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(0) );
8063 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(-1) );
8064 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(1) );
8065
8066 G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
8067 G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
8068
8069 // build media toolbar container
8070 var vtbBg1 = '';
8071 var vtbBg2 = ' toolbarBackground';
8072 if( G.O.viewerToolbar.fullWidth ) {
8073 vtbBg1 = ' toolbarBackground';
8074 vtbBg2 = '';
8075 }
8076 var vtbAlign = 'text-align:center;';
8077 switch ( G.O.viewerToolbar.align ) {
8078 case 'left':
8079 vtbAlign = 'text-align:left;';
8080 break;
8081 case 'right':
8082 vtbAlign = 'text-align:right;';
8083 break;
8084 }
8085 var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
8086 G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
8087
8088 if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
8089 ViewerToolbarForVisibilityMin();
8090 }
8091 else {
8092 ViewerToolbarForVisibilityStd();
8093 }
8094
8095 // top-left toolbar
8096 if( G.O.viewerTools.topLeft != '' ) {
8097 var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
8098 var sTL = G.O.viewerTools.topLeft.split(',');
8099 for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
8100 sTopLeft += ToolbarAddElt( sTL[i] );
8101 }
8102 sTopLeft += '</div></div>';
8103 G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
8104 }
8105 // top-right toolbar
8106 if( G.O.viewerTools.topRight != '' ) {
8107 var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
8108 var sTR = G.O.viewerTools.topRight.split(',');
8109 for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
8110 sTopRight += ToolbarAddElt( sTR[i] );
8111 }
8112 sTopRight += '</div></div>';
8113 G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
8114 }
8115
8116 // set the events handler for toolbars
8117 ViewerToolsOn();
8118
8119 // display logo
8120 if( G.O.viewerDisplayLogo ) { G.$E.vwLogo=jQuery('<div class="nGY2 nGY2ViewerLogo"></div>').appendTo(G.VOM.$viewer); }
8121
8122 // Go to fullscreen mode
8123 if( ngscreenfull.enabled && G.O.viewerFullscreen ) { ngscreenfull.request(); }
8124
8125 setElementOnTop('', G.VOM.$viewer);
8126 ResizeInternalViewer(true);
8127 G.VOM.timeImgChanged = new Date().getTime();
8128
8129 // viewer display transition
8130 var tweenable = new NGTweenable();
8131 tweenable.tween({
8132 from: { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
8133 to: { opacity: 1, posY: 0 },
8134 delay: 30,
8135 duration: 500,
8136 easing: 'easeOutQuart',
8137 step: function (state) {
8138 G.VOM.$viewer.css('opacity', state.opacity);
8139 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8140 }
8141 });
8142
8143 // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
8144 // --> no more supported since v2.0.0
8145 // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
8146
8147
8148 ViewerMediaPanX(0);
8149 ViewerSetEvents();
8150
8151 DisplayInternalViewer(0, '');
8152
8153 if( G.O.slideshowAutoStart ) {
8154 G.VOM.playSlideshow = false;
8155 SlideshowToggle();
8156 }
8157 }
8158
8159 function ViewerEvents() {
8160 if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.NGY2Item(0).mediaKind != 'img') {
8161 // ignore fired event if viewer not displayed or if currently changed or if current media not an image
8162 return false;
8163 }
8164 return true;
8165 }
8166
8167
8168 // viewer gesture handling
8169 function ViewerSetEvents() {
8170
8171 if( G.VOM.hammertime == null ) {
8172
8173 G.VOM.hammertime = new NGHammer.Manager(G.VOM.$cont[0], {
8174 recognizers: [
8175 [NGHammer.Pinch, { enable: true }],
8176 [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
8177 ]
8178 });
8179
8180 // PAN
8181 G.VOM.hammertime.on('pan', function(ev) {
8182 if( !ViewerEvents() ) { return; }
8183
8184 if( G.VOM.zoom.isZooming ) {
8185 // pan zoomed image
8186 ViewerImagePanSetPosition(G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.$mediaCurrent, false);
8187 if( G.VOM.toolbarsDisplayed == true ) {
8188 G.VOM.toolsHide();
8189 }
8190 }
8191 else {
8192 if( ev.deltaY > 50 && Math.abs(ev.deltaX) < 50 ) {
8193 // pan viewer down
8194 ViewerMediaPanX( 0 );
8195 var dist=Math.min(ev.deltaY, 200);
8196 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
8197 G.VOM.$viewer.css('opacity', 1-dist/200/2);
8198 }
8199 else {
8200 // pan media left/right
8201 ViewerMediaPanX( ev.deltaX );
8202 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
8203 G.VOM.$viewer.css('opacity', 1);
8204 }
8205 }
8206 });
8207
8208 // PAN END
8209 G.VOM.hammertime.on('panend', function(ev) {
8210 if( !ViewerEvents() ) { return; }
8211 if( G.VOM.zoom.isZooming ) {
8212 G.VOM.timeImgChanged = new Date().getTime();
8213 ViewerImagePanSetPosition(G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.$mediaCurrent, true);
8214 }
8215 else {
8216 if( ev.deltaY > 50 && Math.abs(ev.deltaX) < 50 ) {
8217 // close viewer
8218 CloseInternalViewer(G.VOM.currItemIdx);
8219 }
8220 else {
8221 if( Math.abs( ev.deltaX ) < 50 ) {
8222 ViewerMediaPanX(0);
8223 }
8224 else {
8225 ev.deltaX > 50 ? DisplayPreviousMedia() : DisplayNextMedia();
8226 }
8227 }
8228 }
8229 });
8230
8231 if( G.O.viewerZoom ) {
8232
8233 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2 }) );
8234 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
8235 G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
8236 G.VOM.hammertime.get('singletap').requireFailure('doubletap');
8237
8238 // single tap -> next/previous media
8239 G.VOM.hammertime.on('singletap', function(ev) {
8240 if( !ViewerEvents() ) { return; }
8241 StopPropagationPreventDefault(ev.srcEvent);
8242 if( G.VOM.toolbarsDisplayed == false ) {
8243 debounce( ViewerToolsUnHide, 100, false)();
8244 }
8245 else {
8246 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
8247 if( ev.srcEvent.pageX < (G.GOM.cache.viewport.w/2) ) {
8248 DisplayPreviousMedia();
8249 }
8250 else {
8251 DisplayNextMedia();
8252 }
8253 }
8254 }
8255 });
8256
8257 // double tap -> zoom
8258 G.VOM.hammertime.on('doubletap', function(ev) {
8259 if( !ViewerEvents() ) { return; }
8260 StopPropagationPreventDefault(ev.srcEvent);
8261
8262 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
8263 // double tap only on image
8264 if( G.VOM.zoom.isZooming ) {
8265 G.VOM.zoom.isZooming = false;
8266 G.VOM.zoom.userFactor = 1;
8267 ResizeInternalViewer(true);
8268 }
8269 else {
8270 if( ViewerZoomStart() ) {
8271 G.VOM.zoom.userFactor = 1.5;
8272 ViewerMediaSetPosAndZoom();
8273 }
8274 }
8275 }
8276 });
8277
8278 // pinch end
8279 G.VOM.hammertime.on('pinchend', function(ev) {
8280 ev.srcEvent.stopPropagation();
8281 ev.srcEvent.preventDefault(); // cancel mouseenter event
8282 G.VOM.timeImgChanged = new Date().getTime();
8283 });
8284 G.VOM.hammertime.on('pinch', function(ev) {
8285 ev.srcEvent.stopPropagation();
8286 ev.srcEvent.preventDefault(); // cancel mouseenter event
8287
8288 if( ViewerZoomStart() ) {
8289 G.VOM.zoom.userFactor = ev.scale;
8290 ViewerZoomMax();
8291 ViewerZoomMin();
8292 ViewerMediaSetPosAndZoom(); // center media
8293 }
8294 });
8295 }
8296 else {
8297 // no zoom -> click/tap on image to go to next/previous one
8298 G.VOM.hammertime.on('tap', function(ev) {
8299 if( !ViewerEvents() ) { return; }
8300 StopPropagationPreventDefault(ev.srcEvent);
8301 if( G.VOM.toolbarsDisplayed == false ){
8302 // display tools on tap if hidden
8303 debounce( ViewerToolsUnHide, 100, false)();
8304 }
8305 else {
8306 // display next/previous image if tools not hidden
8307 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
8308 if( ev.srcEvent.pageX < (G.GOM.cache.viewport.w/2) ) {
8309 DisplayPreviousMedia();
8310 }
8311 else {
8312 DisplayNextMedia();
8313 }
8314 }
8315 }
8316
8317 });
8318 }
8319 }
8320 }
8321
8322 function StopPropagationPreventDefault(e) {
8323 e.stopPropagation();
8324 e.preventDefault();
8325 }
8326
8327 // Hide toolbars on user inactivity
8328 function ViewerToolsHide() {
8329 if( G.VOM.viewerDisplayed ) {
8330 G.VOM.toolbarsDisplayed = false;
8331 ViewerToolsOpacity(0);
8332 }
8333 }
8334
8335 function ViewerToolsUnHide() {
8336 if( G.VOM.viewerDisplayed ) {
8337 G.VOM.toolbarsDisplayed = true;
8338 ViewerToolsOpacity(1);
8339 G.VOM.toolsHide();
8340 }
8341 }
8342
8343 function ViewerToolsOpacity( op ) {
8344 G.VOM.$toolbar.css('opacity', op);
8345 if( G.VOM.$toolbarTL != null ) {
8346 G.VOM.$toolbarTL.css('opacity', op);
8347 }
8348 if( G.VOM.$toolbarTR != null ) {
8349 G.VOM.$toolbarTR.css('opacity', op);
8350 }
8351 G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
8352 G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
8353 }
8354
8355
8356
8357 function ViewerToolsOn() {
8358 // removes all events
8359 G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
8360
8361 // action button
8362 G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
8363 }
8364
8365
8366 // Actions of the buttton/elements
8367 function ViewerToolsAction(e) {
8368 // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
8369 if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
8370 G.timeLastTouchStart = new Date().getTime();
8371
8372 var $this = $(this);
8373 var ngy2action = $this.data('ngy2action');
8374 if( ngy2action == undefined ) { return; }
8375 switch( ngy2action ) {
8376 case 'next':
8377 StopPropagationPreventDefault(e);
8378 DisplayNextMedia();
8379 break;
8380 case 'previous':
8381 StopPropagationPreventDefault(e);
8382 DisplayPreviousMedia();
8383 break;
8384 case 'playPause':
8385 e.stopPropagation();
8386 SlideshowToggle();
8387 break;
8388 case 'zoomIn':
8389 StopPropagationPreventDefault(e);
8390 if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
8391 break;
8392 case 'zoomOut':
8393 StopPropagationPreventDefault(e);
8394 if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
8395 break;
8396 case 'minimize':
8397 // toggle toolbar visibility
8398 StopPropagationPreventDefault(e);
8399 if( G.VOM.toolbarMode == 'std' ) {
8400 ViewerToolbarForVisibilityMin();
8401 }
8402 else {
8403 ViewerToolbarForVisibilityStd();
8404 }
8405 break;
8406 case 'fullScreen':
8407 // Toggle viewer fullscreen mode on/off
8408 e.stopPropagation();
8409 if( ngscreenfull.enabled ) {
8410 ngscreenfull.toggle();
8411 }
8412 break;
8413 case 'info':
8414 e.stopPropagation();
8415 ItemDisplayInfo(G.VOM.NGY2Item(0));
8416 break;
8417 case 'close':
8418 StopPropagationPreventDefault(e);
8419 if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
8420 CloseInternalViewer(G.VOM.currItemIdx);
8421 break;
8422 case 'download':
8423 StopPropagationPreventDefault(e);
8424 DownloadImage(G.VOM.items[G.VOM.currItemIdx].ngy2ItemIdx);
8425 break;
8426 case 'share':
8427 StopPropagationPreventDefault(e);
8428 PopupShare(G.VOM.items[G.VOM.currItemIdx].ngy2ItemIdx);
8429 break;
8430 case 'linkOriginal':
8431 StopPropagationPreventDefault(e);
8432 OpenOriginal( G.VOM.NGY2Item(0) );
8433 break;
8434 }
8435
8436 // custom button
8437 var fu = G.O.fnImgToolbarCustClick;
8438 if( ngy2action.indexOf('custom') == 0 && fu !== null ) {
8439 typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.NGY2Item(0)) : window[fu](ngy2action, $this, G.VOM.NGY2Item(0));
8440 }
8441 }
8442
8443
8444 // Display photo infos
8445 function ItemDisplayInfo( item) {
8446
8447 var content = '<div class="nGY2PopupOneItemText">' + item.title + '</div>';
8448 content += '<div class="nGY2PopupOneItemText">' + item.description + '</div>';
8449 if( item.author != '' ) {
8450 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + item.author + '</div>';
8451 }
8452 if( item.exif.model != '' ) {
8453 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + item.exif.model + '</div>';
8454 }
8455 var sexif = '';
8456 sexif += item.exif.flash == '' ? '' : ' &nbsp; ' + item.exif.flash;
8457 sexif += item.exif.focallength == '' ? '' : ' &nbsp; ' + item.exif.focallength+'mm';
8458 sexif += item.exif.fstop == '' ? '' : ' &nbsp; f' + item.exif.fstop;
8459 sexif += item.exif.exposure == '' ? '' : ' &nbsp; ' + item.exif.exposure+'s';
8460 sexif += item.exif.iso == '' ? '' : ' &nbsp; ' + item.exif.iso+' ISO';
8461 if( item.exif.time != '' ) {
8462 // var date = new Date(parseInt(item.exif.time));
8463 // sexif += ' &nbsp; '+date.toLocaleDateString();
8464 sexif += ' &nbsp; ' + item.exif.time;
8465 }
8466 content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
8467
8468 if( item.exif.location != '' ) {
8469 content += '<div class="nGY2PopupOneItemText">'+G.O.icons.location+' <a href="http://maps.google.com/maps?z=12&t=m&q='+encodeURIComponent(item.exif.location)+'" target="_blank">'+item.exif.location+'</a></div>';
8470 // embed google map in iframe (no api key required)
8471 content += '<iframe width="300" height="150" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?&amp;t=m&amp;q='+encodeURIComponent( item.exif.location ) +'&amp;output=embed"></iframe>';
8472 }
8473
8474 Popup(G.O.icons.viewerInfo, content, 'Left');
8475
8476 }
8477
8478
8479
8480 function ToolbarAddElt( elt ) {
8481 var r = '<div class="ngbt ngy2viewerToolAction ',
8482 e=elt.replace(/^\s+|\s+$/g, ''); // remove trailing/leading whitespace
8483 switch( e ) {
8484 case 'minimizeButton':
8485 var ic = G.O.icons.viewerToolbarMin;
8486 if( G.VOM.toolbarMode == 'min' ) {
8487 ic = G.O.icons.viewerToolbarStd;
8488 }
8489 r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
8490 break;
8491 case 'previousButton':
8492 r += 'previousButton nGEvent" data-ngy2action="previous">'+G.O.icons.viewerPrevious+'</div>';
8493 break;
8494 case 'pageCounter':
8495 r += 'pageCounter nGEvent"></div>';
8496 break;
8497 case 'nextButton':
8498 r += 'nextButton nGEvent" data-ngy2action="next">'+G.O.icons.viewerNext+'</div>';
8499 break;
8500 case 'playPauseButton':
8501 r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+G.O.icons.viewerPlay+'</div>';
8502 break;
8503 case 'downloadButton':
8504 r += 'downloadButton nGEvent" data-ngy2action="download">'+G.O.icons.viewerDownload+'</div>';
8505 break;
8506 case 'zoomButton':
8507 r += 'nGEvent" data-ngy2action="zoomIn">'+G.O.icons.viewerZoomIn+'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+G.O.icons.viewerZoomOut+'</div>';
8508 break;
8509 case 'fullscreenButton':
8510 var s = G.O.icons.viewerFullscreenOn;
8511 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
8512 s = G.O.icons.viewerFullscreenOff;
8513 }
8514 r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
8515 break;
8516 case 'infoButton':
8517 r += 'infoButton nGEvent" data-ngy2action="info">'+G.O.icons.viewerInfo+'</div>';
8518 break;
8519 case 'linkOriginalButton':
8520 r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
8521 break;
8522 case 'closeButton':
8523 r += 'closeButton nGEvent" data-ngy2action="close">'+G.O.icons.buttonClose+'</div>';
8524 break;
8525 case 'shareButton':
8526 r += 'nGEvent" data-ngy2action="share">'+G.O.icons.viewerShare+'</div>';
8527 break;
8528 case 'label':
8529 r += '"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div>';
8530 break;
8531 default:
8532 // custom button
8533 if( e.indexOf('custom') == 0 ) {
8534 var t = '';
8535 // content to display from custom script
8536 var fu = G.O.fnImgToolbarCustInit;
8537 if( fu !== null ) {
8538 typeof fu == 'function' ? fu(e) : window[fu](e);
8539 }
8540 if( t == undefined || t == '' ) {
8541 // content from icons
8542 var n = e.substring(6);
8543 t = G.O.icons['viewerCustomTool'+n];
8544 }
8545 r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
8546 }
8547 else {
8548 r = '';
8549 }
8550 break;
8551 }
8552 return r;
8553 }
8554
8555
8556 // toggle slideshow mode on/off
8557 function SlideshowToggle(){
8558 if( G.VOM.playSlideshow ) {
8559 window.clearTimeout(G.VOM.playSlideshowTimerID);
8560 G.VOM.playSlideshow = false;
8561 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
8562 }
8563 else {
8564 G.VOM.playSlideshow = true;
8565 DisplayNextMedia();
8566 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
8567 }
8568 }
8569
8570 function ViewerToolbarForVisibilityStd() {
8571 G.VOM.toolbarMode = 'std';
8572
8573 var sTB = '';
8574 var t = G.O.viewerToolbar.standard.split(',');
8575 for( var i = 0, lt = t.length; i < lt; i++) {
8576 sTB += ToolbarAddElt( t[i] );
8577 }
8578 G.VOM.$toolbar.find('.toolbar').html(sTB);
8579 ViewerToolbarElementContent();
8580 }
8581
8582 function ViewerToolbarForVisibilityMin() {
8583 if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
8584 ViewerToolbarForVisibilityStd();
8585 }
8586 else {
8587 G.VOM.toolbarMode = 'min';
8588 var sTB = '';
8589 var t = G.O.viewerToolbar.minimized.split(',');
8590 for( var i = 0, lt = t.length; i < lt; i++) {
8591 sTB += ToolbarAddElt( t[i] );
8592 }
8593 G.VOM.$toolbar.find('.toolbar').html(sTB);
8594 ViewerToolbarElementContent();
8595 }
8596 }
8597
8598 function ViewerToolbarElementContent() {
8599
8600 var vomIdx=G.VOM.currItemIdx;
8601 if( vomIdx == null ) { return; }
8602
8603 var item=G.VOM.NGY2Item(0);
8604
8605 // LABEL
8606 var setTxt = false;
8607 // set title
8608 if( item.title !== undefined && item.title != '' ) {
8609 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
8610 setTxt = true;
8611 }
8612 else {
8613 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
8614 }
8615 // set description
8616 if( item.description !== undefined && item.description != '' ) {
8617 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
8618 setTxt = true;
8619 }
8620 else {
8621 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
8622 }
8623
8624 if( setTxt ) {
8625 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
8626 }
8627 else {
8628 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
8629 }
8630
8631 // set page number
8632 var viewerMaxImages = G.VOM.items.length;
8633 if( viewerMaxImages > 0 ) {
8634 G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
8635 }
8636
8637 // custom elements
8638 var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
8639 var fu = G.O.fnImgToolbarCustDisplay;
8640 if( $cu.length > 0 && fu !== null ) {
8641 typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
8642 }
8643
8644 // set event handlers again
8645 ViewerToolsOn();
8646 }
8647
8648 // Pan the media in the lightbox (left/right)
8649 function ViewerMediaPanX( posX ) {
8650 G.VOM.swipePosX = posX;
8651 if( G.CSStransformName == null ) {
8652 // no pan if CSS transform not supported
8653 // G.VOM.$mediaCurrent.css({ left: posX });
8654 }
8655 else {
8656
8657 // pan left/right the current media
8658 // window.ng_draf( function() {
8659 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
8660 // });
8661
8662
8663 // next/previous media
8664 if( G.O.imageTransition.startsWith('SWIPE') ) {
8665 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8666 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 1);
8667 }
8668 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8669 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 1);
8670 }
8671
8672 var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
8673 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
8674
8675 if( posX > 0 ) {
8676 var dir = G.VOM.window.lastWidth;
8677 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8678 // window.ng_draf( function() {
8679 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
8680 // });
8681 }
8682 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8683 // window.ng_draf( function() {
8684 G.VOM.$mediaNext[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
8685 // });
8686 }
8687 }
8688 else {
8689 var dir = -G.VOM.window.lastWidth;
8690 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8691 // window.ng_draf( function() {
8692 G.VOM.$mediaNext[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
8693 // });
8694 }
8695 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8696 // window.ng_draf( function() {
8697 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
8698 // });
8699 }
8700 }
8701 }
8702
8703
8704 if( G.O.imageTransition == 'SLIDEAPPEAR' ) {
8705 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = '';
8706 G.VOM.$mediaNext[0].style[G.CSStransformName] = '';
8707 if( posX < 0 ) {
8708 var o = (-posX) / G.VOM.window.lastWidth;
8709 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8710 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, o);
8711 }
8712 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8713 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
8714 }
8715 }
8716 else {
8717 var o = posX / G.VOM.window.lastWidth;
8718 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8719 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, o);
8720 }
8721 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8722 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
8723 }
8724 }
8725 }
8726 }
8727 }
8728
8729 // Display next image
8730 function DisplayNextMedia() {
8731 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
8732
8733 TriggerCustomEvent('lightboxNextImage');
8734 DisplayInternalViewer(G.VOM.IdxNext(), 'nextImage');
8735 };
8736
8737 // Display previous image
8738 function DisplayPreviousMedia() {
8739 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
8740 if( G.VOM.playSlideshow ) {
8741 SlideshowToggle();
8742 }
8743
8744 TriggerCustomEvent('lightboxPreviousImage');
8745 DisplayInternalViewer(G.VOM.IdxPrevious(), 'previousImage');
8746 };
8747
8748 // Display image (and run animation)
8749 function DisplayInternalViewer( newVomIdx, displayType ) {
8750
8751 if( G.VOM.playSlideshow ) { window.clearTimeout(G.VOM.playSlideshowTimerID); }
8752
8753 var itemOld = G.VOM.NGY2Item(0);
8754 var itemNew = G.I[G.VOM.items[newVomIdx].ngy2ItemIdx];
8755 var $new = (displayType == 'nextImage' ? G.VOM.$mediaNext : G.VOM.$mediaPrevious);
8756 if( displayType == 'nextImage' ) {
8757 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
8758 }
8759 else {
8760 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
8761 }
8762
8763 G.VOM.timeImgChanged = new Date().getTime();
8764 G.VOM.viewerMediaIsChanged = true;
8765 G.VOM.zoom.isZooming = false;
8766 ResizeInternalViewer(true);
8767
8768 if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
8769
8770 SetLocationHash( itemNew.albumID, itemNew.GetID() );
8771
8772 // animation duration is proportional of the remaining distance
8773 var vP = G.GOM.cache.viewport;
8774 var dur = 400 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
8775
8776 if( displayType == '' ) {
8777 // first image --> just appear / no slide animation
8778 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8779 if( G.CSStransformName == null ) {
8780 // no CSS transform support -> no animation
8781 ViewerSetMediaVisibility(itemNew, $new, 1);
8782 DisplayInternalViewerComplete(displayType, newVomIdx);
8783 }
8784 else {
8785 ViewerSetMediaVisibility(itemNew, $new, 0);
8786 new NGTweenable().tween({
8787 from: { opacity: 0 },
8788 to: { opacity: 1 },
8789 attachment: { dT: displayType, item: itemOld },
8790 easing: 'easeInOutSine',
8791 delay: 30,
8792 duration: 400,
8793 step: function (state, att) {
8794 // using scale is not a good idea on Chrome -> image will be blurred
8795 G.VOM.$content.css('opacity', state.opacity);
8796 },
8797 finish: function (state, att) {
8798 ViewerToolsUnHide();
8799 DisplayInternalViewerComplete(att.dT, newVomIdx);
8800 }
8801 });
8802 }
8803 }
8804 else {
8805 // animate the image transition between 2 images
8806 if( G.CSStransformName == null ) {
8807 // no CSS transform support -> no animation
8808 ViewerSetMediaVisibility(itemNew, $new, 1);
8809 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8810 DisplayInternalViewerComplete(displayType, newVomIdx);
8811 }
8812 else {
8813 switch( G.O.imageTransition ) {
8814 case 'SWIPE':
8815 case 'SWIPE2':
8816 var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
8817 $new[0].style[G.CSStransformName] = 'translate('+(-dir)+'px, 0px) '
8818
8819 new NGTweenable().tween({
8820 from: { t: G.VOM.swipePosX },
8821 to: { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
8822 attachment: { dT: displayType, $e: $new, itemNew: itemNew, dir: dir },
8823 delay: 30,
8824 duration: dur,
8825 easing: (G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeInOutCubic'),
8826 step: function (state, att) {
8827 // current media
8828 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8829 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
8830 // new media
8831 if( att.itemNew.mediaTransition() ) {
8832 ViewerSetMediaVisibility(att.itemNew, att.$e, 1);
8833
8834 var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
8835 if( G.O.imageTransition == 'swipe' ) { sc = 1; }
8836 att.$e[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
8837 }
8838 },
8839 finish: function (state, att) {
8840 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = '';
8841 att.$e[0].style[G.CSStransformName] = '';
8842 DisplayInternalViewerComplete(att.dT, newVomIdx);
8843 }
8844 });
8845 break;
8846
8847 case 'SLIDEAPPEAR':
8848 default:
8849 var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
8850 var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
8851 $new[0].style[G.CSStransformName] = '';
8852 new NGTweenable().tween({
8853 from: { o: op, t: G.VOM.swipePosX },
8854 to: { o: 1, t: (displayType == 'nextImage' ? - vP.w : vP.w) },
8855 attachment: { dT:displayType, $e:$new, itemNew: itemNew, dir: dir },
8856 delay: 30,
8857 duration: dur,
8858 easing: 'easeInOutSine',
8859 step: function (state, att) {
8860 // current media - translate
8861 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
8862 // new media - opacity
8863 if( att.itemNew.mediaTransition() ) {
8864 ViewerSetMediaVisibility(att.itemNew, att.$e, state.o);
8865 }
8866 },
8867 finish: function (state, att) {
8868 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= '';
8869 DisplayInternalViewerComplete(att.dT, newVomIdx);
8870 }
8871 });
8872 break;
8873 }
8874 }
8875 }
8876 }
8877
8878
8879 function DisplayInternalViewerComplete( displayType, newVomIdx ) {
8880 G.VOM.currItemIdx = newVomIdx;
8881
8882 var ngy2item = G.VOM.NGY2Item(0);
8883
8884 ViewerToolbarElementContent();
8885 if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
8886
8887 var fu=G.O.fnImgDisplayed;
8888 if( fu !== null ) {
8889 typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
8890 }
8891
8892 G.VOM.swipePosX = 0;
8893
8894 if( displayType != '' ) {
8895 // not on first media display
8896 // G.VOM.$mediaCurrent.off("click");
8897 G.VOM.$mediaCurrent.removeClass('imgCurrent');
8898
8899 var $tmp = G.VOM.$mediaCurrent;
8900 switch( displayType ) {
8901 case 'nextImage':
8902 G.VOM.$mediaCurrent = G.VOM.$mediaNext;
8903 G.VOM.$mediaNext = $tmp;
8904 break;
8905 case 'previousImage':
8906 G.VOM.$mediaCurrent = G.VOM.$mediaPrevious;
8907 G.VOM.$mediaPrevious = $tmp;
8908 break;
8909 }
8910 G.VOM.$mediaCurrent.addClass('imgCurrent');
8911
8912 // re-sort the media containers --> current on top
8913 var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
8914 G.VOM.$mediaCurrent.insertAfter($pans.last());
8915
8916 if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
8917 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 0);
8918 }
8919 else {
8920 G.VOM.$mediaCurrent.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
8921 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8922 }
8923 }
8924
8925 // set the new NEXT media
8926 G.VOM.$mediaNext.empty();
8927 var nextItem = G.VOM.NGY2Item(1);
8928 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8929 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
8930 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8931 }
8932 G.VOM.$mediaNext.append( spreloader + nextItem.mediaMarkup );
8933 ViewerSetMediaVisibility(nextItem, G.VOM.$mediaNext, 0);
8934 if( nextItem.mediaKind == 'img' ) {
8935 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
8936 }
8937 else {
8938 ViewerMediaCenterNotImg( G.VOM.$mediaNext );
8939 }
8940
8941 // set the new PREVIOUS media
8942 G.VOM.$mediaPrevious.empty();
8943 var previousItem = G.VOM.NGY2Item(-1);
8944 spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8945 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
8946 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8947 }
8948 G.VOM.$mediaPrevious.append( spreloader + previousItem.mediaMarkup );
8949 ViewerSetMediaVisibility(previousItem, G.VOM.$mediaPrevious, 0);
8950 if( previousItem.mediaKind == 'img' ) {
8951 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
8952 }
8953 else {
8954 ViewerMediaCenterNotImg( G.VOM.$mediaPrevious );
8955 }
8956
8957
8958 // slideshow mode - wait until image is loaded to start the delay for next image
8959 if( G.VOM.playSlideshow ) {
8960 G.VOM.$mediaCurrent.children().eq(1).ngimagesLoaded().always( function( instance ) {
8961 if( G.VOM.playSlideshow ) {
8962 // in the meantime the user could have stopped the slideshow
8963 G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
8964 }
8965 });
8966 }
8967
8968 // close viewer when user clicks outside of the image
8969 // G.VOM.$mediaCurrent.on("click",function(e){
8970 // e.stopPropagation();
8971 // if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
8972 // StopPropagationPreventDefault(e);
8973 // CloseInternalViewer(G.VOM.currItemIdx);
8974 // return false;
8975 // });
8976
8977 ResizeInternalViewer();
8978
8979 G.VOM.viewerMediaIsChanged = false;
8980 TriggerCustomEvent('lightboxImageDisplayed');
8981
8982 }
8983
8984
8985 // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
8986 function VieweImgSizeRetrieved(w, h, item, n) {
8987 item.imageWidth = w;
8988 item.imageHeight = h;
8989
8990 // image sized retrieved for currently displayed media
8991 // if( G.VOM.$mediaCurrent !== null && G.VOM.$mediaCurrent.children().attr('src') == item.responsiveURL() ) {
8992 if( G.VOM.NGY2Item(0) == item ) {
8993 // it is the current displayed media
8994 G.VOM.$mediaCurrent.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
8995 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8996 G.VOM.zoom.userFactor = 1;
8997 }
8998
8999 if( G.VOM.NGY2Item(1) == item ) { // next media
9000 G.VOM.$mediaNext.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9001 }
9002 if( G.VOM.NGY2Item(-1) == item ) { // previous media
9003 G.VOM.$mediaPrevious.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9004 }
9005
9006 ViewerMediaSetPosAndZoom();
9007
9008 }
9009
9010 // Viewer - Set the visibility of the media and it's container
9011 function ViewerSetMediaVisibility(item, $media, opacity ) {
9012
9013 if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
9014 // do not display image if width is unknown (--> callback will set the width when know)
9015 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9016 // $media.children().css({ opacity: 0, visibility: 'hidden' });
9017 $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' }); // hide media
9018 // $media.css({ opacity: 0, visibility: 'hidden' });
9019 return;
9020 }
9021
9022 if( opacity == 0 ) {
9023 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9024 // $media.css({ opacity: 0, visibility: 'hidden' });
9025 $media.children().css({ opacity: 0, visibility: 'hidden' }); // hide media
9026 }
9027 else {
9028 // $media.css({ opacity: opacity, visibility: 'visible' });
9029 $media.children().css({ opacity: opacity, visibility: 'visible' }); // display media
9030 }
9031 }
9032
9033
9034 // Close the internal lightbox
9035 function CloseInternalViewer( vomIdx ) {
9036
9037 G.VOM.viewerMediaIsChanged = false;
9038
9039 if( G.VOM.viewerDisplayed ) {
9040
9041 // set scrollbar visible again
9042 jQuery('body').css({ overflowX: G.VOM.saveOverflowX, overflowY: G.VOM.saveOverflowY});
9043 // jQuery('body').css({overflow: 'visible'});
9044
9045
9046 if( G.VOM.playSlideshow ) {
9047 window.clearTimeout(G.VOM.playSlideshowTimerID);
9048 G.VOM.playSlideshow = false;
9049 }
9050
9051 // G.VOM.userEvents.removeEventListeners();
9052 // G.VOM.userEvents=null;
9053 G.VOM.hammertime.destroy();
9054 G.VOM.hammertime = null;
9055
9056 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
9057 G.VOM.viewerIsFullscreen = false;
9058 ngscreenfull.exit();
9059 }
9060
9061 G.VOM.$cont.hide(0).off().show(0).html('').remove();
9062 G.VOM.viewerDisplayed = false;
9063
9064 if( G.O.thumbnailAlbumDisplayImage ) {
9065 // content of album displayed directly in lightbox (no gallery display for album content)
9066 if( vomIdx == null ) {
9067 // lightbox closed with browser back-button
9068 // the gallery is already displayed
9069 }
9070 else {
9071 var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
9072 var parent = NGY2Item.Get(G, item.albumID);
9073 if( G.GOM.albumIdx != parent.albumID ) {
9074 // display album only if not already displayed
9075 DisplayAlbum('-1', parent.albumID);
9076 }
9077 else {
9078 GalleryResize();
9079 SetLocationHash( '', '' );
9080 ThumbnailHoverReInitAll();
9081 }
9082 }
9083 // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
9084 }
9085 else {
9086 if( vomIdx != null ) {
9087 if( G.GOM.albumIdx == -1 ) {
9088 // album not displayed --> display gallery
9089 DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
9090 }
9091 else {
9092 GalleryResize();
9093 SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
9094 ThumbnailHoverReInitAll();
9095 }
9096 }
9097 }
9098 G.VOM.timeImgChanged = new Date().getTime();
9099 }
9100 }
9101
9102
9103 // Internal viewer resized -> reposition elements
9104 function ResizeInternalViewer( forceUpdate ) {
9105 forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
9106
9107 if( G.VOM.$toolbar === null ) { return; } // viewer build not finished
9108
9109
9110 // window.requestAnimationFrame( function() { // synchronize with screen
9111 var windowsW = G.VOM.$viewer.width();
9112 var windowsH = G.VOM.$viewer.height();
9113 var $elt = G.VOM.$mediaCurrent.children().eq(1);
9114 if( $elt == null || G.VOM.currItemIdx == -1 ) { return; }
9115
9116 if( !forceUpdate && G.VOM.window.lastWidth == windowsW && G.VOM.window.lastHeight == windowsH ) { return; }
9117
9118 G.VOM.window.lastWidth = windowsW;
9119 G.VOM.window.lastHeight = windowsH;
9120
9121 // var vwImgC_H=$elt.height(),
9122 // vwImgC_W=$elt.width(),
9123 // vwImgC_OHt=$elt.outerHeight(true),
9124 // vwImgC_OHf=$elt.outerHeight(false);
9125
9126 var $tb = G.VOM.$toolbar.find('.toolbar');
9127 var tb_OHt = $tb.outerHeight(true);
9128
9129 switch( G.O.viewerToolbar.position ) {
9130 case 'topOverImage':
9131 G.VOM.$content.css({height: windowsH, width: windowsW, top: 0 });
9132 G.VOM.$toolbar.css({top: 0, bottom: ''});
9133 break;
9134 case 'top':
9135 windowsH -= tb_OHt;
9136 G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt });
9137 G.VOM.$toolbar.css({top: 0});
9138 break;
9139 case 'bottomOverImage':
9140 G.VOM.$content.css({height:windowsH, width: windowsW, bottom: 0, top: 0 });
9141 G.VOM.$toolbar.css({bottom: 0});
9142 break;
9143 case 'bottom':
9144 default:
9145 windowsH -= tb_OHt;
9146 G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
9147 G.VOM.$toolbar.css({bottom: 0});
9148 break;
9149 }
9150
9151 if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
9152 ViewerMediaSetPosAndZoom();
9153 }
9154 else {
9155 G.VOM.zoom.userFactor = 1;
9156 G.VOM.zoom.isZooming = false;
9157 G.VOM.panPosX = 0;
9158 G.VOM.panPosY = 0;
9159 G.VOM.zoom.posX = 0;
9160 G.VOM.zoom.posY = 0;
9161 // G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate3D(0,0,0) ';
9162 // if( G.VOM.NGY2Item(0).mediaKind == 'img' ) {
9163 // G.VOM.$mediaCurrent[0].style[G.CSStransformName] = '';
9164 // }
9165 ViewerMediaSetPosAndZoom();
9166 }
9167 }
9168
9169
9170
9171 /** @function BuildSkeleton */
9172 /** Build the gallery structure **/
9173 function BuildSkeleton() {
9174
9175 // store markup if defined
9176 var $elements = G.$E.base.children('a');
9177 if( $elements.length > 0 ) {
9178 G.O.$markup = $elements;
9179 }
9180 G.$E.base.text('');
9181 G.$E.base.addClass('ngy2_container');
9182
9183 // RTL or LTR
9184 var sRTL='';
9185 if( G.O.RTL ) {
9186 sRTL = 'style="text-align:right;direction:rtl;"';
9187 }
9188
9189 // theme
9190 G.$E.base.addClass(G.O.theme)
9191 // gallery color scheme
9192 SetGalleryTheme();
9193
9194 // Hide icons (thumbnails and breadcrumb)
9195 if( G.O.thumbnailLabel.get('hideIcons') ) {
9196 G.O.icons.thumbnailAlbum = '';
9197 G.O.icons.thumbnailImage = '';
9198 }
9199
9200 // Navigation bar
9201 var styleNavigation="";
9202 if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
9203 styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
9204 }
9205 G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
9206
9207 // pre-loader
9208 G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
9209
9210 // gallery
9211 G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
9212 G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
9213
9214 // configure gallery
9215 switch( G.O.thumbnailAlignment ) {
9216 case 'left':
9217 G.$E.conTnParent.css({'text-align':'left'});
9218 // G.$E.conNavBCon.css({'margin-left':0 });
9219 break;
9220 case 'right':
9221 G.$E.conTnParent.css({'text-align':'right'});
9222 // G.$E.conNavBCon.css({ 'margin-right':0});
9223 break;
9224 }
9225
9226 // apply galleryBuildInit2 css settings to the gallery
9227 if( G.O.galleryBuildInit2 !== undefined ) {
9228 var t1=G.O.galleryBuildInit2.split('|');
9229 for( var i=0; i<t1.length; i++ ) {
9230 var o1=t1[i].split('_');
9231 if( o1.length == 2 ) {
9232 G.$E.conTn.css(o1[0], o1[1]);
9233 }
9234 }
9235 }
9236
9237 // configure gallery depending on some thumbnail hover effects
9238 var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
9239 for( var j=0; j<effects.length; j++) {
9240 switch( effects[j].type ) {
9241 case 'scale':
9242 case 'rotateZ':
9243 case 'rotateX':
9244 case 'rotateY':
9245 case 'translateX':
9246 case 'translateY':
9247 // handle some special cases
9248 if( effects[j].element == '.nGY2GThumbnail' ) {
9249 // allow thumbnail upscale over the gallery's aera
9250 G.$E.base.css('overflow', 'visible');
9251 G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
9252 G.$E.conTnParent.css('overflow', 'visible');
9253 }
9254 break;
9255 }
9256 }
9257
9258 // Gallery bottom container
9259 G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
9260
9261 // portable edition
9262 if( G.O.portable ) {
9263 // http://www.picresize.com/
9264 // http://base64encode.net/base64-image-encoder
9265 // var logo='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAWCAYAAAA4oUfxAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QMPBwY6mxZgsAAABTFJREFUSMe1ll9oVGcaxn/fd86ZSWbSkEBMiWNdTTfRxiVbXFiU1bjKGqNexlURKys0tHqXpQZ64Sq4FxKqFy4qFSm9kA1FHNhFISgJqFCd6lL/YC7M3jhrJv5JmGSSMzPnzDnfuxdpZtP4b1vaF154P3gPD+/zPC/nVSKiAQOsBj7O5XK/nZiYeEtELH6iUEqFNTU1U9XV1d8AnwNfA1qJCMCfHz169NcjR45UXL16VWWzWQnD0PxU4JZl6draWtXW1iYHDx4sLlmy5C/AZwRB0JVOpyWRSHhACMjPmOHChQuL6XRagiDoUiIyumvXrpq+vr6obduqs7OTjRvbsbSFUgqUgKjyFG5+mlKpVH6LCMYYRAQRQSmF1hqtNd+xijGGVCpFMpkkCALZuXOn19fXN6Gmp6dNc3NzMDo66nR2dnL+/Hm+Ov933PwUAPHKagqei4gBFNs7dxGPx38U/du2bSOZTNLQ0FB6+PChbWez2WI+n3dEhI3tf+Det0N8de0Imz9YQWHa48u/3afjgxbqEpUM/es/uF8W+fijffi+TywWQ0S4fv06t2/fJpfLsXjxYtauXUtTUxNBECAihGFIJBJh1apVXLhwgXw+r7LZbNGeYU7MLD1BEPCLxkWs+HUT+SmPJY0TvPerd6l/J05YcLCGHWzbxrZtHjx4wP79+7l27dr3Jqyurqarq4ujR49i2zYAWmvCMJyVygCiZ7dh9kOtNb5XopD3KBQ8fL9EseBRyHsUCz6zS3Dnzh3WrVtXBq6oqGDBggUA5HI5jh07xo4dOzDmf0ujVBlGAWjmhTGC41hEow6RiI3j2DgRh0jUxonYWJaFGGHPnj2Mj49jWRYHDhzg7t27DA0NMTAwwOrVqwFIJpOcOHECx3Fe6oEXwG3bYux5ltHHz3mSGePpk+c8yczUI+knVFVVcePmDe7fvw9AT08Pvb29NDc3U1dXx4YNG7h8+TItLS1orTl58iT5fL68Ga8En55yWb6iifff/iPD/0iQGfglG3/zJ6a+beHf/3yH6Mjv+P269Vy5cgWlFDU1NXR3dxOGYdlcnudRVVXFvn37MMaQTqcZHh5+Kbg99zHjSodPuj997cqMjY0hItTW1hKPx9FalzW1LIswDFm0aBEAQRDguu6bJ581hOd5GBNiTEgYhuXa8z1EhIaGBgAymQzpdBqlFKVSiTCc6bcsi5s3bwJQWVlJfX39fMO9XHMAy7LQeibn1o7toJSio6MDAN/36e7uxvd9IpEIlmURjUZJpVKcOXMGpRStra0sXbr0peDfo30+LS+4U2uMMaxcuZLdu3dz7tw5+vv7aWtrY+/evdTX13Pr1i1OnTrF5OQkAIcPH8ayrNeCvx51njTGGE6fPk0mk2FwcJBUKkUqlXqh9/jx42zatKnMzJzhBEArpZT+zjGWZSEiBEHwypzVtbKykosXL3Lo0CEaGxvLpovFYqxZs4ZLly6VJQnDEBEpM6C11kopheu6JpFI+Fpr2bJli/zYGBkZkeHhYZmcnHxlz9atW0VrLYlEwndd19ixWOzx5s2b3z579qzp7+/X7e3ttLa2Yox5QaP5MfenEY1G0VoTBAHFYhFjTJlJrTX37t1jYGAAY4zp6OiQWCz2mCAItj979kyWL1/uAwE/7zERLFu2zH/69KkEQbB99ozaOz4+fqy3t7d2cHAwdF1XKaXe6P7/16AiQjwel/Xr1+uenp6Jurq6T4Av1JwD8j3gQ2BVsVh8S72J8x8QIiIVFRVTQAo4CwwB+r93qCLI9wKZ8AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wMy0xNVQwNzowNjo1OC0wNDowMBNQsyUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDMtMTVUMDc6MDY6NTgtMDQ6MDBiDQuZAAAAAElFTkSuQmCC';
9266 var logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QgDBCAWVVC/hwAABRxJREFUSMetll9oVFcexz/nnDvJRBmSzWTrmD9uNGZsHta0/qFIFQTxRcnCBgTFNlX0YR8W+1AK9lGwCBJYgn0KKr5136S4gpUQTR4caJRslcxYWV3iaphQapJJppO5957z60Mmk4mN1q75wg/OPefc+/v9vt/fueenKEFEqICqsNWAVNiCA7XwaS0iZeejo6OIiCltdIBdJXMLOYp5/PjxsoTVS5nr0mYDJIE/lObeBhaYAn4oJbboAwBvBedHJicnPx8YGGh/8eJF1dvKoJSShoYGf//+/Zl4PP4l8M2yIEoSLErx6c2bN6W1tXVRglWzLVu2SCqVEhE5LiI457SIoEREW2udMaZtcnLy+2QyWZ3L5XRHR4f+4MNdoBUahUJhcWilmZ/NE4ZhOQHn3LIi1lqjtS6vjY6O8uTJE9vc3MyDBw+mYrHYn0Uk63me8gCtlHLA7uHh4bW5XC7oePddPTQ8xHffDjM/PYe3thqMws35iAcHPj5ENBp9Yxmy2Sw7d+40z549C+7du9ewb9++D6y13wDaK+kE0DAzMyNKKbXtvfd5EfzM+Ef/4C+8x23+wzPm+IhtfMf3/Ksuyl+7u9FaY63l+vXrpFIpCoUCmzdvpquri9bWVoIgQClFIpFg48aNPH/+XE9NTQkQLTGmvEXKRERprZWIEIYhQRjQbN6hmUb+tCaPNnM055v40f3If7XBGMPT8af0fNLD0NDQsozPnDlDb28vx44dIwxDRARrLSKCKmUbiUQQkWWnoLJ20UpjFYAjVA6rBJTFV5ZIJIIfBBw4eICxsTHq6uo4dOgQ8XicgYEB7t69y/Hjx4nH43R1dVHB8q+w4hlXSmGd5edwmjCco5DLkZ+aJvTnyIdTrFmzhn9+/TVjY2M0NTVx+/Zt+vv7OXfuHKlUip6eHgBOnz6N7/vlYl0JKzIw78/T+sdGbn6yjf5ZS2HtJgIP+mcC5kySI1uSXPjqAlprTp06RWdnJ8ViEaUUVVVVnD9/nqtXr5LJZHj48CFbt279fQEEYUisZi2fXel9bWU750gmkwRBgNYaz/Ow1lJfX088Hmd2dpZcLvdaBl4pgQChH4B1iHU4a8E6Qj9ARGhpaUFrzeDgIJFIBGMM1lqMMWQyGSYmJohEIqxfv/7314CIoADtGTAaZTTaLI2VUhw+fBjnHBcvXuTy5cs45/A8j3Q6zcmTJ/F9n71799LW1rbgSOs3D+B1lBljcM7R3d3N0aNHKRQKnDhxgs7OTnbt2sX27dsZGRkhHo/T19e3+Kt/fQ1YawFwzolSCs/zUEqVtX1VcJcuXSKRSNDf3086nS6v79mzh76+Pjo6OigWi1RXV2OMWZC29PL8/PxSAL7vE41Gf4rFYkpEePToEb7vU1VVxW+ht7eXs2fPcv/+fQqFAps2baKlpaW8Xl1dTS6XY3x8HBFxtbW1BiiW4hAlInp8fNxt2LChPZvN/ru9vT2Sz+e93bt3qx07diwrzJWYcM5RU1NDNBots5bP53HOlS+kO3fuMDIy4hKJhKTT6ena2tqtxWJxoqamRr98HX9x7do1qaurExYaiXCVzK5bt04GBwdFRP728nVcWZAO+Hsmk/nsxo0bTTMzM5FXHZ83hYhQX1/vHzx48H9tbW1ngSsVvpYCmJ2dJRaLKRbapjpgOxB7K+9LmAbuAnOAnpiYcI2NjUsRLlo2myUMQ1M5t5rmnDO3bt1aNlfmd4W2XL/0/H8pUDF2rNCW/wLRuCkxx8V6wgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wOC0wM1QwNDozMjoyMi0wNDowMO7mdkwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDgtMDNUMDQ6MzI6MjItMDQ6MDCfu87wAAAAAElFTkSuQmCC';
9267 var st = "font-weight:bold !important;color: #FF0075 !important;font-size: 14px !important;text-transform: lowercase !important;cursor:pointer !important;text-align: center !important;Text-Shadow: #000000 1px 0px 0px, #000000 1px 1px 0px, #000000 1px -1px 0px, #000000 -1px 1px 0px, #000000 -1px 0px 0px, #000000 -1px -1px 0px, #000000 0px 1px 0px, #000000 0px -1px 0px !important;";
9268 G.$E.ngy2i=jQuery('<div class="nGY2PortInfo"><a href="http://nano.gallery" target="_blank" title="nanogallery2 | easy photo gallery for your website" style="' + st + '"><img src="' + logo + '" style="height:32px !important;width:initial !important;box-shadow: none !important;vertical-align: middle !important;"/> &nbsp; nanogallery2</a></div>').appendTo(G.$E.base);
9269
9270 G.$E.ngy2i.find('a').on({
9271 mouseenter: function () {
9272 jQuery(this).attr('style', st);
9273 },
9274 mouseleave: function () {
9275 jQuery(this).attr('style', st);
9276 }
9277 });
9278 }
9279
9280 // Error console
9281 G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
9282
9283 // i18n translations
9284 i18n();
9285
9286 // cache some thumbnails data (sizes, styles...)
9287 ThumbnailDefCaches();
9288
9289 // do special settings depending for some options
9290 // thumbnail display transition
9291 switch( G.tn.opt.Get('displayTransition') ) {
9292 case 'SCALEDOWN':
9293 case 'RANDOMSCALE':
9294 default:
9295 G.$E.base.css('overflow', 'visible');
9296 G.$E.conTnParent.css('overflow', 'visible');
9297 G.$E.conTn.css('overflow', 'visible');
9298 break;
9299 }
9300
9301 }
9302
9303 function TriggerCustomEvent ( eventName ) {
9304 // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
9305 var eN = eventName + '.nanogallery2';
9306 var event=null;
9307 try {
9308 event = new Event( eN );
9309 } catch(e) {
9310 event = document.createEvent('Event');
9311 event.initEvent(eN, false, false);
9312 }
9313 G.$E.base.trigger(eN, event);
9314 }
9315
9316
9317 /** @function SetGlobalEvents */
9318 function SetGlobalEvents() {
9319 // GLOBAL EVENT MANAGEMENT
9320
9321 G.$E.conTnParent.on({
9322 mouseenter: GalleryMouseEnter,
9323 mouseleave: GalleryMouseLeave
9324 }, ".nGY2GThumbnail"); //pass the element as an argument to .on
9325
9326 // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
9327 G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
9328 // G.GOM.hammertime.domEvents = true;
9329
9330 G.GOM.hammertime.on('pan', function(ev) {
9331 if( !G.VOM.viewerDisplayed ) {
9332 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
9333 G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
9334 }
9335 }
9336 });
9337 G.GOM.hammertime.on('panend', function(ev) {
9338 if( !G.VOM.viewerDisplayed ) {
9339 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
9340 if( Math.abs(ev.deltaY) > 100 ) {
9341 // user moved vertically -> cancel pagination
9342 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
9343 return;
9344 }
9345 if( ev.deltaX > 50 ) {
9346 paginationPreviousPage();
9347 return;
9348 }
9349 if( ev.deltaX < -50 ) {
9350 paginationNextPage();
9351 return;
9352 }
9353 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
9354 // pX=0;
9355 }
9356 }
9357 });
9358 G.GOM.hammertime.on('tap', function(ev) {
9359 if( !G.VOM.viewerDisplayed ) {
9360 ev.srcEvent.stopPropagation();
9361 ev.srcEvent.preventDefault(); // cancel mouseenter event
9362
9363 if( ev.pointerType == 'mouse') {
9364 if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
9365 }
9366 else {
9367 var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
9368 if( r.GOMidx == -1 ) { return; }
9369 if( r.action != 'NONE' && r.action != 'OPEN' ) {
9370 // toolbar touched --> execute action
9371 GalleryClicked(ev.srcEvent);
9372 return;
9373 }
9374
9375 if( G.GOM.slider.hostIdx == r.GOMidx ) {
9376 // touch on thumbnail slider -> open immediately
9377 ThumbnailHoverOutAll();
9378 ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
9379 return;
9380 }
9381
9382 if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) || (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
9383 // open on single touch (no hover animation)
9384 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
9385 return;
9386 }
9387
9388 if( G.O.touchAutoOpenDelay > 0 ) {
9389 // open on single touch after end of hover animation (=defined delay)
9390 ThumbnailHoverOutAll();
9391 ThumbnailHover( r.GOMidx );
9392 window.clearInterval( G.touchAutoOpenDelayTimerID );
9393 G.touchAutoOpenDelayTimerID = window.setInterval(function(){
9394 window.clearInterval( G.touchAutoOpenDelayTimerID );
9395 ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
9396 }, G.O.touchAutoOpenDelay );
9397 }
9398 else {
9399 // two touch scenario
9400 if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
9401 ThumbnailHoverOutAll();
9402 ThumbnailHover(r.GOMidx);
9403 }
9404 else {
9405 // second touch
9406 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
9407 }
9408 }
9409 }
9410 }
9411 });
9412
9413
9414 // browser location hash management
9415 if( G.O.locationHash ) {
9416 // jQuery(window).bind( 'hashchange', function() {
9417 // ProcessLocationHash();
9418 // });
9419 jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
9420 }
9421
9422 // Page resize / orientation change
9423 jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
9424
9425 // Event page scrolled
9426 jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
9427
9428 // Debounced function to hide the toolbars on the viewer
9429 G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
9430
9431 // Keyboard management
9432 jQuery(document).keyup(function(e) {
9433 if( G.popup.isDisplayed ) {
9434 switch( e.keyCode) {
9435 case 27: // Esc key
9436 G.popup.close();
9437 break;
9438 }
9439 }
9440 else {
9441 if( G.VOM.viewerDisplayed ) {
9442 ViewerToolsUnHide();
9443 switch( e.keyCode) {
9444 case 27: // Escape key
9445 case 40: // DOWN
9446 CloseInternalViewer(G.VOM.currItemIdx);
9447 break;
9448 case 32: // SPACE
9449 case 13: // ENTER
9450 SlideshowToggle();
9451 break;
9452 case 38: // UP
9453 case 39: // RIGHT
9454 case 33: // PAGE UP
9455 DisplayNextMedia();
9456 break;
9457 case 37: // LEFT
9458 case 34: // PAGE DOWN
9459 DisplayPreviousMedia();
9460 break;
9461 case 35: // END
9462 case 36: // BEGIN
9463 }
9464 }
9465 }
9466 });
9467
9468 // mouse wheel to zoom in/out the image displayed in the internal lightbox
9469 jQuery(window).bind('mousewheel wheel', function(e){
9470 if( G.VOM.viewerDisplayed ) {
9471 var deltaY = 0;
9472 e.preventDefault();
9473
9474 if( ViewerZoomStart() ) {
9475 if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
9476 deltaY = e.originalEvent.deltaY;
9477 } else if (e.originalEvent.wheelDelta) {
9478 deltaY = -e.originalEvent.wheelDelta;
9479 }
9480 ViewerZoomIn( deltaY <= 0 ? true : false );
9481 }
9482 }
9483 });
9484
9485 // mouse mouse -> unhide lightbox toolbars
9486 jQuery(window).bind('mousemove', function(e){
9487 if( G.VOM.viewerDisplayed ) {
9488 debounce( ViewerToolsUnHide, 100, false )();
9489 }
9490 });
9491
9492 // fullscreen mode on/off --> internal lightbox
9493 if( ngscreenfull.enabled ) {
9494 // ngscreenfull.onchange(() => {
9495 ngscreenfull.onchange( function() {
9496 if( G.VOM.viewerDisplayed ) {
9497 if( ngscreenfull.isFullscreen ) {
9498 G.VOM.viewerIsFullscreen=true;
9499 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
9500 }
9501 else {
9502 G.VOM.viewerIsFullscreen=false;
9503 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
9504 }
9505 }
9506 });
9507 }
9508
9509 }
9510
9511 //----- Manage browser location hash (deep linking and browser back/forward)
9512 function ProcessLocationHash() {
9513
9514 // standard use case -> location hash processing
9515 if( !G.O.locationHash ) { return false; }
9516
9517 var curGal = '#nanogallery/' + G.baseEltID + '/',
9518 newLocationHash = location.hash;
9519 if( G.O.debugMode ) {
9520 console.log('------------------------ PROCESS LOCATION HASH');
9521 console.log('newLocationHash1: ' +newLocationHash);
9522 console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
9523 }
9524
9525 if( newLocationHash == '' ) {
9526 // if( G.GOM.lastDisplayedIdx != -1 ) {
9527 if( G.locationHashLastUsed !== '' ) {
9528 // back button and no hash --> display first album
9529 if( G.O.debugMode ) { console.log('display root album' ); }
9530 G.locationHashLastUsed = '';
9531 if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
9532 DisplayAlbum('', '0');
9533 return true;
9534 }
9535 }
9536
9537 if( newLocationHash == G.locationHashLastUsed ) { return; }
9538
9539 if( newLocationHash.indexOf(curGal) == 0 ) {
9540 // item IDs detected
9541 var IDs=parseIDs( newLocationHash.substring(curGal.length) );
9542 if( IDs.imageID != '0' ) {
9543 if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
9544 DisplayPhoto( IDs.imageID, IDs.albumID );
9545 return true;
9546 }
9547 else {
9548 if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID ); }
9549 DisplayAlbum( '-1', IDs.albumID );
9550 return true;
9551 }
9552 }
9553
9554 return false;
9555 }
9556
9557 //---- Set a new browser location hash
9558 function SetLocationHash(albumID, imageID ) {
9559 if( !G.O.locationHash ) { return false; }
9560
9561 if( G.O.debugMode ) {
9562 console.log('------------------------ SET LOCATION HASH');
9563 }
9564
9565 if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
9566 // root album level --> do not set top.location.hash if not already set
9567 if( location.hash != '' ) {
9568 // try to clear the hash if set
9569 if ("pushState" in history) {
9570 history.pushState("", document.title, window.location.pathname + window.location.search);
9571 }
9572 else {
9573 location.hash='';
9574 }
9575 }
9576 G.locationHashLastUsed='';
9577 if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
9578 return;
9579 }
9580
9581 var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
9582 if( imageID != '' ) {
9583 newLocationHash+='/'+imageID;
9584 }
9585
9586 var lH=location.hash;
9587 if( G.O.debugMode ) {
9588 console.log('newLocationHash2: '+newLocationHash);
9589 console.log('location.hash: '+lH);
9590 }
9591
9592 G.locationHashLastUsed=newLocationHash;
9593 if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
9594
9595 if( lH == '' || lH != newLocationHash ) {
9596 // G.locationHashLastUsed='#'+newLocationHash;
9597 try {
9598 top.location.hash=newLocationHash;
9599 }
9600 catch(e) {
9601 // location hash is not supported by current browser --> disable the option
9602 G.O.locationHash=false;
9603 }
9604 }
9605 }
9606
9607
9608 function ResizeWindowEvent() {
9609 G.GOM.cache.viewport = getViewport();
9610 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
9611 G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
9612
9613 if( G.VOM.viewerDisplayed ) {
9614 ResizeInternalViewer();
9615 }
9616 else {
9617 if( G.galleryResizeEventEnabled ) {
9618 var nw = RetrieveCurWidth();
9619 if( G.GOM.albumIdx != -1 &&
9620 ( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] != G.tn.settings.height[G.GOM.curNavLevel][nw] ||
9621 G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] != G.tn.settings.width[G.GOM.curNavLevel][nw] ) ) {
9622 // do not use settings.getH() / settings.getW()
9623 // thumbnail size changed --> render the gallery with the new sizes
9624 G.GOM.curWidth = nw;
9625 //G.layout.SetEngine();
9626 G.GOM.pagination.currentPage = 0;
9627 GalleryRender( G.GOM.albumIdx );
9628 }
9629 else {
9630 GalleryResize();
9631 }
9632 }
9633 }
9634 }
9635
9636
9637 function OnScrollEvent() {
9638 // if( G.scrollTimeOut ) {
9639 // clearTimeout(G.scrollTimeOut);
9640 // }
9641
9642 // G.scrollTimeOut = setTimeout(function () {
9643 if( !G.VOM.viewerDisplayed ) {
9644 if( G.galleryResizeEventEnabled ) {
9645 GalleryResize();
9646 }
9647 return;
9648 }
9649 // }, 100);
9650 }
9651
9652
9653 // I18N : define text translations
9654 function i18n() {
9655
9656 // browser language
9657 G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
9658 if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
9659
9660 var llang=-('_'+G.i18nLang).length;
9661
9662 if( toType(G.O.i18n) == 'object' ){
9663
9664 for( var key in G.O.i18n ) {
9665 //var value = G.O.i18n[key];
9666 var s=key.substr(llang);
9667 if( s == ('_'+G.i18nLang) ) {
9668 G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
9669 }
9670 else {
9671 G.i18nTranslations[key]=G.O.i18n[key];
9672 }
9673 }
9674 }
9675 }
9676
9677 function GetI18nItem( item, property ) {
9678 var s='';
9679 if( G.i18nLang != '' ) {
9680 if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
9681 s=item[property+'_'+G.i18nLang];
9682 return s;
9683 }
9684 }
9685 s=item[property];
9686 return s;
9687 }
9688
9689
9690 function RetrieveCurWidth() {
9691 var vpW= G.GOM.cache.viewport.w;
9692
9693 if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
9694 if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
9695 if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
9696 if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
9697
9698 return 'xl';
9699 }
9700
9701
9702 /** @function browserNotification */
9703 function browserNotification() {
9704 var m = 'Your browser version is not supported anymore. The image gallery cannot be displayed. <br><br>Please update to a more recent one. Download:<br>';
9705 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
9706 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
9707 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
9708 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
9709 NanoAlert(G, m, false);
9710 }
9711
9712 // Original author : John Hrvatin, Lead Program Manager, Internet Explorer - http://blogs.msdn.com/b/ie/archive/2011/10/28/a-best-practice-for-programming-with-vendor-prefixes.aspx
9713 function FirstSupportedPropertyName(prefixedPropertyNames) {
9714 var tempDiv = document.createElement("div");
9715 for (var i = 0; i < prefixedPropertyNames.length; ++i) {
9716 if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
9717 return prefixedPropertyNames[i];
9718 }
9719 return null;
9720 }
9721
9722
9723
9724
9725 }
9726
9727
9728
9729//##########################################################################################################################
9730//## imagesLoaded ##########################################################################################################
9731//##########################################################################################################################
9732
9733// external module EMBEDED in nanogallery
9734// NGY BUILD:
9735// replace "imagesLoaded" with "ngimagesLoaded"
9736// replace "ImagesLoaded" with "ngImagesLoaded"
9737// replace "EvEmitter" with "ngEvEmitter"
9738// replace "var $ = window.jQuery" with "var $ = jQuery;"
9739// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
9740
9741
9742
9743/*!
9744 * imagesLoaded PACKAGED v4.1.1
9745 * JavaScript is all like "You images are done yet or what?"
9746 * MIT License
9747 */
9748
9749/**
9750 * EvEmitter v1.0.3
9751 * Lil' event emitter
9752 * MIT License
9753 */
9754
9755
9756/* jshint unused: true, undef: true, strict: true */
9757
9758( function( global, factory ) {
9759 // universal module definition
9760 /* jshint strict: false */ /* globals define, module, window */
9761// if ( typeof define == 'function' && define.amd ) {
9762 // AMD - RequireJS
9763// define( 'ev-emitter/ev-emitter',factory );
9764// } else if ( typeof module == 'object' && module.exports ) {
9765 // CommonJS - Browserify, Webpack
9766// module.exports = factory();
9767// } else {
9768 // Browser globals
9769 global.ngEvEmitter = factory();
9770// }
9771
9772}( typeof window != 'undefined' ? window : this, function() {
9773
9774
9775
9776function ngEvEmitter() {}
9777
9778var proto = ngEvEmitter.prototype;
9779
9780proto.on = function( eventName, listener ) {
9781 if ( !eventName || !listener ) {
9782 return;
9783 }
9784 // set events hash
9785 var events = this._events = this._events || {};
9786 // set listeners array
9787 var listeners = events[ eventName ] = events[ eventName ] || [];
9788 // only add once
9789 if ( listeners.indexOf( listener ) == -1 ) {
9790 listeners.push( listener );
9791 }
9792
9793 return this;
9794};
9795
9796proto.once = function( eventName, listener ) {
9797 if ( !eventName || !listener ) {
9798 return;
9799 }
9800 // add event
9801 this.on( eventName, listener );
9802 // set once flag
9803 // set onceEvents hash
9804 var onceEvents = this._onceEvents = this._onceEvents || {};
9805 // set onceListeners object
9806 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
9807 // set flag
9808 onceListeners[ listener ] = true;
9809
9810 return this;
9811};
9812
9813proto.off = function( eventName, listener ) {
9814 var listeners = this._events && this._events[ eventName ];
9815 if ( !listeners || !listeners.length ) {
9816 return;
9817 }
9818 var index = listeners.indexOf( listener );
9819 if ( index != -1 ) {
9820 listeners.splice( index, 1 );
9821 }
9822
9823 return this;
9824};
9825
9826proto.emitEvent = function( eventName, args ) {
9827 var listeners = this._events && this._events[ eventName ];
9828 if ( !listeners || !listeners.length ) {
9829 return;
9830 }
9831 var i = 0;
9832 var listener = listeners[i];
9833 args = args || [];
9834 // once stuff
9835 var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
9836
9837 while ( listener ) {
9838 var isOnce = onceListeners && onceListeners[ listener ];
9839 if ( isOnce ) {
9840 // remove listener
9841 // remove before trigger to prevent recursion
9842 this.off( eventName, listener );
9843 // unset once flag
9844 delete onceListeners[ listener ];
9845 }
9846 // trigger listener
9847 listener.apply( this, args );
9848 // get next listener
9849 i += isOnce ? 0 : 1;
9850 listener = listeners[i];
9851 }
9852
9853 return this;
9854};
9855
9856return ngEvEmitter;
9857
9858}));
9859
9860/*!
9861 * ngimagesLoaded v4.1.1
9862 * JavaScript is all like "You images are done yet or what?"
9863 * MIT License
9864 */
9865
9866( function( window, factory ) { 'use strict';
9867 // universal module definition
9868
9869 /*global define: false, module: false, require: false */
9870
9871// if ( typeof define == 'function' && define.amd ) {
9872 // AMD
9873// define( [
9874// 'ev-emitter/ev-emitter'
9875// ], function( ngEvEmitter ) {
9876// return factory( window, ngEvEmitter );
9877// });
9878// } else if ( typeof module == 'object' && module.exports ) {
9879 // CommonJS
9880// module.exports = factory(
9881// window,
9882// require('ev-emitter')
9883// );
9884// } else {
9885 // browser global
9886 window.ngimagesLoaded = factory(
9887 window,
9888 window.ngEvEmitter
9889 );
9890 //}
9891
9892})( window,
9893
9894// -------------------------- factory -------------------------- //
9895
9896function factory( window, ngEvEmitter ) {
9897
9898
9899
9900// var $ = window.jQuery;
9901var $ = jQuery;
9902var console = window.console;
9903
9904// -------------------------- helpers -------------------------- //
9905
9906// extend objects
9907function extend( a, b ) {
9908 for ( var prop in b ) {
9909 a[ prop ] = b[ prop ];
9910 }
9911 return a;
9912}
9913
9914// turn element or nodeList into an array
9915function makeArray( obj ) {
9916 var ary = [];
9917 if ( Array.isArray( obj ) ) {
9918 // use object if already an array
9919 ary = obj;
9920 } else if ( typeof obj.length == 'number' ) {
9921 // convert nodeList to array
9922 for ( var i=0; i < obj.length; i++ ) {
9923 ary.push( obj[i] );
9924 }
9925 } else {
9926 // array of single index
9927 ary.push( obj );
9928 }
9929 return ary;
9930}
9931
9932// -------------------------- ngimagesLoaded -------------------------- //
9933
9934/**
9935 * @param {Array, Element, NodeList, String} elem
9936 * @param {Object or Function} options - if function, use as callback
9937 * @param {Function} onAlways - callback function
9938 */
9939function ngImagesLoaded( elem, options, onAlways ) {
9940 // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
9941 if ( !( this instanceof ngImagesLoaded ) ) {
9942 return new ngImagesLoaded( elem, options, onAlways );
9943 }
9944 // use elem as selector string
9945 if ( typeof elem == 'string' ) {
9946 elem = document.querySelectorAll( elem );
9947 }
9948
9949 this.elements = makeArray( elem );
9950 this.options = extend( {}, this.options );
9951
9952 if ( typeof options == 'function' ) {
9953 onAlways = options;
9954 } else {
9955 extend( this.options, options );
9956 }
9957
9958 if ( onAlways ) {
9959 this.on( 'always', onAlways );
9960 }
9961
9962 this.getImages();
9963
9964 if ( $ ) {
9965 // add jQuery Deferred object
9966 this.jqDeferred = new $.Deferred();
9967 }
9968
9969 // HACK check async to allow time to bind listeners
9970 setTimeout( function() {
9971 this.check();
9972 }.bind( this ));
9973}
9974
9975ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
9976
9977ngImagesLoaded.prototype.options = {};
9978
9979ngImagesLoaded.prototype.getImages = function() {
9980 this.images = [];
9981
9982 // filter & find items if we have an item selector
9983 this.elements.forEach( this.addElementImages, this );
9984};
9985
9986/**
9987 * @param {Node} element
9988 */
9989ngImagesLoaded.prototype.addElementImages = function( elem ) {
9990 // filter siblings
9991 if ( elem.nodeName == 'IMG' ) {
9992 this.addImage( elem );
9993 }
9994 // get background image on element
9995 if ( this.options.background === true ) {
9996 this.addElementBackgroundImages( elem );
9997 }
9998
9999 // find children
10000 // no non-element nodes, #143
10001 var nodeType = elem.nodeType;
10002 if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
10003 return;
10004 }
10005 var childImgs = elem.querySelectorAll('img');
10006 // concat childElems to filterFound array
10007 for ( var i=0; i < childImgs.length; i++ ) {
10008 var img = childImgs[i];
10009 this.addImage( img );
10010 }
10011
10012 // get child background images
10013 if ( typeof this.options.background == 'string' ) {
10014 var children = elem.querySelectorAll( this.options.background );
10015 for ( i=0; i < children.length; i++ ) {
10016 var child = children[i];
10017 this.addElementBackgroundImages( child );
10018 }
10019 }
10020};
10021
10022var elementNodeTypes = {
10023 1: true,
10024 9: true,
10025 11: true
10026};
10027
10028ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
10029 var style = getComputedStyle( elem );
10030 if ( !style ) {
10031 // Firefox returns null if in a hidden iframe https://bugzil.la/548397
10032 return;
10033 }
10034 // get url inside url("...")
10035 var reURL = /url\((['"])?(.*?)\1\)/gi;
10036 var matches = reURL.exec( style.backgroundImage );
10037 while ( matches !== null ) {
10038 var url = matches && matches[2];
10039 if ( url ) {
10040 this.addBackground( url, elem );
10041 }
10042 matches = reURL.exec( style.backgroundImage );
10043 }
10044};
10045
10046/**
10047 * @param {Image} img
10048 */
10049ngImagesLoaded.prototype.addImage = function( img ) {
10050 var loadingImage = new LoadingImage( img );
10051 this.images.push( loadingImage );
10052};
10053
10054ngImagesLoaded.prototype.addBackground = function( url, elem ) {
10055 var background = new Background( url, elem );
10056 this.images.push( background );
10057};
10058
10059ngImagesLoaded.prototype.check = function() {
10060 var _this = this;
10061 this.progressedCount = 0;
10062 this.hasAnyBroken = false;
10063 // complete if no images
10064 if ( !this.images.length ) {
10065 this.complete();
10066 return;
10067 }
10068
10069 function onProgress( image, elem, message ) {
10070 // HACK - Chrome triggers event before object properties have changed. #83
10071 setTimeout( function() {
10072 _this.progress( image, elem, message );
10073 });
10074 }
10075
10076 this.images.forEach( function( loadingImage ) {
10077 loadingImage.once( 'progress', onProgress );
10078 loadingImage.check();
10079 });
10080};
10081
10082ngImagesLoaded.prototype.progress = function( image, elem, message ) {
10083 this.progressedCount++;
10084 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
10085 // progress event
10086 this.emitEvent( 'progress', [ this, image, elem ] );
10087 if ( this.jqDeferred && this.jqDeferred.notify ) {
10088 this.jqDeferred.notify( this, image );
10089 }
10090 // check if completed
10091 if ( this.progressedCount == this.images.length ) {
10092 this.complete();
10093 }
10094
10095 if ( this.options.debug && console ) {
10096 console.log( 'progress: ' + message, image, elem );
10097 }
10098};
10099
10100ngImagesLoaded.prototype.complete = function() {
10101 var eventName = this.hasAnyBroken ? 'fail' : 'done';
10102 this.isComplete = true;
10103 this.emitEvent( eventName, [ this ] );
10104 this.emitEvent( 'always', [ this ] );
10105 if ( this.jqDeferred ) {
10106 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
10107 this.jqDeferred[ jqMethod ]( this );
10108 }
10109};
10110
10111// -------------------------- -------------------------- //
10112
10113function LoadingImage( img ) {
10114 this.img = img;
10115}
10116
10117LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
10118
10119LoadingImage.prototype.check = function() {
10120 // If complete is true and browser supports natural sizes,
10121 // try to check for image status manually.
10122 var isComplete = this.getIsImageComplete();
10123 if ( isComplete ) {
10124 // report based on naturalWidth
10125 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
10126 return;
10127 }
10128
10129 // If none of the checks above matched, simulate loading on detached element.
10130 this.proxyImage = new Image();
10131 this.proxyImage.addEventListener( 'load', this );
10132 this.proxyImage.addEventListener( 'error', this );
10133 // bind to image as well for Firefox. #191
10134 this.img.addEventListener( 'load', this );
10135 this.img.addEventListener( 'error', this );
10136 this.proxyImage.src = this.img.src;
10137};
10138
10139LoadingImage.prototype.getIsImageComplete = function() {
10140 return this.img.complete && this.img.naturalWidth !== undefined;
10141};
10142
10143LoadingImage.prototype.confirm = function( isLoaded, message ) {
10144 this.isLoaded = isLoaded;
10145 this.emitEvent( 'progress', [ this, this.img, message ] );
10146};
10147
10148// ----- events ----- //
10149
10150// trigger specified handler for event type
10151LoadingImage.prototype.handleEvent = function( event ) {
10152 var method = 'on' + event.type;
10153 if ( this[ method ] ) {
10154 this[ method ]( event );
10155 }
10156};
10157
10158LoadingImage.prototype.onload = function() {
10159 this.confirm( true, 'onload' );
10160 this.unbindEvents();
10161};
10162
10163LoadingImage.prototype.onerror = function() {
10164 this.confirm( false, 'onerror' );
10165 this.unbindEvents();
10166};
10167
10168LoadingImage.prototype.unbindEvents = function() {
10169 this.proxyImage.removeEventListener( 'load', this );
10170 this.proxyImage.removeEventListener( 'error', this );
10171 this.img.removeEventListener( 'load', this );
10172 this.img.removeEventListener( 'error', this );
10173};
10174
10175// -------------------------- Background -------------------------- //
10176
10177function Background( url, element ) {
10178 this.url = url;
10179 this.element = element;
10180 this.img = new Image();
10181}
10182
10183// inherit LoadingImage prototype
10184Background.prototype = Object.create( LoadingImage.prototype );
10185
10186Background.prototype.check = function() {
10187 this.img.addEventListener( 'load', this );
10188 this.img.addEventListener( 'error', this );
10189 this.img.src = this.url;
10190 // check if image is already complete
10191 var isComplete = this.getIsImageComplete();
10192 if ( isComplete ) {
10193 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
10194 this.unbindEvents();
10195 }
10196};
10197
10198Background.prototype.unbindEvents = function() {
10199 this.img.removeEventListener( 'load', this );
10200 this.img.removeEventListener( 'error', this );
10201};
10202
10203Background.prototype.confirm = function( isLoaded, message ) {
10204 this.isLoaded = isLoaded;
10205 this.emitEvent( 'progress', [ this, this.element, message ] );
10206};
10207
10208// -------------------------- jQuery -------------------------- //
10209
10210ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
10211 jQuery = jQuery || window.jQuery;
10212 if ( !jQuery ) {
10213 return;
10214 }
10215 // set local variable
10216 $ = jQuery;
10217 // $().ngimagesLoaded()
10218 $.fn.ngimagesLoaded = function( options, callback ) {
10219 var instance = new ngImagesLoaded( this, options, callback );
10220 return instance.jqDeferred.promise( $(this) );
10221 };
10222};
10223// try making plugin
10224ngImagesLoaded.makeJQueryPlugin();
10225
10226// -------------------------- -------------------------- //
10227
10228return ngImagesLoaded;
10229
10230});
10231
10232
10233
10234//##########################################################################################################################
10235//## screenfull.js #########################################################################################################
10236//##########################################################################################################################
10237
10238// screenfull.js
10239// v3.2.0
10240// by sindresorhus - https://github.com/sindresorhus
10241// from: https://github.com/sindresorhus/screenfull.js
10242
10243// external module embeded in nanogallery
10244// NGY BUILD:
10245// replace "screenfull" with "ngscreenfull"
10246//
10247
10248(function () {
10249 'use strict';
10250
10251 var document = typeof window === 'undefined' ? {} : window.document;
10252 var isCommonjs = typeof module !== 'undefined' && module.exports;
10253 var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
10254
10255 var fn = (function () {
10256 var val;
10257
10258 var fnMap = [
10259 [
10260 'requestFullscreen',
10261 'exitFullscreen',
10262 'fullscreenElement',
10263 'fullscreenEnabled',
10264 'fullscreenchange',
10265 'fullscreenerror'
10266 ],
10267 // New WebKit
10268 [
10269 'webkitRequestFullscreen',
10270 'webkitExitFullscreen',
10271 'webkitFullscreenElement',
10272 'webkitFullscreenEnabled',
10273 'webkitfullscreenchange',
10274 'webkitfullscreenerror'
10275
10276 ],
10277 // Old WebKit (Safari 5.1)
10278 [
10279 'webkitRequestFullScreen',
10280 'webkitCancelFullScreen',
10281 'webkitCurrentFullScreenElement',
10282 'webkitCancelFullScreen',
10283 'webkitfullscreenchange',
10284 'webkitfullscreenerror'
10285
10286 ],
10287 [
10288 'mozRequestFullScreen',
10289 'mozCancelFullScreen',
10290 'mozFullScreenElement',
10291 'mozFullScreenEnabled',
10292 'mozfullscreenchange',
10293 'mozfullscreenerror'
10294 ],
10295 [
10296 'msRequestFullscreen',
10297 'msExitFullscreen',
10298 'msFullscreenElement',
10299 'msFullscreenEnabled',
10300 'MSFullscreenChange',
10301 'MSFullscreenError'
10302 ]
10303 ];
10304
10305 var i = 0;
10306 var l = fnMap.length;
10307 var ret = {};
10308
10309 for (; i < l; i++) {
10310 val = fnMap[i];
10311 if (val && val[1] in document) {
10312 for (i = 0; i < val.length; i++) {
10313 ret[fnMap[0][i]] = val[i];
10314 }
10315 return ret;
10316 }
10317 }
10318
10319 return false;
10320 })();
10321
10322 var ngscreenfull = {
10323 request: function (elem) {
10324 var request = fn.requestFullscreen;
10325
10326 elem = elem || document.documentElement;
10327
10328 // Work around Safari 5.1 bug: reports support for
10329 // keyboard in fullscreen even though it doesn't.
10330 // Browser sniffing, since the alternative with
10331 // setTimeout is even worse.
10332 if (/5\.1[.\d]* Safari/.test(navigator.userAgent)) {
10333 elem[request]();
10334 } else {
10335 elem[request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT);
10336 }
10337 },
10338 exit: function () {
10339 document[fn.exitFullscreen]();
10340 },
10341 toggle: function (elem) {
10342 if (this.isFullscreen) {
10343 this.exit();
10344 } else {
10345 this.request(elem);
10346 }
10347 },
10348 onchange: function (callback) {
10349 document.addEventListener(fn.fullscreenchange, callback, false);
10350 },
10351 onerror: function (callback) {
10352 document.addEventListener(fn.fullscreenerror, callback, false);
10353 },
10354 raw: fn
10355 };
10356
10357 if (!fn) {
10358 if (isCommonjs) {
10359 module.exports = false;
10360 } else {
10361 window.ngscreenfull = false;
10362 }
10363
10364 return;
10365 }
10366
10367 Object.defineProperties(ngscreenfull, {
10368 isFullscreen: {
10369 get: function () {
10370 return Boolean(document[fn.fullscreenElement]);
10371 }
10372 },
10373 element: {
10374 enumerable: true,
10375 get: function () {
10376 return document[fn.fullscreenElement];
10377 }
10378 },
10379 enabled: {
10380 enumerable: true,
10381 get: function () {
10382 // Coerce to boolean in case of old WebKit
10383 return Boolean(document[fn.fullscreenEnabled]);
10384 }
10385 }
10386 });
10387
10388 if (isCommonjs) {
10389 module.exports = ngscreenfull;
10390 } else {
10391 window.ngscreenfull = ngscreenfull;
10392 }
10393})();
10394
10395
10396
10397//##########################################################################################################################
10398//## Shifty ################################################################################################################
10399//##########################################################################################################################
10400
10401 /*!
10402 * Shifty
10403 * By Jeremy Kahn - jeremyckahn@gmail.com
10404 */
10405
10406// external module EMBEDED in nanogallery
10407// NGY BUILD:
10408//
10409// replace "Tweenable" with "NGTweenable"
10410// replace "define.amd" with "define.amdDISABLED"
10411/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
10412;(function () {
10413 var root = this || Function('return this')();
10414
10415/**
10416 * Shifty Core
10417 * By Jeremy Kahn - jeremyckahn@gmail.com
10418 */
10419
10420var NGTweenable = (function () {
10421
10422 'use strict';
10423
10424 // Aliases that get defined later in this function
10425 var formula;
10426
10427 // CONSTANTS
10428 var DEFAULT_SCHEDULE_FUNCTION;
10429 var DEFAULT_EASING = 'linear';
10430 var DEFAULT_DURATION = 500;
10431 var UPDATE_TIME = 1000 / 60;
10432
10433 var _now = Date.now
10434 ? Date.now
10435 : function () {return +new Date();};
10436
10437 var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
10438
10439 if (typeof window !== 'undefined') {
10440 // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
10441 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
10442 DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
10443 || window.webkitRequestAnimationFrame
10444 || window.oRequestAnimationFrame
10445 || window.msRequestAnimationFrame
10446 || (window.mozCancelRequestAnimationFrame
10447 && window.mozRequestAnimationFrame)
10448 || setTimeout;
10449 } else {
10450 DEFAULT_SCHEDULE_FUNCTION = setTimeout;
10451 }
10452
10453 function noop () {
10454 // NOOP!
10455 }
10456
10457 /**
10458 * Handy shortcut for doing a for-in loop. This is not a "normal" each
10459 * function, it is optimized for Shifty. The iterator function only receives
10460 * the property name, not the value.
10461 * @param {Object} obj
10462 * @param {Function(string)} fn
10463 * @private
10464 */
10465 function each (obj, fn) {
10466 var key;
10467 for (key in obj) {
10468 if (Object.hasOwnProperty.call(obj, key)) {
10469 fn(key);
10470 }
10471 }
10472 }
10473
10474 /**
10475 * Perform a shallow copy of Object properties.
10476 * @param {Object} targetObject The object to copy into
10477 * @param {Object} srcObject The object to copy from
10478 * @return {Object} A reference to the augmented `targetObj` Object
10479 * @private
10480 */
10481 function shallowCopy (targetObj, srcObj) {
10482 each(srcObj, function (prop) {
10483 targetObj[prop] = srcObj[prop];
10484 });
10485
10486 return targetObj;
10487 }
10488
10489 /**
10490 * Copies each property from src onto target, but only if the property to
10491 * copy to target is undefined.
10492 * @param {Object} target Missing properties in this Object are filled in
10493 * @param {Object} src
10494 * @private
10495 */
10496 function defaults (target, src) {
10497 each(src, function (prop) {
10498 if (typeof target[prop] === 'undefined') {
10499 target[prop] = src[prop];
10500 }
10501 });
10502 }
10503
10504 /**
10505 * Calculates the interpolated tween values of an Object for a given
10506 * timestamp.
10507 * @param {Number} forPosition The position to compute the state for.
10508 * @param {Object} currentState Current state properties.
10509 * @param {Object} originalState: The original state properties the Object is
10510 * tweening from.
10511 * @param {Object} targetState: The destination state properties the Object
10512 * is tweening to.
10513 * @param {number} duration: The length of the tween in milliseconds.
10514 * @param {number} timestamp: The UNIX epoch time at which the tween began.
10515 * @param {Object} easing: This Object's keys must correspond to the keys in
10516 * targetState.
10517 * @private
10518 */
10519 function tweenProps (forPosition, currentState, originalState, targetState,
10520 duration, timestamp, easing) {
10521 var normalizedPosition =
10522 forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
10523
10524
10525 var prop;
10526 var easingObjectProp;
10527 var easingFn;
10528 for (prop in currentState) {
10529 if (currentState.hasOwnProperty(prop)) {
10530 easingObjectProp = easing[prop];
10531 easingFn = typeof easingObjectProp === 'function'
10532 ? easingObjectProp
10533 : formula[easingObjectProp];
10534
10535 currentState[prop] = tweenProp(
10536 originalState[prop],
10537 targetState[prop],
10538 easingFn,
10539 normalizedPosition
10540 );
10541 }
10542 }
10543
10544 return currentState;
10545 }
10546
10547 /**
10548 * Tweens a single property.
10549 * @param {number} start The value that the tween started from.
10550 * @param {number} end The value that the tween should end at.
10551 * @param {Function} easingFunc The easing curve to apply to the tween.
10552 * @param {number} position The normalized position (between 0.0 and 1.0) to
10553 * calculate the midpoint of 'start' and 'end' against.
10554 * @return {number} The tweened value.
10555 * @private
10556 */
10557 function tweenProp (start, end, easingFunc, position) {
10558 return start + (end - start) * easingFunc(position);
10559 }
10560
10561 /**
10562 * Applies a filter to NGTweenable instance.
10563 * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
10564 * upon.
10565 * @param {String} filterName The name of the filter to apply.
10566 * @private
10567 */
10568 function applyFilter (tweenable, filterName) {
10569 var filters = NGTweenable.prototype.filter;
10570 var args = tweenable._filterArgs;
10571
10572 each(filters, function (name) {
10573 if (typeof filters[name][filterName] !== 'undefined') {
10574 filters[name][filterName].apply(tweenable, args);
10575 }
10576 });
10577 }
10578
10579 var timeoutHandler_endTime;
10580 var timeoutHandler_currentTime;
10581 var timeoutHandler_isEnded;
10582 var timeoutHandler_offset;
10583 /**
10584 * Handles the update logic for one step of a tween.
10585 * @param {NGTweenable} tweenable
10586 * @param {number} timestamp
10587 * @param {number} delay
10588 * @param {number} duration
10589 * @param {Object} currentState
10590 * @param {Object} originalState
10591 * @param {Object} targetState
10592 * @param {Object} easing
10593 * @param {Function(Object, *, number)} step
10594 * @param {Function(Function,number)}} schedule
10595 * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
10596 * NGTweenable#seek.
10597 * @private
10598 */
10599 function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
10600 originalState, targetState, easing, step, schedule,
10601 opt_currentTimeOverride) {
10602
10603 timeoutHandler_endTime = timestamp + delay + duration;
10604
10605 timeoutHandler_currentTime =
10606 Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
10607
10608 timeoutHandler_isEnded =
10609 timeoutHandler_currentTime >= timeoutHandler_endTime;
10610
10611 timeoutHandler_offset = duration - (
10612 timeoutHandler_endTime - timeoutHandler_currentTime);
10613
10614 if (tweenable.isPlaying()) {
10615 if (timeoutHandler_isEnded) {
10616 step(targetState, tweenable._attachment, timeoutHandler_offset);
10617 tweenable.stop(true);
10618 } else {
10619 tweenable._scheduleId =
10620 schedule(tweenable._timeoutHandler, UPDATE_TIME);
10621
10622 applyFilter(tweenable, 'beforeTween');
10623
10624 // If the animation has not yet reached the start point (e.g., there was
10625 // delay that has not yet completed), just interpolate the starting
10626 // position of the tween.
10627 if (timeoutHandler_currentTime < (timestamp + delay)) {
10628 tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
10629 } else {
10630 tweenProps(timeoutHandler_currentTime, currentState, originalState,
10631 targetState, duration, timestamp + delay, easing);
10632 }
10633
10634 applyFilter(tweenable, 'afterTween');
10635
10636 step(currentState, tweenable._attachment, timeoutHandler_offset);
10637 }
10638 }
10639 }
10640
10641
10642 /**
10643 * Creates a usable easing Object from a string, a function or another easing
10644 * Object. If `easing` is an Object, then this function clones it and fills
10645 * in the missing properties with `"linear"`.
10646 * @param {Object.<string|Function>} fromTweenParams
10647 * @param {Object|string|Function} easing
10648 * @return {Object.<string|Function>}
10649 * @private
10650 */
10651 function composeEasingObject (fromTweenParams, easing) {
10652 var composedEasing = {};
10653 var typeofEasing = typeof easing;
10654
10655 if (typeofEasing === 'string' || typeofEasing === 'function') {
10656 each(fromTweenParams, function (prop) {
10657 composedEasing[prop] = easing;
10658 });
10659 } else {
10660 each(fromTweenParams, function (prop) {
10661 if (!composedEasing[prop]) {
10662 composedEasing[prop] = easing[prop] || DEFAULT_EASING;
10663 }
10664 });
10665 }
10666
10667 return composedEasing;
10668 }
10669
10670 /**
10671 * NGTweenable constructor.
10672 * @class NGTweenable
10673 * @param {Object=} opt_initialState The values that the initial tween should
10674 * start at if a `from` object is not provided to `{{#crossLink
10675 * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
10676 * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10677 * @param {Object=} opt_config Configuration object to be passed to
10678 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10679 * @module NGTweenable
10680 * @constructor
10681 */
10682 function NGTweenable (opt_initialState, opt_config) {
10683 this._currentState = opt_initialState || {};
10684 this._configured = false;
10685 this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
10686
10687 // To prevent unnecessary calls to setConfig do not set default
10688 // configuration here. Only set default configuration immediately before
10689 // tweening if none has been set.
10690 if (typeof opt_config !== 'undefined') {
10691 this.setConfig(opt_config);
10692 }
10693 }
10694
10695 /**
10696 * Configure and start a tween.
10697 * @method tween
10698 * @param {Object=} opt_config Configuration object to be passed to
10699 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10700 * @chainable
10701 */
10702 NGTweenable.prototype.tween = function (opt_config) {
10703 if (this._isTweening) {
10704 return this;
10705 }
10706
10707 // Only set default config if no configuration has been set previously and
10708 // none is provided now.
10709 if (opt_config !== undefined || !this._configured) {
10710 this.setConfig(opt_config);
10711 }
10712
10713 this._timestamp = now();
10714 this._start(this.get(), this._attachment);
10715 return this.resume();
10716 };
10717
10718 /**
10719 * Configure a tween that will start at some point in the future.
10720 *
10721 * @method setConfig
10722 * @param {Object} config The following values are valid:
10723 * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink
10724 * "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
10725 * - __to__ (_Object=_): Ending position.
10726 * - __duration__ (_number=_): How many milliseconds to animate for.
10727 * - __delay__ (_delay=_): How many milliseconds to wait before starting the
10728 * tween.
10729 * - __start__ (_Function(Object, *)_): Function to execute when the tween
10730 * begins. Receives the state of the tween as the first parameter and
10731 * `attachment` as the second parameter.
10732 * - __step__ (_Function(Object, *, number)_): Function to execute on every
10733 * tick. Receives `{{#crossLink
10734 * "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
10735 * `attachment` as the second parameter, and the time elapsed since the
10736 * start of the tween as the third. This function is not called on the
10737 * final step of the animation, but `finish` is.
10738 * - __finish__ (_Function(Object, *)_): Function to execute upon tween
10739 * completion. Receives the state of the tween as the first parameter and
10740 * `attachment` as the second parameter.
10741 * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
10742 * name(s) or function(s) to use for the tween.
10743 * - __attachment__ (_*_): Cached value that is passed to the
10744 * `step`/`start`/`finish` methods.
10745 * @chainable
10746 */
10747 NGTweenable.prototype.setConfig = function (config) {
10748 config = config || {};
10749 this._configured = true;
10750
10751 // Attach something to this NGTweenable instance (e.g.: a DOM element, an
10752 // object, a string, etc.);
10753 this._attachment = config.attachment;
10754
10755 // Init the internal state
10756 this._pausedAtTime = null;
10757 this._scheduleId = null;
10758 this._delay = config.delay || 0;
10759 this._start = config.start || noop;
10760 this._step = config.step || noop;
10761 this._finish = config.finish || noop;
10762 this._duration = config.duration || DEFAULT_DURATION;
10763 this._currentState = shallowCopy({}, config.from || this.get());
10764 this._originalState = this.get();
10765 this._targetState = shallowCopy({}, config.to || this.get());
10766
10767 var self = this;
10768 this._timeoutHandler = function () {
10769 timeoutHandler(self,
10770 self._timestamp,
10771 self._delay,
10772 self._duration,
10773 self._currentState,
10774 self._originalState,
10775 self._targetState,
10776 self._easing,
10777 self._step,
10778 self._scheduleFunction
10779 );
10780 };
10781
10782 // Aliases used below
10783 var currentState = this._currentState;
10784 var targetState = this._targetState;
10785
10786 // Ensure that there is always something to tween to.
10787 defaults(targetState, currentState);
10788
10789 this._easing = composeEasingObject(
10790 currentState, config.easing || DEFAULT_EASING);
10791
10792 this._filterArgs =
10793 [currentState, this._originalState, targetState, this._easing];
10794
10795 applyFilter(this, 'tweenCreated');
10796 return this;
10797 };
10798
10799 /**
10800 * @method get
10801 * @return {Object} The current state.
10802 */
10803 NGTweenable.prototype.get = function () {
10804 return shallowCopy({}, this._currentState);
10805 };
10806
10807 /**
10808 * @method set
10809 * @param {Object} state The current state.
10810 */
10811 NGTweenable.prototype.set = function (state) {
10812 this._currentState = state;
10813 };
10814
10815 /**
10816 * Pause a tween. Paused tweens can be resumed from the point at which they
10817 * were paused. This is different from `{{#crossLink
10818 * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
10819 * causes a tween to start over when it is resumed.
10820 * @method pause
10821 * @chainable
10822 */
10823 NGTweenable.prototype.pause = function () {
10824 this._pausedAtTime = now();
10825 this._isPaused = true;
10826 return this;
10827 };
10828
10829 /**
10830 * Resume a paused tween.
10831 * @method resume
10832 * @chainable
10833 */
10834 NGTweenable.prototype.resume = function () {
10835 if (this._isPaused) {
10836 this._timestamp += now() - this._pausedAtTime;
10837 }
10838
10839 this._isPaused = false;
10840 this._isTweening = true;
10841
10842 this._timeoutHandler();
10843
10844 return this;
10845 };
10846
10847 /**
10848 * Move the state of the animation to a specific point in the tween's
10849 * timeline. If the animation is not running, this will cause the `step`
10850 * handlers to be called.
10851 * @method seek
10852 * @param {millisecond} millisecond The millisecond of the animation to seek
10853 * to. This must not be less than `0`.
10854 * @chainable
10855 */
10856 NGTweenable.prototype.seek = function (millisecond) {
10857 millisecond = Math.max(millisecond, 0);
10858 var currentTime = now();
10859
10860 if ((this._timestamp + millisecond) === 0) {
10861 return this;
10862 }
10863
10864 this._timestamp = currentTime - millisecond;
10865
10866 if (!this.isPlaying()) {
10867 this._isTweening = true;
10868 this._isPaused = false;
10869
10870 // If the animation is not running, call timeoutHandler to make sure that
10871 // any step handlers are run.
10872 timeoutHandler(this,
10873 this._timestamp,
10874 this._delay,
10875 this._duration,
10876 this._currentState,
10877 this._originalState,
10878 this._targetState,
10879 this._easing,
10880 this._step,
10881 this._scheduleFunction,
10882 currentTime
10883 );
10884
10885 this.pause();
10886 }
10887
10888 return this;
10889 };
10890
10891 /**
10892 * Stops and cancels a tween.
10893 * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
10894 * its current state, and the `finish` handler is not invoked. If `true`,
10895 * the tweened object's values are instantly set to the target values, and
10896 * `finish` is invoked.
10897 * @method stop
10898 * @chainable
10899 */
10900 NGTweenable.prototype.stop = function (gotoEnd) {
10901 this._isTweening = false;
10902 this._isPaused = false;
10903 this._timeoutHandler = noop;
10904
10905 (root.cancelAnimationFrame ||
10906 root.webkitCancelAnimationFrame ||
10907 root.oCancelAnimationFrame ||
10908 root.msCancelAnimationFrame ||
10909 root.mozCancelRequestAnimationFrame ||
10910 root.clearTimeout)(this._scheduleId);
10911
10912 if (gotoEnd) {
10913 applyFilter(this, 'beforeTween');
10914 tweenProps(
10915 1,
10916 this._currentState,
10917 this._originalState,
10918 this._targetState,
10919 1,
10920 0,
10921 this._easing
10922 );
10923 applyFilter(this, 'afterTween');
10924 applyFilter(this, 'afterTweenEnd');
10925 this._finish.call(this, this._currentState, this._attachment);
10926 }
10927
10928 return this;
10929 };
10930
10931 /**
10932 * @method isPlaying
10933 * @return {boolean} Whether or not a tween is running.
10934 */
10935 NGTweenable.prototype.isPlaying = function () {
10936 return this._isTweening && !this._isPaused;
10937 };
10938
10939 /**
10940 * Set a custom schedule function.
10941 *
10942 * If a custom function is not set,
10943 * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
10944 * is used if available, otherwise
10945 * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
10946 * is used.
10947 * @method setScheduleFunction
10948 * @param {Function(Function,number)} scheduleFunction The function to be
10949 * used to schedule the next frame to be rendered.
10950 */
10951 NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
10952 this._scheduleFunction = scheduleFunction;
10953 };
10954
10955 /**
10956 * `delete` all "own" properties. Call this when the `NGTweenable` instance
10957 * is no longer needed to free memory.
10958 * @method dispose
10959 */
10960 NGTweenable.prototype.dispose = function () {
10961 var prop;
10962 for (prop in this) {
10963 if (this.hasOwnProperty(prop)) {
10964 delete this[prop];
10965 }
10966 }
10967 };
10968
10969 /**
10970 * Filters are used for transforming the properties of a tween at various
10971 * points in a NGTweenable's life cycle. See the README for more info on this.
10972 * @private
10973 */
10974 NGTweenable.prototype.filter = {};
10975
10976 /**
10977 * This object contains all of the tweens available to Shifty. It is
10978 * extensible - simply attach properties to the `NGTweenable.prototype.formula`
10979 * Object following the same format as `linear`.
10980 *
10981 * `pos` should be a normalized `number` (between 0 and 1).
10982 * @property formula
10983 * @type {Object(function)}
10984 */
10985 NGTweenable.prototype.formula = {
10986 linear: function (pos) {
10987 return pos;
10988 }
10989 };
10990
10991 formula = NGTweenable.prototype.formula;
10992
10993 shallowCopy(NGTweenable, {
10994 'now': now
10995 ,'each': each
10996 ,'tweenProps': tweenProps
10997 ,'tweenProp': tweenProp
10998 ,'applyFilter': applyFilter
10999 ,'shallowCopy': shallowCopy
11000 ,'defaults': defaults
11001 ,'composeEasingObject': composeEasingObject
11002 });
11003
11004 // `root` is provided in the intro/outro files.
11005
11006 // A hook used for unit testing.
11007 if (typeof SHIFTY_DEBUG_NOW === 'function') {
11008 root.timeoutHandler = timeoutHandler;
11009 }
11010
11011 // Bootstrap NGTweenable appropriately for the environment.
11012 if (typeof exports === 'object') {
11013 // CommonJS
11014 module.exports = NGTweenable;
11015 } else if (typeof define === 'function' && define.amdDISABLED) {
11016 // AMD
11017 define(function () {return NGTweenable;});
11018 } else if (typeof root.NGTweenable === 'undefined') {
11019 // Browser: Make `NGTweenable` globally accessible.
11020 root.NGTweenable = NGTweenable;
11021 }
11022
11023 return NGTweenable;
11024
11025} ());
11026
11027/*!
11028 * All equations are adapted from Thomas Fuchs'
11029 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
11030 *
11031 * Based on Easing Equations (c) 2003 [Robert
11032 * Penner](http://www.robertpenner.com/), all rights reserved. This work is
11033 * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
11034 */
11035
11036/*!
11037 * TERMS OF USE - EASING EQUATIONS
11038 * Open source under the BSD License.
11039 * Easing Equations (c) 2003 Robert Penner, all rights reserved.
11040 */
11041
11042;(function () {
11043
11044 NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
11045 easeInQuad: function (pos) {
11046 return Math.pow(pos, 2);
11047 },
11048
11049 easeOutQuad: function (pos) {
11050 return -(Math.pow((pos - 1), 2) - 1);
11051 },
11052
11053 easeInOutQuad: function (pos) {
11054 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
11055 return -0.5 * ((pos -= 2) * pos - 2);
11056 },
11057
11058 easeInCubic: function (pos) {
11059 return Math.pow(pos, 3);
11060 },
11061
11062 easeOutCubic: function (pos) {
11063 return (Math.pow((pos - 1), 3) + 1);
11064 },
11065
11066 easeInOutCubic: function (pos) {
11067 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
11068 return 0.5 * (Math.pow((pos - 2),3) + 2);
11069 },
11070
11071 easeInQuart: function (pos) {
11072 return Math.pow(pos, 4);
11073 },
11074
11075 easeOutQuart: function (pos) {
11076 return -(Math.pow((pos - 1), 4) - 1);
11077 },
11078
11079 easeInOutQuart: function (pos) {
11080 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
11081 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
11082 },
11083
11084 easeInQuint: function (pos) {
11085 return Math.pow(pos, 5);
11086 },
11087
11088 easeOutQuint: function (pos) {
11089 return (Math.pow((pos - 1), 5) + 1);
11090 },
11091
11092 easeInOutQuint: function (pos) {
11093 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
11094 return 0.5 * (Math.pow((pos - 2),5) + 2);
11095 },
11096
11097 easeInSine: function (pos) {
11098 return -Math.cos(pos * (Math.PI / 2)) + 1;
11099 },
11100
11101 easeOutSine: function (pos) {
11102 return Math.sin(pos * (Math.PI / 2));
11103 },
11104
11105 easeInOutSine: function (pos) {
11106 return (-0.5 * (Math.cos(Math.PI * pos) - 1));
11107 },
11108
11109 easeInExpo: function (pos) {
11110 return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
11111 },
11112
11113 easeOutExpo: function (pos) {
11114 return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
11115 },
11116
11117 easeInOutExpo: function (pos) {
11118 if (pos === 0) {return 0;}
11119 if (pos === 1) {return 1;}
11120 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
11121 return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
11122 },
11123
11124 easeInCirc: function (pos) {
11125 return -(Math.sqrt(1 - (pos * pos)) - 1);
11126 },
11127
11128 easeOutCirc: function (pos) {
11129 return Math.sqrt(1 - Math.pow((pos - 1), 2));
11130 },
11131
11132 easeInOutCirc: function (pos) {
11133 if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
11134 return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
11135 },
11136
11137 easeOutBounce: function (pos) {
11138 if ((pos) < (1 / 2.75)) {
11139 return (7.5625 * pos * pos);
11140 } else if (pos < (2 / 2.75)) {
11141 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
11142 } else if (pos < (2.5 / 2.75)) {
11143 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
11144 } else {
11145 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
11146 }
11147 },
11148
11149 easeInBack: function (pos) {
11150 var s = 1.70158;
11151 return (pos) * pos * ((s + 1) * pos - s);
11152 },
11153
11154 easeOutBack: function (pos) {
11155 var s = 1.70158;
11156 return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
11157 },
11158
11159 easeInOutBack: function (pos) {
11160 var s = 1.70158;
11161 if ((pos /= 0.5) < 1) {
11162 return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
11163 }
11164 return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
11165 },
11166
11167 elastic: function (pos) {
11168 // jshint maxlen:90
11169 return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
11170 },
11171
11172 swingFromTo: function (pos) {
11173 var s = 1.70158;
11174 return ((pos /= 0.5) < 1) ?
11175 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
11176 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
11177 },
11178
11179 swingFrom: function (pos) {
11180 var s = 1.70158;
11181 return pos * pos * ((s + 1) * pos - s);
11182 },
11183
11184 swingTo: function (pos) {
11185 var s = 1.70158;
11186 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
11187 },
11188
11189 bounce: function (pos) {
11190 if (pos < (1 / 2.75)) {
11191 return (7.5625 * pos * pos);
11192 } else if (pos < (2 / 2.75)) {
11193 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
11194 } else if (pos < (2.5 / 2.75)) {
11195 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
11196 } else {
11197 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
11198 }
11199 },
11200
11201 bouncePast: function (pos) {
11202 if (pos < (1 / 2.75)) {
11203 return (7.5625 * pos * pos);
11204 } else if (pos < (2 / 2.75)) {
11205 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
11206 } else if (pos < (2.5 / 2.75)) {
11207 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
11208 } else {
11209 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
11210 }
11211 },
11212
11213 easeFromTo: function (pos) {
11214 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
11215 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
11216 },
11217
11218 easeFrom: function (pos) {
11219 return Math.pow(pos,4);
11220 },
11221
11222 easeTo: function (pos) {
11223 return Math.pow(pos,0.25);
11224 }
11225 });
11226
11227}());
11228
11229// jshint maxlen:100
11230/**
11231 * The Bezier magic in this file is adapted/copied almost wholesale from
11232 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
11233 * which was adapted from Apple code (which probably came from
11234 * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
11235 * Special thanks to Apple and Thomas Fuchs for much of this code.
11236 */
11237
11238/**
11239 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
11240 *
11241 * Redistribution and use in source and binary forms, with or without
11242 * modification, are permitted provided that the following conditions are met:
11243 *
11244 * 1. Redistributions of source code must retain the above copyright notice,
11245 * this list of conditions and the following disclaimer.
11246 *
11247 * 2. Redistributions in binary form must reproduce the above copyright notice,
11248 * this list of conditions and the following disclaimer in the documentation
11249 * and/or other materials provided with the distribution.
11250 *
11251 * 3. Neither the name of the copyright holder(s) nor the names of any
11252 * contributors may be used to endorse or promote products derived from
11253 * this software without specific prior written permission.
11254 *
11255 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
11256 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
11257 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
11258 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
11259 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
11260 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
11261 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
11262 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
11263 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
11264 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
11265 * POSSIBILITY OF SUCH DAMAGE.
11266 */
11267;(function () {
11268 // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
11269 function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
11270 var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
11271 function sampleCurveX(t) {
11272 return ((ax * t + bx) * t + cx) * t;
11273 }
11274 function sampleCurveY(t) {
11275 return ((ay * t + by) * t + cy) * t;
11276 }
11277 function sampleCurveDerivativeX(t) {
11278 return (3.0 * ax * t + 2.0 * bx) * t + cx;
11279 }
11280 function solveEpsilon(duration) {
11281 return 1.0 / (200.0 * duration);
11282 }
11283 function solve(x,epsilon) {
11284 return sampleCurveY(solveCurveX(x, epsilon));
11285 }
11286 function fabs(n) {
11287 if (n >= 0) {
11288 return n;
11289 } else {
11290 return 0 - n;
11291 }
11292 }
11293 function solveCurveX(x, epsilon) {
11294 var t0,t1,t2,x2,d2,i;
11295 for (t2 = x, i = 0; i < 8; i++) {
11296 x2 = sampleCurveX(t2) - x;
11297 if (fabs(x2) < epsilon) {
11298 return t2;
11299 }
11300 d2 = sampleCurveDerivativeX(t2);
11301 if (fabs(d2) < 1e-6) {
11302 break;
11303 }
11304 t2 = t2 - x2 / d2;
11305 }
11306 t0 = 0.0;
11307 t1 = 1.0;
11308 t2 = x;
11309 if (t2 < t0) {
11310 return t0;
11311 }
11312 if (t2 > t1) {
11313 return t1;
11314 }
11315 while (t0 < t1) {
11316 x2 = sampleCurveX(t2);
11317 if (fabs(x2 - x) < epsilon) {
11318 return t2;
11319 }
11320 if (x > x2) {
11321 t0 = t2;
11322 }else {
11323 t1 = t2;
11324 }
11325 t2 = (t1 - t0) * 0.5 + t0;
11326 }
11327 return t2; // Failure.
11328 }
11329 cx = 3.0 * p1x;
11330 bx = 3.0 * (p2x - p1x) - cx;
11331 ax = 1.0 - cx - bx;
11332 cy = 3.0 * p1y;
11333 by = 3.0 * (p2y - p1y) - cy;
11334 ay = 1.0 - cy - by;
11335 return solve(t, solveEpsilon(duration));
11336 }
11337 /**
11338 * getCubicBezierTransition(x1, y1, x2, y2) -> Function
11339 *
11340 * Generates a transition easing function that is compatible
11341 * with WebKit's CSS transitions `-webkit-transition-timing-function`
11342 * CSS property.
11343 *
11344 * The W3C has more information about CSS3 transition timing functions:
11345 * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
11346 *
11347 * @param {number} x1
11348 * @param {number} y1
11349 * @param {number} x2
11350 * @param {number} y2
11351 * @return {function}
11352 * @private
11353 */
11354 function getCubicBezierTransition (x1, y1, x2, y2) {
11355 return function (pos) {
11356 return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
11357 };
11358 }
11359 // End ported code
11360
11361 /**
11362 * Create a Bezier easing function and attach it to `{{#crossLink
11363 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. This
11364 * function gives you total control over the easing curve. Matthew Lein's
11365 * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
11366 * the curves you can make with this function.
11367 * @method setBezierFunction
11368 * @param {string} name The name of the easing curve. Overwrites the old
11369 * easing function on `{{#crossLink
11370 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
11371 * exists.
11372 * @param {number} x1
11373 * @param {number} y1
11374 * @param {number} x2
11375 * @param {number} y2
11376 * @return {function} The easing function that was attached to
11377 * NGTweenable.prototype.formula.
11378 */
11379 NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
11380 var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
11381 cubicBezierTransition.displayName = name;
11382 cubicBezierTransition.x1 = x1;
11383 cubicBezierTransition.y1 = y1;
11384 cubicBezierTransition.x2 = x2;
11385 cubicBezierTransition.y2 = y2;
11386
11387 return NGTweenable.prototype.formula[name] = cubicBezierTransition;
11388 };
11389
11390
11391 /**
11392 * `delete` an easing function from `{{#crossLink
11393 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. Be
11394 * careful with this method, as it `delete`s whatever easing formula matches
11395 * `name` (which means you can delete standard Shifty easing functions).
11396 * @method unsetBezierFunction
11397 * @param {string} name The name of the easing function to delete.
11398 * @return {function}
11399 */
11400 NGTweenable.unsetBezierFunction = function (name) {
11401 delete NGTweenable.prototype.formula[name];
11402 };
11403
11404})();
11405
11406;(function () {
11407
11408 function getInterpolatedValues (
11409 from, current, targetState, position, easing, delay) {
11410 return NGTweenable.tweenProps(
11411 position, current, from, targetState, 1, delay, easing);
11412 }
11413
11414 // Fake a NGTweenable and patch some internals. This approach allows us to
11415 // skip uneccessary processing and object recreation, cutting down on garbage
11416 // collection pauses.
11417 var mockNGTweenable = new NGTweenable();
11418 mockNGTweenable._filterArgs = [];
11419
11420 /**
11421 * Compute the midpoint of two Objects. This method effectively calculates a
11422 * specific frame of animation that `{{#crossLink
11423 * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
11424 * of a full tween.
11425 *
11426 * var interpolatedValues = NGTweenable.interpolate({
11427 * width: '100px',
11428 * opacity: 0,
11429 * color: '#fff'
11430 * }, {
11431 * width: '200px',
11432 * opacity: 1,
11433 * color: '#000'
11434 * }, 0.5);
11435 *
11436 * console.log(interpolatedValues);
11437 * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
11438 *
11439 * @static
11440 * @method interpolate
11441 * @param {Object} from The starting values to tween from.
11442 * @param {Object} targetState The ending values to tween to.
11443 * @param {number} position The normalized position value (between `0.0` and
11444 * `1.0`) to interpolate the values between `from` and `to` for. `from`
11445 * represents `0` and `to` represents `1`.
11446 * @param {Object.<string|Function>|string|Function} easing The easing
11447 * curve(s) to calculate the midpoint against. You can reference any easing
11448 * function attached to `NGTweenable.prototype.formula`, or provide the easing
11449 * function(s) directly. If omitted, this defaults to "linear".
11450 * @param {number=} opt_delay Optional delay to pad the beginning of the
11451 * interpolated tween with. This increases the range of `position` from (`0`
11452 * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would
11453 * increase all valid values of `position` to numbers between `0` and `1.5`.
11454 * @return {Object}
11455 */
11456 NGTweenable.interpolate = function (
11457 from, targetState, position, easing, opt_delay) {
11458
11459 var current = NGTweenable.shallowCopy({}, from);
11460 var delay = opt_delay || 0;
11461 var easingObject = NGTweenable.composeEasingObject(
11462 from, easing || 'linear');
11463
11464 mockNGTweenable.set({});
11465
11466 // Alias and reuse the _filterArgs array instead of recreating it.
11467 var filterArgs = mockNGTweenable._filterArgs;
11468 filterArgs.length = 0;
11469 filterArgs[0] = current;
11470 filterArgs[1] = from;
11471 filterArgs[2] = targetState;
11472 filterArgs[3] = easingObject;
11473
11474 // Any defined value transformation must be applied
11475 NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
11476 NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
11477
11478 var interpolatedValues = getInterpolatedValues(
11479 from, current, targetState, position, easingObject, delay);
11480
11481 // Transform values back into their original format
11482 NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
11483
11484 return interpolatedValues;
11485 };
11486
11487}());
11488
11489/**
11490 * This module adds string interpolation support to Shifty.
11491 *
11492 * The Token extension allows Shifty to tween numbers inside of strings. Among
11493 * other things, this allows you to animate CSS properties. For example, you
11494 * can do this:
11495 *
11496 * var tweenable = new NGTweenable();
11497 * tweenable.tween({
11498 * from: { transform: 'translateX(45px)' },
11499 * to: { transform: 'translateX(90xp)' }
11500 * });
11501 *
11502 * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate:
11503 *
11504 * var tweenable = new NGTweenable();
11505 * tweenable.tween({
11506 * from: { transform: 'translateX(45px)' },
11507 * to: { transform: 'translateX(90px)' },
11508 * step: function (state) {
11509 * console.log(state.transform);
11510 * }
11511 * });
11512 *
11513 * The above snippet will log something like this in the console:
11514 *
11515 * translateX(60.3px)
11516 * ...
11517 * translateX(76.05px)
11518 * ...
11519 * translateX(90px)
11520 *
11521 * Another use for this is animating colors:
11522 *
11523 * var tweenable = new NGTweenable();
11524 * tweenable.tween({
11525 * from: { color: 'rgb(0,255,0)' },
11526 * to: { color: 'rgb(255,0,255)' },
11527 * step: function (state) {
11528 * console.log(state.color);
11529 * }
11530 * });
11531 *
11532 * The above snippet will log something like this:
11533 *
11534 * rgb(84,170,84)
11535 * ...
11536 * rgb(170,84,170)
11537 * ...
11538 * rgb(255,0,255)
11539 *
11540 * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
11541 * and short (`#f0f`) forms. Be aware that hexadecimal input values will be
11542 * converted into the equivalent RGB output values. This is done to optimize
11543 * for performance.
11544 *
11545 * var tweenable = new NGTweenable();
11546 * tweenable.tween({
11547 * from: { color: '#0f0' },
11548 * to: { color: '#f0f' },
11549 * step: function (state) {
11550 * console.log(state.color);
11551 * }
11552 * });
11553 *
11554 * This snippet will generate the same output as the one before it because
11555 * equivalent values were supplied (just in hexadecimal form rather than RGB):
11556 *
11557 * rgb(84,170,84)
11558 * ...
11559 * rgb(170,84,170)
11560 * ...
11561 * rgb(255,0,255)
11562 *
11563 * ## Easing support
11564 *
11565 * Easing works somewhat differently in the Token extension. This is because
11566 * some CSS properties have multiple values in them, and you might need to
11567 * tween each value along its own easing curve. A basic example:
11568 *
11569 * var tweenable = new NGTweenable();
11570 * tweenable.tween({
11571 * from: { transform: 'translateX(0px) translateY(0px)' },
11572 * to: { transform: 'translateX(100px) translateY(100px)' },
11573 * easing: { transform: 'easeInQuad' },
11574 * step: function (state) {
11575 * console.log(state.transform);
11576 * }
11577 * });
11578 *
11579 * The above snippet will create values like this:
11580 *
11581 * translateX(11.56px) translateY(11.56px)
11582 * ...
11583 * translateX(46.24px) translateY(46.24px)
11584 * ...
11585 * translateX(100px) translateY(100px)
11586 *
11587 * In this case, the values for `translateX` and `translateY` are always the
11588 * same for each step of the tween, because they have the same start and end
11589 * points and both use the same easing curve. We can also tween `translateX`
11590 * and `translateY` along independent curves:
11591 *
11592 * var tweenable = new NGTweenable();
11593 * tweenable.tween({
11594 * from: { transform: 'translateX(0px) translateY(0px)' },
11595 * to: { transform: 'translateX(100px) translateY(100px)' },
11596 * easing: { transform: 'easeInQuad bounce' },
11597 * step: function (state) {
11598 * console.log(state.transform);
11599 * }
11600 * });
11601 *
11602 * The above snippet will create values like this:
11603 *
11604 * translateX(10.89px) translateY(82.35px)
11605 * ...
11606 * translateX(44.89px) translateY(86.73px)
11607 * ...
11608 * translateX(100px) translateY(100px)
11609 *
11610 * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
11611 * was specified for `translateX` and `bounce` for `translateY`. Mixing and
11612 * matching easing curves can make for some interesting motion in your
11613 * animations.
11614 *
11615 * The order of the space-separated easing curves correspond the token values
11616 * they apply to. If there are more token values than easing curves listed,
11617 * the last easing curve listed is used.
11618 * @submodule NGTweenable.token
11619 */
11620
11621// token function is defined above only so that dox-foundation sees it as
11622// documentation and renders it. It is never used, and is optimized away at
11623// build time.
11624
11625;(function (NGTweenable) {
11626
11627 /**
11628 * @typedef {{
11629 * formatString: string
11630 * chunkNames: Array.<string>
11631 * }}
11632 * @private
11633 */
11634 var formatManifest;
11635
11636 // CONSTANTS
11637
11638 var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
11639 var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
11640 var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
11641 var R_RGB = new RegExp(
11642 'rgb\\(' + R_UNFORMATTED_VALUES.source +
11643 (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
11644 (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
11645 var R_RGB_PREFIX = /^.*\(/;
11646 var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
11647 var VALUE_PLACEHOLDER = 'VAL';
11648
11649 // HELPERS
11650
11651 /**
11652 * @param {Array.number} rawValues
11653 * @param {string} prefix
11654 *
11655 * @return {Array.<string>}
11656 * @private
11657 */
11658 function getFormatChunksFrom (rawValues, prefix) {
11659 var accumulator = [];
11660
11661 var rawValuesLength = rawValues.length;
11662 var i;
11663
11664 for (i = 0; i < rawValuesLength; i++) {
11665 accumulator.push('_' + prefix + '_' + i);
11666 }
11667
11668 return accumulator;
11669 }
11670
11671 /**
11672 * @param {string} formattedString
11673 *
11674 * @return {string}
11675 * @private
11676 */
11677 function getFormatStringFrom (formattedString) {
11678 var chunks = formattedString.match(R_FORMAT_CHUNKS);
11679
11680 if (!chunks) {
11681 // chunks will be null if there were no tokens to parse in
11682 // formattedString (for example, if formattedString is '2'). Coerce
11683 // chunks to be useful here.
11684 chunks = ['', ''];
11685
11686 // If there is only one chunk, assume that the string is a number
11687 // followed by a token...
11688 // NOTE: This may be an unwise assumption.
11689 } else if (chunks.length === 1 ||
11690 // ...or if the string starts with a number component (".", "-", or a
11691 // digit)...
11692 formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
11693 // ...prepend an empty string here to make sure that the formatted number
11694 // is properly replaced by VALUE_PLACEHOLDER
11695 chunks.unshift('');
11696 }
11697
11698 return chunks.join(VALUE_PLACEHOLDER);
11699 }
11700
11701 /**
11702 * Convert all hex color values within a string to an rgb string.
11703 *
11704 * @param {Object} stateObject
11705 *
11706 * @return {Object} The modified obj
11707 * @private
11708 */
11709 function sanitizeObjectForHexProps (stateObject) {
11710 NGTweenable.each(stateObject, function (prop) {
11711 var currentProp = stateObject[prop];
11712
11713 if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
11714 stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
11715 }
11716 });
11717 }
11718
11719 /**
11720 * @param {string} str
11721 *
11722 * @return {string}
11723 * @private
11724 */
11725 function sanitizeHexChunksToRGB (str) {
11726 return filterStringChunks(R_HEX, str, convertHexToRGB);
11727 }
11728
11729 /**
11730 * @param {string} hexString
11731 *
11732 * @return {string}
11733 * @private
11734 */
11735 function convertHexToRGB (hexString) {
11736 var rgbArr = hexToRGBArray(hexString);
11737 return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
11738 }
11739
11740 var hexToRGBArray_returnArray = [];
11741 /**
11742 * Convert a hexadecimal string to an array with three items, one each for
11743 * the red, blue, and green decimal values.
11744 *
11745 * @param {string} hex A hexadecimal string.
11746 *
11747 * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
11748 * valid string, or an Array of three 0's.
11749 * @private
11750 */
11751 function hexToRGBArray (hex) {
11752
11753 hex = hex.replace(/#/, '');
11754
11755 // If the string is a shorthand three digit hex notation, normalize it to
11756 // the standard six digit notation
11757 if (hex.length === 3) {
11758 hex = hex.split('');
11759 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
11760 }
11761
11762 hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
11763 hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
11764 hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
11765
11766 return hexToRGBArray_returnArray;
11767 }
11768
11769 /**
11770 * Convert a base-16 number to base-10.
11771 *
11772 * @param {Number|String} hex The value to convert
11773 *
11774 * @returns {Number} The base-10 equivalent of `hex`.
11775 * @private
11776 */
11777 function hexToDec (hex) {
11778 return parseInt(hex, 16);
11779 }
11780
11781 /**
11782 * Runs a filter operation on all chunks of a string that match a RegExp
11783 *
11784 * @param {RegExp} pattern
11785 * @param {string} unfilteredString
11786 * @param {function(string)} filter
11787 *
11788 * @return {string}
11789 * @private
11790 */
11791 function filterStringChunks (pattern, unfilteredString, filter) {
11792 var pattenMatches = unfilteredString.match(pattern);
11793 var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
11794
11795 if (pattenMatches) {
11796 var pattenMatchesLength = pattenMatches.length;
11797 var currentChunk;
11798
11799 for (var i = 0; i < pattenMatchesLength; i++) {
11800 currentChunk = pattenMatches.shift();
11801 filteredString = filteredString.replace(
11802 VALUE_PLACEHOLDER, filter(currentChunk));
11803 }
11804 }
11805
11806 return filteredString;
11807 }
11808
11809 /**
11810 * Check for floating point values within rgb strings and rounds them.
11811 *
11812 * @param {string} formattedString
11813 *
11814 * @return {string}
11815 * @private
11816 */
11817 function sanitizeRGBChunks (formattedString) {
11818 return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
11819 }
11820
11821 /**
11822 * @param {string} rgbChunk
11823 *
11824 * @return {string}
11825 * @private
11826 */
11827 function sanitizeRGBChunk (rgbChunk) {
11828 var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
11829 var numbersLength = numbers.length;
11830 var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
11831
11832 for (var i = 0; i < numbersLength; i++) {
11833 sanitizedString += parseInt(numbers[i], 10) + ',';
11834 }
11835
11836 sanitizedString = sanitizedString.slice(0, -1) + ')';
11837
11838 return sanitizedString;
11839 }
11840
11841 /**
11842 * @param {Object} stateObject
11843 *
11844 * @return {Object} An Object of formatManifests that correspond to
11845 * the string properties of stateObject
11846 * @private
11847 */
11848 function getFormatManifests (stateObject) {
11849 var manifestAccumulator = {};
11850
11851 NGTweenable.each(stateObject, function (prop) {
11852 var currentProp = stateObject[prop];
11853
11854 if (typeof currentProp === 'string') {
11855 var rawValues = getValuesFrom(currentProp);
11856
11857 manifestAccumulator[prop] = {
11858 'formatString': getFormatStringFrom(currentProp)
11859 ,'chunkNames': getFormatChunksFrom(rawValues, prop)
11860 };
11861 }
11862 });
11863
11864 return manifestAccumulator;
11865 }
11866
11867 /**
11868 * @param {Object} stateObject
11869 * @param {Object} formatManifests
11870 * @private
11871 */
11872 function expandFormattedProperties (stateObject, formatManifests) {
11873 NGTweenable.each(formatManifests, function (prop) {
11874 var currentProp = stateObject[prop];
11875 var rawValues = getValuesFrom(currentProp);
11876 var rawValuesLength = rawValues.length;
11877
11878 for (var i = 0; i < rawValuesLength; i++) {
11879 stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
11880 }
11881
11882 delete stateObject[prop];
11883 });
11884 }
11885
11886 /**
11887 * @param {Object} stateObject
11888 * @param {Object} formatManifests
11889 * @private
11890 */
11891 function collapseFormattedProperties (stateObject, formatManifests) {
11892 NGTweenable.each(formatManifests, function (prop) {
11893 var currentProp = stateObject[prop];
11894 var formatChunks = extractPropertyChunks(
11895 stateObject, formatManifests[prop].chunkNames);
11896 var valuesList = getValuesList(
11897 formatChunks, formatManifests[prop].chunkNames);
11898 currentProp = getFormattedValues(
11899 formatManifests[prop].formatString, valuesList);
11900 stateObject[prop] = sanitizeRGBChunks(currentProp);
11901 });
11902 }
11903
11904 /**
11905 * @param {Object} stateObject
11906 * @param {Array.<string>} chunkNames
11907 *
11908 * @return {Object} The extracted value chunks.
11909 * @private
11910 */
11911 function extractPropertyChunks (stateObject, chunkNames) {
11912 var extractedValues = {};
11913 var currentChunkName, chunkNamesLength = chunkNames.length;
11914
11915 for (var i = 0; i < chunkNamesLength; i++) {
11916 currentChunkName = chunkNames[i];
11917 extractedValues[currentChunkName] = stateObject[currentChunkName];
11918 delete stateObject[currentChunkName];
11919 }
11920
11921 return extractedValues;
11922 }
11923
11924 var getValuesList_accumulator = [];
11925 /**
11926 * @param {Object} stateObject
11927 * @param {Array.<string>} chunkNames
11928 *
11929 * @return {Array.<number>}
11930 * @private
11931 */
11932 function getValuesList (stateObject, chunkNames) {
11933 getValuesList_accumulator.length = 0;
11934 var chunkNamesLength = chunkNames.length;
11935
11936 for (var i = 0; i < chunkNamesLength; i++) {
11937 getValuesList_accumulator.push(stateObject[chunkNames[i]]);
11938 }
11939
11940 return getValuesList_accumulator;
11941 }
11942
11943 /**
11944 * @param {string} formatString
11945 * @param {Array.<number>} rawValues
11946 *
11947 * @return {string}
11948 * @private
11949 */
11950 function getFormattedValues (formatString, rawValues) {
11951 var formattedValueString = formatString;
11952 var rawValuesLength = rawValues.length;
11953
11954 for (var i = 0; i < rawValuesLength; i++) {
11955 formattedValueString = formattedValueString.replace(
11956 VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
11957 }
11958
11959 return formattedValueString;
11960 }
11961
11962 /**
11963 * Note: It's the duty of the caller to convert the Array elements of the
11964 * return value into numbers. This is a performance optimization.
11965 *
11966 * @param {string} formattedString
11967 *
11968 * @return {Array.<string>|null}
11969 * @private
11970 */
11971 function getValuesFrom (formattedString) {
11972 return formattedString.match(R_UNFORMATTED_VALUES);
11973 }
11974
11975 /**
11976 * @param {Object} easingObject
11977 * @param {Object} tokenData
11978 * @private
11979 */
11980 function expandEasingObject (easingObject, tokenData) {
11981 NGTweenable.each(tokenData, function (prop) {
11982 var currentProp = tokenData[prop];
11983 var chunkNames = currentProp.chunkNames;
11984 var chunkLength = chunkNames.length;
11985
11986 var easing = easingObject[prop];
11987 var i;
11988
11989 if (typeof easing === 'string') {
11990 var easingChunks = easing.split(' ');
11991 var lastEasingChunk = easingChunks[easingChunks.length - 1];
11992
11993 for (i = 0; i < chunkLength; i++) {
11994 easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
11995 }
11996
11997 } else {
11998 for (i = 0; i < chunkLength; i++) {
11999 easingObject[chunkNames[i]] = easing;
12000 }
12001 }
12002
12003 delete easingObject[prop];
12004 });
12005 }
12006
12007 /**
12008 * @param {Object} easingObject
12009 * @param {Object} tokenData
12010 * @private
12011 */
12012 function collapseEasingObject (easingObject, tokenData) {
12013 NGTweenable.each(tokenData, function (prop) {
12014 var currentProp = tokenData[prop];
12015 var chunkNames = currentProp.chunkNames;
12016 var chunkLength = chunkNames.length;
12017
12018 var firstEasing = easingObject[chunkNames[0]];
12019 var typeofEasings = typeof firstEasing;
12020
12021 if (typeofEasings === 'string') {
12022 var composedEasingString = '';
12023
12024 for (var i = 0; i < chunkLength; i++) {
12025 composedEasingString += ' ' + easingObject[chunkNames[i]];
12026 delete easingObject[chunkNames[i]];
12027 }
12028
12029 easingObject[prop] = composedEasingString.substr(1);
12030 } else {
12031 easingObject[prop] = firstEasing;
12032 }
12033 });
12034 }
12035
12036 NGTweenable.prototype.filter.token = {
12037 'tweenCreated': function (currentState, fromState, toState, easingObject) {
12038 sanitizeObjectForHexProps(currentState);
12039 sanitizeObjectForHexProps(fromState);
12040 sanitizeObjectForHexProps(toState);
12041 this._tokenData = getFormatManifests(currentState);
12042 },
12043
12044 'beforeTween': function (currentState, fromState, toState, easingObject) {
12045 expandEasingObject(easingObject, this._tokenData);
12046 expandFormattedProperties(currentState, this._tokenData);
12047 expandFormattedProperties(fromState, this._tokenData);
12048 expandFormattedProperties(toState, this._tokenData);
12049 },
12050
12051 'afterTween': function (currentState, fromState, toState, easingObject) {
12052 collapseFormattedProperties(currentState, this._tokenData);
12053 collapseFormattedProperties(fromState, this._tokenData);
12054 collapseFormattedProperties(toState, this._tokenData);
12055 collapseEasingObject(easingObject, this._tokenData);
12056 }
12057 };
12058
12059} (NGTweenable));
12060
12061}).call(null);
12062
12063
12064
12065
12066//##########################################################################################################################
12067//## HAMMER.JS #############################################################################################################
12068//##########################################################################################################################
12069
12070// HAMMER.JS
12071
12072// external module EMBEDED in nanogallery
12073// NGY BUILD:
12074// replace "Hammer" with "NGHammer" (case sensitive)
12075// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
12076// replace "define.amd" with "define.amdDISABLED"
12077
12078
12079
12080/*! NGHammer.JS - v2.0.7 - 2016-04-22
12081 * http://hammerjs.github.io/
12082 *
12083 * Copyright (c) 2016 Jorik Tangelder;
12084 * Licensed under the MIT license */
12085(function(window, document, exportName, undefined) {
12086 'use strict';
12087
12088var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
12089var TEST_ELEMENT = document.createElement('div');
12090
12091var TYPE_FUNCTION = 'function';
12092
12093var round = Math.round;
12094var abs = Math.abs;
12095var now = Date.now;
12096
12097/**
12098 * set a timeout with a given scope
12099 * @param {Function} fn
12100 * @param {Number} timeout
12101 * @param {Object} context
12102 * @returns {number}
12103 */
12104function setTimeoutContext(fn, timeout, context) {
12105 return setTimeout(bindFn(fn, context), timeout);
12106}
12107
12108/**
12109 * if the argument is an array, we want to execute the fn on each entry
12110 * if it aint an array we don't want to do a thing.
12111 * this is used by all the methods that accept a single and array argument.
12112 * @param {*|Array} arg
12113 * @param {String} fn
12114 * @param {Object} [context]
12115 * @returns {Boolean}
12116 */
12117function invokeArrayArg(arg, fn, context) {
12118 if (Array.isArray(arg)) {
12119 each(arg, context[fn], context);
12120 return true;
12121 }
12122 return false;
12123}
12124
12125/**
12126 * walk objects and arrays
12127 * @param {Object} obj
12128 * @param {Function} iterator
12129 * @param {Object} context
12130 */
12131function each(obj, iterator, context) {
12132 var i;
12133
12134 if (!obj) {
12135 return;
12136 }
12137
12138 if (obj.forEach) {
12139 obj.forEach(iterator, context);
12140 } else if (obj.length !== undefined) {
12141 i = 0;
12142 while (i < obj.length) {
12143 iterator.call(context, obj[i], i, obj);
12144 i++;
12145 }
12146 } else {
12147 for (i in obj) {
12148 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
12149 }
12150 }
12151}
12152
12153/**
12154 * wrap a method with a deprecation warning and stack trace
12155 * @param {Function} method
12156 * @param {String} name
12157 * @param {String} message
12158 * @returns {Function} A new function wrapping the supplied method.
12159 */
12160function deprecate(method, name, message) {
12161 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
12162 return function() {
12163 var e = new Error('get-stack-trace');
12164 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
12165 .replace(/^\s+at\s+/gm, '')
12166 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
12167
12168 var log = window.console && (window.console.warn || window.console.log);
12169 if (log) {
12170 log.call(window.console, deprecationMessage, stack);
12171 }
12172 return method.apply(this, arguments);
12173 };
12174}
12175
12176/**
12177 * extend object.
12178 * means that properties in dest will be overwritten by the ones in src.
12179 * @param {Object} target
12180 * @param {...Object} objects_to_assign
12181 * @returns {Object} target
12182 */
12183var assign;
12184if (typeof Object.assign !== 'function') {
12185 assign = function assign(target) {
12186 if (target === undefined || target === null) {
12187 throw new TypeError('Cannot convert undefined or null to object');
12188 }
12189
12190 var output = Object(target);
12191 for (var index = 1; index < arguments.length; index++) {
12192 var source = arguments[index];
12193 if (source !== undefined && source !== null) {
12194 for (var nextKey in source) {
12195 if (source.hasOwnProperty(nextKey)) {
12196 output[nextKey] = source[nextKey];
12197 }
12198 }
12199 }
12200 }
12201 return output;
12202 };
12203} else {
12204 assign = Object.assign;
12205}
12206
12207/**
12208 * extend object.
12209 * means that properties in dest will be overwritten by the ones in src.
12210 * @param {Object} dest
12211 * @param {Object} src
12212 * @param {Boolean} [merge=false]
12213 * @returns {Object} dest
12214 */
12215var extend = deprecate(function extend(dest, src, merge) {
12216 var keys = Object.keys(src);
12217 var i = 0;
12218 while (i < keys.length) {
12219 if (!merge || (merge && dest[keys[i]] === undefined)) {
12220 dest[keys[i]] = src[keys[i]];
12221 }
12222 i++;
12223 }
12224 return dest;
12225}, 'extend', 'Use `assign`.');
12226
12227/**
12228 * merge the values from src in the dest.
12229 * means that properties that exist in dest will not be overwritten by src
12230 * @param {Object} dest
12231 * @param {Object} src
12232 * @returns {Object} dest
12233 */
12234var merge = deprecate(function merge(dest, src) {
12235 return extend(dest, src, true);
12236}, 'merge', 'Use `assign`.');
12237
12238/**
12239 * simple class inheritance
12240 * @param {Function} child
12241 * @param {Function} base
12242 * @param {Object} [properties]
12243 */
12244function inherit(child, base, properties) {
12245 var baseP = base.prototype,
12246 childP;
12247
12248 childP = child.prototype = Object.create(baseP);
12249 childP.constructor = child;
12250 childP._super = baseP;
12251
12252 if (properties) {
12253 assign(childP, properties);
12254 }
12255}
12256
12257/**
12258 * simple function bind
12259 * @param {Function} fn
12260 * @param {Object} context
12261 * @returns {Function}
12262 */
12263function bindFn(fn, context) {
12264 return function boundFn() {
12265 return fn.apply(context, arguments);
12266 };
12267}
12268
12269/**
12270 * let a boolean value also be a function that must return a boolean
12271 * this first item in args will be used as the context
12272 * @param {Boolean|Function} val
12273 * @param {Array} [args]
12274 * @returns {Boolean}
12275 */
12276function boolOrFn(val, args) {
12277 if (typeof val == TYPE_FUNCTION) {
12278 return val.apply(args ? args[0] || undefined : undefined, args);
12279 }
12280 return val;
12281}
12282
12283/**
12284 * use the val2 when val1 is undefined
12285 * @param {*} val1
12286 * @param {*} val2
12287 * @returns {*}
12288 */
12289function ifUndefined(val1, val2) {
12290 return (val1 === undefined) ? val2 : val1;
12291}
12292
12293/**
12294 * addEventListener with multiple events at once
12295 * @param {EventTarget} target
12296 * @param {String} types
12297 * @param {Function} handler
12298 */
12299function addEventListeners(target, types, handler) {
12300 each(splitStr(types), function(type) {
12301 target.addEventListener(type, handler, false);
12302 });
12303}
12304
12305/**
12306 * removeEventListener with multiple events at once
12307 * @param {EventTarget} target
12308 * @param {String} types
12309 * @param {Function} handler
12310 */
12311function removeEventListeners(target, types, handler) {
12312 each(splitStr(types), function(type) {
12313 target.removeEventListener(type, handler, false);
12314 });
12315}
12316
12317/**
12318 * find if a node is in the given parent
12319 * @method hasParent
12320 * @param {HTMLElement} node
12321 * @param {HTMLElement} parent
12322 * @return {Boolean} found
12323 */
12324function hasParent(node, parent) {
12325 while (node) {
12326 if (node == parent) {
12327 return true;
12328 }
12329 node = node.parentNode;
12330 }
12331 return false;
12332}
12333
12334/**
12335 * small indexOf wrapper
12336 * @param {String} str
12337 * @param {String} find
12338 * @returns {Boolean} found
12339 */
12340function inStr(str, find) {
12341 return str.indexOf(find) > -1;
12342}
12343
12344/**
12345 * split string on whitespace
12346 * @param {String} str
12347 * @returns {Array} words
12348 */
12349function splitStr(str) {
12350 return str.trim().split(/\s+/g);
12351}
12352
12353/**
12354 * find if a array contains the object using indexOf or a simple polyFill
12355 * @param {Array} src
12356 * @param {String} find
12357 * @param {String} [findByKey]
12358 * @return {Boolean|Number} false when not found, or the index
12359 */
12360function inArray(src, find, findByKey) {
12361 if (src.indexOf && !findByKey) {
12362 return src.indexOf(find);
12363 } else {
12364 var i = 0;
12365 while (i < src.length) {
12366 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
12367 return i;
12368 }
12369 i++;
12370 }
12371 return -1;
12372 }
12373}
12374
12375/**
12376 * convert array-like objects to real arrays
12377 * @param {Object} obj
12378 * @returns {Array}
12379 */
12380function toArray(obj) {
12381 return Array.prototype.slice.call(obj, 0);
12382}
12383
12384/**
12385 * unique array with objects based on a key (like 'id') or just by the array's value
12386 * @param {Array} src [{id:1},{id:2},{id:1}]
12387 * @param {String} [key]
12388 * @param {Boolean} [sort=False]
12389 * @returns {Array} [{id:1},{id:2}]
12390 */
12391function uniqueArray(src, key, sort) {
12392 var results = [];
12393 var values = [];
12394 var i = 0;
12395
12396 while (i < src.length) {
12397 var val = key ? src[i][key] : src[i];
12398 if (inArray(values, val) < 0) {
12399 results.push(src[i]);
12400 }
12401 values[i] = val;
12402 i++;
12403 }
12404
12405 if (sort) {
12406 if (!key) {
12407 results = results.sort();
12408 } else {
12409 results = results.sort(function sortUniqueArray(a, b) {
12410 return a[key] > b[key];
12411 });
12412 }
12413 }
12414
12415 return results;
12416}
12417
12418/**
12419 * get the prefixed property
12420 * @param {Object} obj
12421 * @param {String} property
12422 * @returns {String|Undefined} prefixed
12423 */
12424function prefixed(obj, property) {
12425 var prefix, prop;
12426 var camelProp = property[0].toUpperCase() + property.slice(1);
12427
12428 var i = 0;
12429 while (i < VENDOR_PREFIXES.length) {
12430 prefix = VENDOR_PREFIXES[i];
12431 prop = (prefix) ? prefix + camelProp : property;
12432
12433 if (prop in obj) {
12434 return prop;
12435 }
12436 i++;
12437 }
12438 return undefined;
12439}
12440
12441/**
12442 * get a unique id
12443 * @returns {number} uniqueId
12444 */
12445var _uniqueId = 1;
12446function uniqueId() {
12447 return _uniqueId++;
12448}
12449
12450/**
12451 * get the window object of an element
12452 * @param {HTMLElement} element
12453 * @returns {DocumentView|Window}
12454 */
12455function getWindowForElement(element) {
12456 var doc = element.ownerDocument || element;
12457 return (doc.defaultView || doc.parentWindow || window);
12458}
12459
12460var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
12461
12462var SUPPORT_TOUCH = ('ontouchstart' in window);
12463// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
12464var SUPPORT_POINTER_EVENTS = false;
12465var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
12466
12467var INPUT_TYPE_TOUCH = 'touch';
12468var INPUT_TYPE_PEN = 'pen';
12469var INPUT_TYPE_MOUSE = 'mouse';
12470var INPUT_TYPE_KINECT = 'kinect';
12471
12472var COMPUTE_INTERVAL = 25;
12473
12474var INPUT_START = 1;
12475var INPUT_MOVE = 2;
12476var INPUT_END = 4;
12477var INPUT_CANCEL = 8;
12478
12479var DIRECTION_NONE = 1;
12480var DIRECTION_LEFT = 2;
12481var DIRECTION_RIGHT = 4;
12482var DIRECTION_UP = 8;
12483var DIRECTION_DOWN = 16;
12484
12485var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
12486var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
12487var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
12488
12489var PROPS_XY = ['x', 'y'];
12490var PROPS_CLIENT_XY = ['clientX', 'clientY'];
12491
12492/**
12493 * create new input type manager
12494 * @param {Manager} manager
12495 * @param {Function} callback
12496 * @returns {Input}
12497 * @constructor
12498 */
12499function Input(manager, callback) {
12500 var self = this;
12501 this.manager = manager;
12502 this.callback = callback;
12503 this.element = manager.element;
12504 this.target = manager.options.inputTarget;
12505
12506 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
12507 // so when disabled the input events are completely bypassed.
12508 this.domHandler = function(ev) {
12509 if (boolOrFn(manager.options.enable, [manager])) {
12510 self.handler(ev);
12511 }
12512 };
12513
12514 this.init();
12515
12516}
12517
12518Input.prototype = {
12519 /**
12520 * should handle the inputEvent data and trigger the callback
12521 * @virtual
12522 */
12523 handler: function() { },
12524
12525 /**
12526 * bind the events
12527 */
12528 init: function() {
12529 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
12530 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
12531 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
12532 },
12533
12534 /**
12535 * unbind the events
12536 */
12537 destroy: function() {
12538 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
12539 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
12540 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
12541 }
12542};
12543
12544/**
12545 * create new input type manager
12546 * called by the Manager constructor
12547 * @param {NGHammer} manager
12548 * @returns {Input}
12549 */
12550function createInputInstance(manager) {
12551 var Type;
12552 var inputClass = manager.options.inputClass;
12553
12554 if (inputClass) {
12555 Type = inputClass;
12556 } else if (SUPPORT_POINTER_EVENTS) {
12557 Type = PointerEventInput;
12558 } else if (SUPPORT_ONLY_TOUCH) {
12559 Type = TouchInput;
12560 } else if (!SUPPORT_TOUCH) {
12561 Type = MouseInput;
12562 } else {
12563 Type = TouchMouseInput;
12564 }
12565 return new (Type)(manager, inputHandler);
12566}
12567
12568/**
12569 * handle input events
12570 * @param {Manager} manager
12571 * @param {String} eventType
12572 * @param {Object} input
12573 */
12574function inputHandler(manager, eventType, input) {
12575 var pointersLen = input.pointers.length;
12576 var changedPointersLen = input.changedPointers.length;
12577 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
12578 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
12579
12580 input.isFirst = !!isFirst;
12581 input.isFinal = !!isFinal;
12582
12583 if (isFirst) {
12584 manager.session = {};
12585 }
12586
12587 // source event is the normalized value of the domEvents
12588 // like 'touchstart, mouseup, pointerdown'
12589 input.eventType = eventType;
12590
12591 // compute scale, rotation etc
12592 computeInputData(manager, input);
12593
12594 // emit secret event
12595 manager.emit('hammer.input', input);
12596
12597 manager.recognize(input);
12598 manager.session.prevInput = input;
12599}
12600
12601/**
12602 * extend the data with some usable properties like scale, rotate, velocity etc
12603 * @param {Object} manager
12604 * @param {Object} input
12605 */
12606function computeInputData(manager, input) {
12607 var session = manager.session;
12608 var pointers = input.pointers;
12609 var pointersLength = pointers.length;
12610
12611 // store the first input to calculate the distance and direction
12612 if (!session.firstInput) {
12613 session.firstInput = simpleCloneInputData(input);
12614 }
12615
12616 // to compute scale and rotation we need to store the multiple touches
12617 if (pointersLength > 1 && !session.firstMultiple) {
12618 session.firstMultiple = simpleCloneInputData(input);
12619 } else if (pointersLength === 1) {
12620 session.firstMultiple = false;
12621 }
12622
12623 var firstInput = session.firstInput;
12624 var firstMultiple = session.firstMultiple;
12625 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
12626
12627 var center = input.center = getCenter(pointers);
12628 input.timeStamp = now();
12629 input.deltaTime = input.timeStamp - firstInput.timeStamp;
12630
12631 input.angle = getAngle(offsetCenter, center);
12632 input.distance = getDistance(offsetCenter, center);
12633
12634 computeDeltaXY(session, input);
12635 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
12636
12637 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
12638 input.overallVelocityX = overallVelocity.x;
12639 input.overallVelocityY = overallVelocity.y;
12640 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
12641
12642 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
12643 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
12644
12645 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
12646 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
12647
12648 computeIntervalInputData(session, input);
12649
12650 // find the correct target
12651 var target = manager.element;
12652 if (hasParent(input.srcEvent.target, target)) {
12653 target = input.srcEvent.target;
12654 }
12655 input.target = target;
12656}
12657
12658function computeDeltaXY(session, input) {
12659 var center = input.center;
12660 var offset = session.offsetDelta || {};
12661 var prevDelta = session.prevDelta || {};
12662 var prevInput = session.prevInput || {};
12663
12664 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
12665 prevDelta = session.prevDelta = {
12666 x: prevInput.deltaX || 0,
12667 y: prevInput.deltaY || 0
12668 };
12669
12670 offset = session.offsetDelta = {
12671 x: center.x,
12672 y: center.y
12673 };
12674 }
12675
12676 input.deltaX = prevDelta.x + (center.x - offset.x);
12677 input.deltaY = prevDelta.y + (center.y - offset.y);
12678}
12679
12680/**
12681 * velocity is calculated every x ms
12682 * @param {Object} session
12683 * @param {Object} input
12684 */
12685function computeIntervalInputData(session, input) {
12686 var last = session.lastInterval || input,
12687 deltaTime = input.timeStamp - last.timeStamp,
12688 velocity, velocityX, velocityY, direction;
12689
12690 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
12691 var deltaX = input.deltaX - last.deltaX;
12692 var deltaY = input.deltaY - last.deltaY;
12693
12694 var v = getVelocity(deltaTime, deltaX, deltaY);
12695 velocityX = v.x;
12696 velocityY = v.y;
12697 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
12698 direction = getDirection(deltaX, deltaY);
12699
12700 session.lastInterval = input;
12701 } else {
12702 // use latest velocity info if it doesn't overtake a minimum period
12703 velocity = last.velocity;
12704 velocityX = last.velocityX;
12705 velocityY = last.velocityY;
12706 direction = last.direction;
12707 }
12708
12709 input.velocity = velocity;
12710 input.velocityX = velocityX;
12711 input.velocityY = velocityY;
12712 input.direction = direction;
12713}
12714
12715/**
12716 * create a simple clone from the input used for storage of firstInput and firstMultiple
12717 * @param {Object} input
12718 * @returns {Object} clonedInputData
12719 */
12720function simpleCloneInputData(input) {
12721 // make a simple copy of the pointers because we will get a reference if we don't
12722 // we only need clientXY for the calculations
12723 var pointers = [];
12724 var i = 0;
12725 while (i < input.pointers.length) {
12726 pointers[i] = {
12727 clientX: round(input.pointers[i].clientX),
12728 clientY: round(input.pointers[i].clientY)
12729 };
12730 i++;
12731 }
12732
12733 return {
12734 timeStamp: now(),
12735 pointers: pointers,
12736 center: getCenter(pointers),
12737 deltaX: input.deltaX,
12738 deltaY: input.deltaY
12739 };
12740}
12741
12742/**
12743 * get the center of all the pointers
12744 * @param {Array} pointers
12745 * @return {Object} center contains `x` and `y` properties
12746 */
12747function getCenter(pointers) {
12748 var pointersLength = pointers.length;
12749
12750 // no need to loop when only one touch
12751 if (pointersLength === 1) {
12752 return {
12753 x: round(pointers[0].clientX),
12754 y: round(pointers[0].clientY)
12755 };
12756 }
12757
12758 var x = 0, y = 0, i = 0;
12759 while (i < pointersLength) {
12760 x += pointers[i].clientX;
12761 y += pointers[i].clientY;
12762 i++;
12763 }
12764
12765 return {
12766 x: round(x / pointersLength),
12767 y: round(y / pointersLength)
12768 };
12769}
12770
12771/**
12772 * calculate the velocity between two points. unit is in px per ms.
12773 * @param {Number} deltaTime
12774 * @param {Number} x
12775 * @param {Number} y
12776 * @return {Object} velocity `x` and `y`
12777 */
12778function getVelocity(deltaTime, x, y) {
12779 return {
12780 x: x / deltaTime || 0,
12781 y: y / deltaTime || 0
12782 };
12783}
12784
12785/**
12786 * get the direction between two points
12787 * @param {Number} x
12788 * @param {Number} y
12789 * @return {Number} direction
12790 */
12791function getDirection(x, y) {
12792 if (x === y) {
12793 return DIRECTION_NONE;
12794 }
12795
12796 if (abs(x) >= abs(y)) {
12797 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
12798 }
12799 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
12800}
12801
12802/**
12803 * calculate the absolute distance between two points
12804 * @param {Object} p1 {x, y}
12805 * @param {Object} p2 {x, y}
12806 * @param {Array} [props] containing x and y keys
12807 * @return {Number} distance
12808 */
12809function getDistance(p1, p2, props) {
12810 if (!props) {
12811 props = PROPS_XY;
12812 }
12813 var x = p2[props[0]] - p1[props[0]],
12814 y = p2[props[1]] - p1[props[1]];
12815
12816 return Math.sqrt((x * x) + (y * y));
12817}
12818
12819/**
12820 * calculate the angle between two coordinates
12821 * @param {Object} p1
12822 * @param {Object} p2
12823 * @param {Array} [props] containing x and y keys
12824 * @return {Number} angle
12825 */
12826function getAngle(p1, p2, props) {
12827 if (!props) {
12828 props = PROPS_XY;
12829 }
12830 var x = p2[props[0]] - p1[props[0]],
12831 y = p2[props[1]] - p1[props[1]];
12832 return Math.atan2(y, x) * 180 / Math.PI;
12833}
12834
12835/**
12836 * calculate the rotation degrees between two pointersets
12837 * @param {Array} start array of pointers
12838 * @param {Array} end array of pointers
12839 * @return {Number} rotation
12840 */
12841function getRotation(start, end) {
12842 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
12843}
12844
12845/**
12846 * calculate the scale factor between two pointersets
12847 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
12848 * @param {Array} start array of pointers
12849 * @param {Array} end array of pointers
12850 * @return {Number} scale
12851 */
12852function getScale(start, end) {
12853 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
12854}
12855
12856var MOUSE_INPUT_MAP = {
12857 mousedown: INPUT_START,
12858 mousemove: INPUT_MOVE,
12859 mouseup: INPUT_END
12860};
12861
12862var MOUSE_ELEMENT_EVENTS = 'mousedown';
12863var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
12864
12865/**
12866 * Mouse events input
12867 * @constructor
12868 * @extends Input
12869 */
12870function MouseInput() {
12871 this.evEl = MOUSE_ELEMENT_EVENTS;
12872 this.evWin = MOUSE_WINDOW_EVENTS;
12873
12874 this.pressed = false; // mousedown state
12875
12876 Input.apply(this, arguments);
12877}
12878
12879inherit(MouseInput, Input, {
12880 /**
12881 * handle mouse events
12882 * @param {Object} ev
12883 */
12884 handler: function MEhandler(ev) {
12885 var eventType = MOUSE_INPUT_MAP[ev.type];
12886
12887 // on start we want to have the left mouse button down
12888 if (eventType & INPUT_START && ev.button === 0) {
12889 this.pressed = true;
12890 }
12891
12892 if (eventType & INPUT_MOVE && ev.which !== 1) {
12893 eventType = INPUT_END;
12894 }
12895
12896 // mouse must be down
12897 if (!this.pressed) {
12898 return;
12899 }
12900
12901 if (eventType & INPUT_END) {
12902 this.pressed = false;
12903 }
12904
12905 this.callback(this.manager, eventType, {
12906 pointers: [ev],
12907 changedPointers: [ev],
12908 pointerType: INPUT_TYPE_MOUSE,
12909 srcEvent: ev
12910 });
12911 }
12912});
12913
12914var POINTER_INPUT_MAP = {
12915 pointerdown: INPUT_START,
12916 pointermove: INPUT_MOVE,
12917 pointerup: INPUT_END,
12918 pointercancel: INPUT_CANCEL,
12919 pointerout: INPUT_CANCEL
12920};
12921
12922// in IE10 the pointer types is defined as an enum
12923var IE10_POINTER_TYPE_ENUM = {
12924 2: INPUT_TYPE_TOUCH,
12925 3: INPUT_TYPE_PEN,
12926 4: INPUT_TYPE_MOUSE,
12927 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
12928};
12929
12930var POINTER_ELEMENT_EVENTS = 'pointerdown';
12931var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
12932
12933// IE10 has prefixed support, and case-sensitive
12934if (window.MSPointerEvent && !window.PointerEvent) {
12935 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
12936 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
12937}
12938
12939/**
12940 * Pointer events input
12941 * @constructor
12942 * @extends Input
12943 */
12944function PointerEventInput() {
12945 this.evEl = POINTER_ELEMENT_EVENTS;
12946 this.evWin = POINTER_WINDOW_EVENTS;
12947
12948 Input.apply(this, arguments);
12949
12950 this.store = (this.manager.session.pointerEvents = []);
12951}
12952
12953inherit(PointerEventInput, Input, {
12954 /**
12955 * handle mouse events
12956 * @param {Object} ev
12957 */
12958 handler: function PEhandler(ev) {
12959 var store = this.store;
12960 var removePointer = false;
12961
12962 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
12963 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
12964 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
12965
12966 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
12967
12968 // get index of the event in the store
12969 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
12970
12971 // start and mouse must be down
12972 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
12973 if (storeIndex < 0) {
12974 store.push(ev);
12975 storeIndex = store.length - 1;
12976 }
12977 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
12978 removePointer = true;
12979 }
12980
12981 // it not found, so the pointer hasn't been down (so it's probably a hover)
12982 if (storeIndex < 0) {
12983 return;
12984 }
12985
12986 // update the event in the store
12987 store[storeIndex] = ev;
12988
12989 this.callback(this.manager, eventType, {
12990 pointers: store,
12991 changedPointers: [ev],
12992 pointerType: pointerType,
12993 srcEvent: ev
12994 });
12995
12996 if (removePointer) {
12997 // remove from the store
12998 store.splice(storeIndex, 1);
12999 }
13000 }
13001});
13002
13003var SINGLE_TOUCH_INPUT_MAP = {
13004 touchstart: INPUT_START,
13005 touchmove: INPUT_MOVE,
13006 touchend: INPUT_END,
13007 touchcancel: INPUT_CANCEL
13008};
13009
13010var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
13011var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
13012
13013/**
13014 * Touch events input
13015 * @constructor
13016 * @extends Input
13017 */
13018function SingleTouchInput() {
13019 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
13020 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
13021 this.started = false;
13022
13023 Input.apply(this, arguments);
13024}
13025
13026inherit(SingleTouchInput, Input, {
13027 handler: function TEhandler(ev) {
13028 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
13029
13030 // should we handle the touch events?
13031 if (type === INPUT_START) {
13032 this.started = true;
13033 }
13034
13035 if (!this.started) {
13036 return;
13037 }
13038
13039 var touches = normalizeSingleTouches.call(this, ev, type);
13040
13041 // when done, reset the started state
13042 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
13043 this.started = false;
13044 }
13045
13046 this.callback(this.manager, type, {
13047 pointers: touches[0],
13048 changedPointers: touches[1],
13049 pointerType: INPUT_TYPE_TOUCH,
13050 srcEvent: ev
13051 });
13052 }
13053});
13054
13055/**
13056 * @this {TouchInput}
13057 * @param {Object} ev
13058 * @param {Number} type flag
13059 * @returns {undefined|Array} [all, changed]
13060 */
13061function normalizeSingleTouches(ev, type) {
13062 var all = toArray(ev.touches);
13063 var changed = toArray(ev.changedTouches);
13064
13065 if (type & (INPUT_END | INPUT_CANCEL)) {
13066 all = uniqueArray(all.concat(changed), 'identifier', true);
13067 }
13068
13069 return [all, changed];
13070}
13071
13072var TOUCH_INPUT_MAP = {
13073 touchstart: INPUT_START,
13074 touchmove: INPUT_MOVE,
13075 touchend: INPUT_END,
13076 touchcancel: INPUT_CANCEL
13077};
13078
13079var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
13080
13081/**
13082 * Multi-user touch events input
13083 * @constructor
13084 * @extends Input
13085 */
13086function TouchInput() {
13087 this.evTarget = TOUCH_TARGET_EVENTS;
13088 this.targetIds = {};
13089
13090 Input.apply(this, arguments);
13091}
13092
13093inherit(TouchInput, Input, {
13094 handler: function MTEhandler(ev) {
13095 var type = TOUCH_INPUT_MAP[ev.type];
13096 var touches = getTouches.call(this, ev, type);
13097 if (!touches) {
13098 return;
13099 }
13100
13101 this.callback(this.manager, type, {
13102 pointers: touches[0],
13103 changedPointers: touches[1],
13104 pointerType: INPUT_TYPE_TOUCH,
13105 srcEvent: ev
13106 });
13107 }
13108});
13109
13110/**
13111 * @this {TouchInput}
13112 * @param {Object} ev
13113 * @param {Number} type flag
13114 * @returns {undefined|Array} [all, changed]
13115 */
13116function getTouches(ev, type) {
13117 var allTouches = toArray(ev.touches);
13118 var targetIds = this.targetIds;
13119
13120 // when there is only one touch, the process can be simplified
13121 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
13122 targetIds[allTouches[0].identifier] = true;
13123 return [allTouches, allTouches];
13124 }
13125
13126 var i,
13127 targetTouches,
13128 changedTouches = toArray(ev.changedTouches),
13129 changedTargetTouches = [],
13130 target = this.target;
13131
13132 // get target touches from touches
13133 targetTouches = allTouches.filter(function(touch) {
13134 return hasParent(touch.target, target);
13135 });
13136
13137 // collect touches
13138 if (type === INPUT_START) {
13139 i = 0;
13140 while (i < targetTouches.length) {
13141 targetIds[targetTouches[i].identifier] = true;
13142 i++;
13143 }
13144 }
13145
13146 // filter changed touches to only contain touches that exist in the collected target ids
13147 i = 0;
13148 while (i < changedTouches.length) {
13149 if (targetIds[changedTouches[i].identifier]) {
13150 changedTargetTouches.push(changedTouches[i]);
13151 }
13152
13153 // cleanup removed touches
13154 if (type & (INPUT_END | INPUT_CANCEL)) {
13155 delete targetIds[changedTouches[i].identifier];
13156 }
13157 i++;
13158 }
13159
13160 if (!changedTargetTouches.length) {
13161 return;
13162 }
13163
13164 return [
13165 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
13166 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
13167 changedTargetTouches
13168 ];
13169}
13170
13171/**
13172 * Combined touch and mouse input
13173 *
13174 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
13175 * This because touch devices also emit mouse events while doing a touch.
13176 *
13177 * @constructor
13178 * @extends Input
13179 */
13180
13181var DEDUP_TIMEOUT = 2500;
13182var DEDUP_DISTANCE = 25;
13183
13184function TouchMouseInput() {
13185 Input.apply(this, arguments);
13186
13187 var handler = bindFn(this.handler, this);
13188 this.touch = new TouchInput(this.manager, handler);
13189 this.mouse = new MouseInput(this.manager, handler);
13190
13191 this.primaryTouch = null;
13192 this.lastTouches = [];
13193}
13194
13195inherit(TouchMouseInput, Input, {
13196 /**
13197 * handle mouse and touch events
13198 * @param {NGHammer} manager
13199 * @param {String} inputEvent
13200 * @param {Object} inputData
13201 */
13202 handler: function TMEhandler(manager, inputEvent, inputData) {
13203 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
13204 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
13205
13206 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
13207 return;
13208 }
13209
13210 // when we're in a touch event, record touches to de-dupe synthetic mouse event
13211 if (isTouch) {
13212 recordTouches.call(this, inputEvent, inputData);
13213 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
13214 return;
13215 }
13216
13217 this.callback(manager, inputEvent, inputData);
13218 },
13219
13220 /**
13221 * remove the event listeners
13222 */
13223 destroy: function destroy() {
13224 this.touch.destroy();
13225 this.mouse.destroy();
13226 }
13227});
13228
13229function recordTouches(eventType, eventData) {
13230 if (eventType & INPUT_START) {
13231 this.primaryTouch = eventData.changedPointers[0].identifier;
13232 setLastTouch.call(this, eventData);
13233 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
13234 setLastTouch.call(this, eventData);
13235 }
13236}
13237
13238function setLastTouch(eventData) {
13239 var touch = eventData.changedPointers[0];
13240
13241 if (touch.identifier === this.primaryTouch) {
13242 var lastTouch = {x: touch.clientX, y: touch.clientY};
13243 this.lastTouches.push(lastTouch);
13244 var lts = this.lastTouches;
13245 var removeLastTouch = function() {
13246 var i = lts.indexOf(lastTouch);
13247 if (i > -1) {
13248 lts.splice(i, 1);
13249 }
13250 };
13251 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
13252 }
13253}
13254
13255function isSyntheticEvent(eventData) {
13256 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
13257 for (var i = 0; i < this.lastTouches.length; i++) {
13258 var t = this.lastTouches[i];
13259 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
13260 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
13261 return true;
13262 }
13263 }
13264 return false;
13265}
13266
13267var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
13268var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
13269
13270// magical touchAction value
13271var TOUCH_ACTION_COMPUTE = 'compute';
13272var TOUCH_ACTION_AUTO = 'auto';
13273var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
13274var TOUCH_ACTION_NONE = 'none';
13275var TOUCH_ACTION_PAN_X = 'pan-x';
13276var TOUCH_ACTION_PAN_Y = 'pan-y';
13277var TOUCH_ACTION_MAP = getTouchActionProps();
13278
13279/**
13280 * Touch Action
13281 * sets the touchAction property or uses the js alternative
13282 * @param {Manager} manager
13283 * @param {String} value
13284 * @constructor
13285 */
13286function TouchAction(manager, value) {
13287 this.manager = manager;
13288 this.set(value);
13289}
13290
13291TouchAction.prototype = {
13292 /**
13293 * set the touchAction value on the element or enable the polyfill
13294 * @param {String} value
13295 */
13296 set: function(value) {
13297 // find out the touch-action by the event handlers
13298 if (value == TOUCH_ACTION_COMPUTE) {
13299 value = this.compute();
13300 }
13301
13302 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
13303 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
13304 }
13305 this.actions = value.toLowerCase().trim();
13306 },
13307
13308 /**
13309 * just re-set the touchAction value
13310 */
13311 update: function() {
13312 this.set(this.manager.options.touchAction);
13313 },
13314
13315 /**
13316 * compute the value for the touchAction property based on the recognizer's settings
13317 * @returns {String} value
13318 */
13319 compute: function() {
13320 var actions = [];
13321 each(this.manager.recognizers, function(recognizer) {
13322 if (boolOrFn(recognizer.options.enable, [recognizer])) {
13323 actions = actions.concat(recognizer.getTouchAction());
13324 }
13325 });
13326 return cleanTouchActions(actions.join(' '));
13327 },
13328
13329 /**
13330 * this method is called on each input cycle and provides the preventing of the browser behavior
13331 * @param {Object} input
13332 */
13333 preventDefaults: function(input) {
13334 var srcEvent = input.srcEvent;
13335 var direction = input.offsetDirection;
13336
13337 // if the touch action did prevented once this session
13338 if (this.manager.session.prevented) {
13339 srcEvent.preventDefault();
13340 return;
13341 }
13342
13343 var actions = this.actions;
13344 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
13345 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
13346 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
13347
13348 if (hasNone) {
13349 //do not prevent defaults if this is a tap gesture
13350
13351 var isTapPointer = input.pointers.length === 1;
13352 var isTapMovement = input.distance < 2;
13353 var isTapTouchTime = input.deltaTime < 250;
13354
13355 if (isTapPointer && isTapMovement && isTapTouchTime) {
13356 return;
13357 }
13358 }
13359
13360 if (hasPanX && hasPanY) {
13361 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
13362 return;
13363 }
13364
13365 if (hasNone ||
13366 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
13367 (hasPanX && direction & DIRECTION_VERTICAL)) {
13368 return this.preventSrc(srcEvent);
13369 }
13370 },
13371
13372 /**
13373 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
13374 * @param {Object} srcEvent
13375 */
13376 preventSrc: function(srcEvent) {
13377 this.manager.session.prevented = true;
13378 srcEvent.preventDefault();
13379 }
13380};
13381
13382/**
13383 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
13384 * @param {String} actions
13385 * @returns {*}
13386 */
13387function cleanTouchActions(actions) {
13388 // none
13389 if (inStr(actions, TOUCH_ACTION_NONE)) {
13390 return TOUCH_ACTION_NONE;
13391 }
13392
13393 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
13394 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
13395
13396 // if both pan-x and pan-y are set (different recognizers
13397 // for different directions, e.g. horizontal pan but vertical swipe?)
13398 // we need none (as otherwise with pan-x pan-y combined none of these
13399 // recognizers will work, since the browser would handle all panning
13400 if (hasPanX && hasPanY) {
13401 return TOUCH_ACTION_NONE;
13402 }
13403
13404 // pan-x OR pan-y
13405 if (hasPanX || hasPanY) {
13406 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
13407 }
13408
13409 // manipulation
13410 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
13411 return TOUCH_ACTION_MANIPULATION;
13412 }
13413
13414 return TOUCH_ACTION_AUTO;
13415}
13416
13417function getTouchActionProps() {
13418 if (!NATIVE_TOUCH_ACTION) {
13419 return false;
13420 }
13421 var touchMap = {};
13422 var cssSupports = window.CSS && window.CSS.supports;
13423 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
13424
13425 // If css.supports is not supported but there is native touch-action assume it supports
13426 // all values. This is the case for IE 10 and 11.
13427 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
13428 });
13429 return touchMap;
13430}
13431
13432/**
13433 * Recognizer flow explained; *
13434 * All recognizers have the initial state of POSSIBLE when a input session starts.
13435 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
13436 * Example session for mouse-input: mousedown -> mousemove -> mouseup
13437 *
13438 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
13439 * which determines with state it should be.
13440 *
13441 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
13442 * POSSIBLE to give it another change on the next cycle.
13443 *
13444 * Possible
13445 * |
13446 * +-----+---------------+
13447 * | |
13448 * +-----+-----+ |
13449 * | | |
13450 * Failed Cancelled |
13451 * +-------+------+
13452 * | |
13453 * Recognized Began
13454 * |
13455 * Changed
13456 * |
13457 * Ended/Recognized
13458 */
13459var STATE_POSSIBLE = 1;
13460var STATE_BEGAN = 2;
13461var STATE_CHANGED = 4;
13462var STATE_ENDED = 8;
13463var STATE_RECOGNIZED = STATE_ENDED;
13464var STATE_CANCELLED = 16;
13465var STATE_FAILED = 32;
13466
13467/**
13468 * Recognizer
13469 * Every recognizer needs to extend from this class.
13470 * @constructor
13471 * @param {Object} options
13472 */
13473function Recognizer(options) {
13474 this.options = assign({}, this.defaults, options || {});
13475
13476 this.id = uniqueId();
13477
13478 this.manager = null;
13479
13480 // default is enable true
13481 this.options.enable = ifUndefined(this.options.enable, true);
13482
13483 this.state = STATE_POSSIBLE;
13484
13485 this.simultaneous = {};
13486 this.requireFail = [];
13487}
13488
13489Recognizer.prototype = {
13490 /**
13491 * @virtual
13492 * @type {Object}
13493 */
13494 defaults: {},
13495
13496 /**
13497 * set options
13498 * @param {Object} options
13499 * @return {Recognizer}
13500 */
13501 set: function(options) {
13502 assign(this.options, options);
13503
13504 // also update the touchAction, in case something changed about the directions/enabled state
13505 this.manager && this.manager.touchAction.update();
13506 return this;
13507 },
13508
13509 /**
13510 * recognize simultaneous with an other recognizer.
13511 * @param {Recognizer} otherRecognizer
13512 * @returns {Recognizer} this
13513 */
13514 recognizeWith: function(otherRecognizer) {
13515 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
13516 return this;
13517 }
13518
13519 var simultaneous = this.simultaneous;
13520 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13521 if (!simultaneous[otherRecognizer.id]) {
13522 simultaneous[otherRecognizer.id] = otherRecognizer;
13523 otherRecognizer.recognizeWith(this);
13524 }
13525 return this;
13526 },
13527
13528 /**
13529 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
13530 * @param {Recognizer} otherRecognizer
13531 * @returns {Recognizer} this
13532 */
13533 dropRecognizeWith: function(otherRecognizer) {
13534 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
13535 return this;
13536 }
13537
13538 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13539 delete this.simultaneous[otherRecognizer.id];
13540 return this;
13541 },
13542
13543 /**
13544 * recognizer can only run when an other is failing
13545 * @param {Recognizer} otherRecognizer
13546 * @returns {Recognizer} this
13547 */
13548 requireFailure: function(otherRecognizer) {
13549 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
13550 return this;
13551 }
13552
13553 var requireFail = this.requireFail;
13554 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13555 if (inArray(requireFail, otherRecognizer) === -1) {
13556 requireFail.push(otherRecognizer);
13557 otherRecognizer.requireFailure(this);
13558 }
13559 return this;
13560 },
13561
13562 /**
13563 * drop the requireFailure link. it does not remove the link on the other recognizer.
13564 * @param {Recognizer} otherRecognizer
13565 * @returns {Recognizer} this
13566 */
13567 dropRequireFailure: function(otherRecognizer) {
13568 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
13569 return this;
13570 }
13571
13572 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13573 var index = inArray(this.requireFail, otherRecognizer);
13574 if (index > -1) {
13575 this.requireFail.splice(index, 1);
13576 }
13577 return this;
13578 },
13579
13580 /**
13581 * has require failures boolean
13582 * @returns {boolean}
13583 */
13584 hasRequireFailures: function() {
13585 return this.requireFail.length > 0;
13586 },
13587
13588 /**
13589 * if the recognizer can recognize simultaneous with an other recognizer
13590 * @param {Recognizer} otherRecognizer
13591 * @returns {Boolean}
13592 */
13593 canRecognizeWith: function(otherRecognizer) {
13594 return !!this.simultaneous[otherRecognizer.id];
13595 },
13596
13597 /**
13598 * You should use `tryEmit` instead of `emit` directly to check
13599 * that all the needed recognizers has failed before emitting.
13600 * @param {Object} input
13601 */
13602 emit: function(input) {
13603 var self = this;
13604 var state = this.state;
13605
13606 function emit(event) {
13607 self.manager.emit(event, input);
13608 }
13609
13610 // 'panstart' and 'panmove'
13611 if (state < STATE_ENDED) {
13612 emit(self.options.event + stateStr(state));
13613 }
13614
13615 emit(self.options.event); // simple 'eventName' events
13616
13617 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
13618 emit(input.additionalEvent);
13619 }
13620
13621 // panend and pancancel
13622 if (state >= STATE_ENDED) {
13623 emit(self.options.event + stateStr(state));
13624 }
13625 },
13626
13627 /**
13628 * Check that all the require failure recognizers has failed,
13629 * if true, it emits a gesture event,
13630 * otherwise, setup the state to FAILED.
13631 * @param {Object} input
13632 */
13633 tryEmit: function(input) {
13634 if (this.canEmit()) {
13635 return this.emit(input);
13636 }
13637 // it's failing anyway
13638 this.state = STATE_FAILED;
13639 },
13640
13641 /**
13642 * can we emit?
13643 * @returns {boolean}
13644 */
13645 canEmit: function() {
13646 var i = 0;
13647 while (i < this.requireFail.length) {
13648 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
13649 return false;
13650 }
13651 i++;
13652 }
13653 return true;
13654 },
13655
13656 /**
13657 * update the recognizer
13658 * @param {Object} inputData
13659 */
13660 recognize: function(inputData) {
13661 // make a new copy of the inputData
13662 // so we can change the inputData without messing up the other recognizers
13663 var inputDataClone = assign({}, inputData);
13664
13665 // is is enabled and allow recognizing?
13666 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
13667 this.reset();
13668 this.state = STATE_FAILED;
13669 return;
13670 }
13671
13672 // reset when we've reached the end
13673 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
13674 this.state = STATE_POSSIBLE;
13675 }
13676
13677 this.state = this.process(inputDataClone);
13678
13679 // the recognizer has recognized a gesture
13680 // so trigger an event
13681 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
13682 this.tryEmit(inputDataClone);
13683 }
13684 },
13685
13686 /**
13687 * return the state of the recognizer
13688 * the actual recognizing happens in this method
13689 * @virtual
13690 * @param {Object} inputData
13691 * @returns {Const} STATE
13692 */
13693 process: function(inputData) { }, // jshint ignore:line
13694
13695 /**
13696 * return the preferred touch-action
13697 * @virtual
13698 * @returns {Array}
13699 */
13700 getTouchAction: function() { },
13701
13702 /**
13703 * called when the gesture isn't allowed to recognize
13704 * like when another is being recognized or it is disabled
13705 * @virtual
13706 */
13707 reset: function() { }
13708};
13709
13710/**
13711 * get a usable string, used as event postfix
13712 * @param {Const} state
13713 * @returns {String} state
13714 */
13715function stateStr(state) {
13716 if (state & STATE_CANCELLED) {
13717 return 'cancel';
13718 } else if (state & STATE_ENDED) {
13719 return 'end';
13720 } else if (state & STATE_CHANGED) {
13721 return 'move';
13722 } else if (state & STATE_BEGAN) {
13723 return 'start';
13724 }
13725 return '';
13726}
13727
13728/**
13729 * direction cons to string
13730 * @param {Const} direction
13731 * @returns {String}
13732 */
13733function directionStr(direction) {
13734 if (direction == DIRECTION_DOWN) {
13735 return 'down';
13736 } else if (direction == DIRECTION_UP) {
13737 return 'up';
13738 } else if (direction == DIRECTION_LEFT) {
13739 return 'left';
13740 } else if (direction == DIRECTION_RIGHT) {
13741 return 'right';
13742 }
13743 return '';
13744}
13745
13746/**
13747 * get a recognizer by name if it is bound to a manager
13748 * @param {Recognizer|String} otherRecognizer
13749 * @param {Recognizer} recognizer
13750 * @returns {Recognizer}
13751 */
13752function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
13753 var manager = recognizer.manager;
13754 if (manager) {
13755 return manager.get(otherRecognizer);
13756 }
13757 return otherRecognizer;
13758}
13759
13760/**
13761 * This recognizer is just used as a base for the simple attribute recognizers.
13762 * @constructor
13763 * @extends Recognizer
13764 */
13765function AttrRecognizer() {
13766 Recognizer.apply(this, arguments);
13767}
13768
13769inherit(AttrRecognizer, Recognizer, {
13770 /**
13771 * @namespace
13772 * @memberof AttrRecognizer
13773 */
13774 defaults: {
13775 /**
13776 * @type {Number}
13777 * @default 1
13778 */
13779 pointers: 1
13780 },
13781
13782 /**
13783 * Used to check if it the recognizer receives valid input, like input.distance > 10.
13784 * @memberof AttrRecognizer
13785 * @param {Object} input
13786 * @returns {Boolean} recognized
13787 */
13788 attrTest: function(input) {
13789 var optionPointers = this.options.pointers;
13790 return optionPointers === 0 || input.pointers.length === optionPointers;
13791 },
13792
13793 /**
13794 * Process the input and return the state for the recognizer
13795 * @memberof AttrRecognizer
13796 * @param {Object} input
13797 * @returns {*} State
13798 */
13799 process: function(input) {
13800 var state = this.state;
13801 var eventType = input.eventType;
13802
13803 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
13804 var isValid = this.attrTest(input);
13805
13806 // on cancel input and we've recognized before, return STATE_CANCELLED
13807 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
13808 return state | STATE_CANCELLED;
13809 } else if (isRecognized || isValid) {
13810 if (eventType & INPUT_END) {
13811 return state | STATE_ENDED;
13812 } else if (!(state & STATE_BEGAN)) {
13813 return STATE_BEGAN;
13814 }
13815 return state | STATE_CHANGED;
13816 }
13817 return STATE_FAILED;
13818 }
13819});
13820
13821/**
13822 * Pan
13823 * Recognized when the pointer is down and moved in the allowed direction.
13824 * @constructor
13825 * @extends AttrRecognizer
13826 */
13827function PanRecognizer() {
13828 AttrRecognizer.apply(this, arguments);
13829
13830 this.pX = null;
13831 this.pY = null;
13832}
13833
13834inherit(PanRecognizer, AttrRecognizer, {
13835 /**
13836 * @namespace
13837 * @memberof PanRecognizer
13838 */
13839 defaults: {
13840 event: 'pan',
13841 threshold: 10,
13842 pointers: 1,
13843 direction: DIRECTION_ALL
13844 },
13845
13846 getTouchAction: function() {
13847 var direction = this.options.direction;
13848 var actions = [];
13849 if (direction & DIRECTION_HORIZONTAL) {
13850 actions.push(TOUCH_ACTION_PAN_Y);
13851 }
13852 if (direction & DIRECTION_VERTICAL) {
13853 actions.push(TOUCH_ACTION_PAN_X);
13854 }
13855 return actions;
13856 },
13857
13858 directionTest: function(input) {
13859 var options = this.options;
13860 var hasMoved = true;
13861 var distance = input.distance;
13862 var direction = input.direction;
13863 var x = input.deltaX;
13864 var y = input.deltaY;
13865
13866 // lock to axis?
13867 if (!(direction & options.direction)) {
13868 if (options.direction & DIRECTION_HORIZONTAL) {
13869 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
13870 hasMoved = x != this.pX;
13871 distance = Math.abs(input.deltaX);
13872 } else {
13873 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
13874 hasMoved = y != this.pY;
13875 distance = Math.abs(input.deltaY);
13876 }
13877 }
13878 input.direction = direction;
13879 return hasMoved && distance > options.threshold && direction & options.direction;
13880 },
13881
13882 attrTest: function(input) {
13883 return AttrRecognizer.prototype.attrTest.call(this, input) &&
13884 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
13885 },
13886
13887 emit: function(input) {
13888
13889 this.pX = input.deltaX;
13890 this.pY = input.deltaY;
13891
13892 var direction = directionStr(input.direction);
13893
13894 if (direction) {
13895 input.additionalEvent = this.options.event + direction;
13896 }
13897 this._super.emit.call(this, input);
13898 }
13899});
13900
13901/**
13902 * Pinch
13903 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
13904 * @constructor
13905 * @extends AttrRecognizer
13906 */
13907function PinchRecognizer() {
13908 AttrRecognizer.apply(this, arguments);
13909}
13910
13911inherit(PinchRecognizer, AttrRecognizer, {
13912 /**
13913 * @namespace
13914 * @memberof PinchRecognizer
13915 */
13916 defaults: {
13917 event: 'pinch',
13918 threshold: 0,
13919 pointers: 2
13920 },
13921
13922 getTouchAction: function() {
13923 return [TOUCH_ACTION_NONE];
13924 },
13925
13926 attrTest: function(input) {
13927 return this._super.attrTest.call(this, input) &&
13928 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
13929 },
13930
13931 emit: function(input) {
13932 if (input.scale !== 1) {
13933 var inOut = input.scale < 1 ? 'in' : 'out';
13934 input.additionalEvent = this.options.event + inOut;
13935 }
13936 this._super.emit.call(this, input);
13937 }
13938});
13939
13940/**
13941 * Press
13942 * Recognized when the pointer is down for x ms without any movement.
13943 * @constructor
13944 * @extends Recognizer
13945 */
13946function PressRecognizer() {
13947 Recognizer.apply(this, arguments);
13948
13949 this._timer = null;
13950 this._input = null;
13951}
13952
13953inherit(PressRecognizer, Recognizer, {
13954 /**
13955 * @namespace
13956 * @memberof PressRecognizer
13957 */
13958 defaults: {
13959 event: 'press',
13960 pointers: 1,
13961 time: 251, // minimal time of the pointer to be pressed
13962 threshold: 9 // a minimal movement is ok, but keep it low
13963 },
13964
13965 getTouchAction: function() {
13966 return [TOUCH_ACTION_AUTO];
13967 },
13968
13969 process: function(input) {
13970 var options = this.options;
13971 var validPointers = input.pointers.length === options.pointers;
13972 var validMovement = input.distance < options.threshold;
13973 var validTime = input.deltaTime > options.time;
13974
13975 this._input = input;
13976
13977 // we only allow little movement
13978 // and we've reached an end event, so a tap is possible
13979 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
13980 this.reset();
13981 } else if (input.eventType & INPUT_START) {
13982 this.reset();
13983 this._timer = setTimeoutContext(function() {
13984 this.state = STATE_RECOGNIZED;
13985 this.tryEmit();
13986 }, options.time, this);
13987 } else if (input.eventType & INPUT_END) {
13988 return STATE_RECOGNIZED;
13989 }
13990 return STATE_FAILED;
13991 },
13992
13993 reset: function() {
13994 clearTimeout(this._timer);
13995 },
13996
13997 emit: function(input) {
13998 if (this.state !== STATE_RECOGNIZED) {
13999 return;
14000 }
14001
14002 if (input && (input.eventType & INPUT_END)) {
14003 this.manager.emit(this.options.event + 'up', input);
14004 } else {
14005 this._input.timeStamp = now();
14006 this.manager.emit(this.options.event, this._input);
14007 }
14008 }
14009});
14010
14011/**
14012 * Rotate
14013 * Recognized when two or more pointer are moving in a circular motion.
14014 * @constructor
14015 * @extends AttrRecognizer
14016 */
14017function RotateRecognizer() {
14018 AttrRecognizer.apply(this, arguments);
14019}
14020
14021inherit(RotateRecognizer, AttrRecognizer, {
14022 /**
14023 * @namespace
14024 * @memberof RotateRecognizer
14025 */
14026 defaults: {
14027 event: 'rotate',
14028 threshold: 0,
14029 pointers: 2
14030 },
14031
14032 getTouchAction: function() {
14033 return [TOUCH_ACTION_NONE];
14034 },
14035
14036 attrTest: function(input) {
14037 return this._super.attrTest.call(this, input) &&
14038 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
14039 }
14040});
14041
14042/**
14043 * Swipe
14044 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
14045 * @constructor
14046 * @extends AttrRecognizer
14047 */
14048function SwipeRecognizer() {
14049 AttrRecognizer.apply(this, arguments);
14050}
14051
14052inherit(SwipeRecognizer, AttrRecognizer, {
14053 /**
14054 * @namespace
14055 * @memberof SwipeRecognizer
14056 */
14057 defaults: {
14058 event: 'swipe',
14059 threshold: 10,
14060 velocity: 0.3,
14061 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
14062 pointers: 1
14063 },
14064
14065 getTouchAction: function() {
14066 return PanRecognizer.prototype.getTouchAction.call(this);
14067 },
14068
14069 attrTest: function(input) {
14070 var direction = this.options.direction;
14071 var velocity;
14072
14073 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
14074 velocity = input.overallVelocity;
14075 } else if (direction & DIRECTION_HORIZONTAL) {
14076 velocity = input.overallVelocityX;
14077 } else if (direction & DIRECTION_VERTICAL) {
14078 velocity = input.overallVelocityY;
14079 }
14080
14081 return this._super.attrTest.call(this, input) &&
14082 direction & input.offsetDirection &&
14083 input.distance > this.options.threshold &&
14084 input.maxPointers == this.options.pointers &&
14085 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
14086 },
14087
14088 emit: function(input) {
14089 var direction = directionStr(input.offsetDirection);
14090 if (direction) {
14091 this.manager.emit(this.options.event + direction, input);
14092 }
14093
14094 this.manager.emit(this.options.event, input);
14095 }
14096});
14097
14098/**
14099 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
14100 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
14101 * a single tap.
14102 *
14103 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
14104 * multi-taps being recognized.
14105 * @constructor
14106 * @extends Recognizer
14107 */
14108function TapRecognizer() {
14109 Recognizer.apply(this, arguments);
14110
14111 // previous time and center,
14112 // used for tap counting
14113 this.pTime = false;
14114 this.pCenter = false;
14115
14116 this._timer = null;
14117 this._input = null;
14118 this.count = 0;
14119}
14120
14121inherit(TapRecognizer, Recognizer, {
14122 /**
14123 * @namespace
14124 * @memberof PinchRecognizer
14125 */
14126 defaults: {
14127 event: 'tap',
14128 pointers: 1,
14129 taps: 1,
14130 interval: 300, // max time between the multi-tap taps
14131 time: 250, // max time of the pointer to be down (like finger on the screen)
14132 threshold: 9, // a minimal movement is ok, but keep it low
14133 posThreshold: 10 // a multi-tap can be a bit off the initial position
14134 },
14135
14136 getTouchAction: function() {
14137 return [TOUCH_ACTION_MANIPULATION];
14138 },
14139
14140 process: function(input) {
14141 var options = this.options;
14142
14143 var validPointers = input.pointers.length === options.pointers;
14144 var validMovement = input.distance < options.threshold;
14145 var validTouchTime = input.deltaTime < options.time;
14146
14147 this.reset();
14148
14149 if ((input.eventType & INPUT_START) && (this.count === 0)) {
14150 return this.failTimeout();
14151 }
14152
14153 // we only allow little movement
14154 // and we've reached an end event, so a tap is possible
14155 if (validMovement && validTouchTime && validPointers) {
14156 if (input.eventType != INPUT_END) {
14157 return this.failTimeout();
14158 }
14159
14160 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
14161 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
14162
14163 this.pTime = input.timeStamp;
14164 this.pCenter = input.center;
14165
14166 if (!validMultiTap || !validInterval) {
14167 this.count = 1;
14168 } else {
14169 this.count += 1;
14170 }
14171
14172 this._input = input;
14173
14174 // if tap count matches we have recognized it,
14175 // else it has began recognizing...
14176 var tapCount = this.count % options.taps;
14177 if (tapCount === 0) {
14178 // no failing requirements, immediately trigger the tap event
14179 // or wait as long as the multitap interval to trigger
14180 if (!this.hasRequireFailures()) {
14181 return STATE_RECOGNIZED;
14182 } else {
14183 this._timer = setTimeoutContext(function() {
14184 this.state = STATE_RECOGNIZED;
14185 this.tryEmit();
14186 }, options.interval, this);
14187 return STATE_BEGAN;
14188 }
14189 }
14190 }
14191 return STATE_FAILED;
14192 },
14193
14194 failTimeout: function() {
14195 this._timer = setTimeoutContext(function() {
14196 this.state = STATE_FAILED;
14197 }, this.options.interval, this);
14198 return STATE_FAILED;
14199 },
14200
14201 reset: function() {
14202 clearTimeout(this._timer);
14203 },
14204
14205 emit: function() {
14206 if (this.state == STATE_RECOGNIZED) {
14207 this._input.tapCount = this.count;
14208 this.manager.emit(this.options.event, this._input);
14209 }
14210 }
14211});
14212
14213/**
14214 * Simple way to create a manager with a default set of recognizers.
14215 * @param {HTMLElement} element
14216 * @param {Object} [options]
14217 * @constructor
14218 */
14219function NGHammer(element, options) {
14220 options = options || {};
14221 options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
14222 return new Manager(element, options);
14223}
14224
14225/**
14226 * @const {string}
14227 */
14228NGHammer.VERSION = '2.0.7';
14229
14230/**
14231 * default settings
14232 * @namespace
14233 */
14234NGHammer.defaults = {
14235 /**
14236 * set if DOM events are being triggered.
14237 * But this is slower and unused by simple implementations, so disabled by default.
14238 * @type {Boolean}
14239 * @default false
14240 */
14241 domEvents: false,
14242
14243 /**
14244 * The value for the touchAction property/fallback.
14245 * When set to `compute` it will magically set the correct value based on the added recognizers.
14246 * @type {String}
14247 * @default compute
14248 */
14249 touchAction: TOUCH_ACTION_COMPUTE,
14250
14251 /**
14252 * @type {Boolean}
14253 * @default true
14254 */
14255 enable: true,
14256
14257 /**
14258 * EXPERIMENTAL FEATURE -- can be removed/changed
14259 * Change the parent input target element.
14260 * If Null, then it is being set the to main element.
14261 * @type {Null|EventTarget}
14262 * @default null
14263 */
14264 inputTarget: null,
14265
14266 /**
14267 * force an input class
14268 * @type {Null|Function}
14269 * @default null
14270 */
14271 inputClass: null,
14272
14273 /**
14274 * Default recognizer setup when calling `NGHammer()`
14275 * When creating a new Manager these will be skipped.
14276 * @type {Array}
14277 */
14278 preset: [
14279 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
14280 [RotateRecognizer, {enable: false}],
14281 [PinchRecognizer, {enable: false}, ['rotate']],
14282 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
14283 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
14284 [TapRecognizer],
14285 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
14286 [PressRecognizer]
14287 ],
14288
14289 /**
14290 * Some CSS properties can be used to improve the working of NGHammer.
14291 * Add them to this method and they will be set when creating a new Manager.
14292 * @namespace
14293 */
14294 cssProps: {
14295 /**
14296 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
14297 * @type {String}
14298 * @default 'none'
14299 */
14300 userSelect: 'none',
14301
14302 /**
14303 * Disable the Windows Phone grippers when pressing an element.
14304 * @type {String}
14305 * @default 'none'
14306 */
14307 touchSelect: 'none',
14308
14309 /**
14310 * Disables the default callout shown when you touch and hold a touch target.
14311 * On iOS, when you touch and hold a touch target such as a link, Safari displays
14312 * a callout containing information about the link. This property allows you to disable that callout.
14313 * @type {String}
14314 * @default 'none'
14315 */
14316 touchCallout: 'none',
14317
14318 /**
14319 * Specifies whether zooming is enabled. Used by IE10>
14320 * @type {String}
14321 * @default 'none'
14322 */
14323 contentZooming: 'none',
14324
14325 /**
14326 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
14327 * @type {String}
14328 * @default 'none'
14329 */
14330 userDrag: 'none',
14331
14332 /**
14333 * Overrides the highlight color shown when the user taps a link or a JavaScript
14334 * clickable element in iOS. This property obeys the alpha value, if specified.
14335 * @type {String}
14336 * @default 'rgba(0,0,0,0)'
14337 */
14338 tapHighlightColor: 'rgba(0,0,0,0)'
14339 }
14340};
14341
14342var STOP = 1;
14343var FORCED_STOP = 2;
14344
14345/**
14346 * Manager
14347 * @param {HTMLElement} element
14348 * @param {Object} [options]
14349 * @constructor
14350 */
14351function Manager(element, options) {
14352 this.options = assign({}, NGHammer.defaults, options || {});
14353
14354 this.options.inputTarget = this.options.inputTarget || element;
14355
14356 this.handlers = {};
14357 this.session = {};
14358 this.recognizers = [];
14359 this.oldCssProps = {};
14360
14361 this.element = element;
14362 this.input = createInputInstance(this);
14363 this.touchAction = new TouchAction(this, this.options.touchAction);
14364
14365 toggleCssProps(this, true);
14366
14367 each(this.options.recognizers, function(item) {
14368 var recognizer = this.add(new (item[0])(item[1]));
14369 item[2] && recognizer.recognizeWith(item[2]);
14370 item[3] && recognizer.requireFailure(item[3]);
14371 }, this);
14372}
14373
14374Manager.prototype = {
14375 /**
14376 * set options
14377 * @param {Object} options
14378 * @returns {Manager}
14379 */
14380 set: function(options) {
14381 assign(this.options, options);
14382
14383 // Options that need a little more setup
14384 if (options.touchAction) {
14385 this.touchAction.update();
14386 }
14387 if (options.inputTarget) {
14388 // Clean up existing event listeners and reinitialize
14389 this.input.destroy();
14390 this.input.target = options.inputTarget;
14391 this.input.init();
14392 }
14393 return this;
14394 },
14395
14396 /**
14397 * stop recognizing for this session.
14398 * This session will be discarded, when a new [input]start event is fired.
14399 * When forced, the recognizer cycle is stopped immediately.
14400 * @param {Boolean} [force]
14401 */
14402 stop: function(force) {
14403 this.session.stopped = force ? FORCED_STOP : STOP;
14404 },
14405
14406 /**
14407 * run the recognizers!
14408 * called by the inputHandler function on every movement of the pointers (touches)
14409 * it walks through all the recognizers and tries to detect the gesture that is being made
14410 * @param {Object} inputData
14411 */
14412 recognize: function(inputData) {
14413 var session = this.session;
14414 if (session.stopped) {
14415 return;
14416 }
14417
14418 // run the touch-action polyfill
14419 this.touchAction.preventDefaults(inputData);
14420
14421 var recognizer;
14422 var recognizers = this.recognizers;
14423
14424 // this holds the recognizer that is being recognized.
14425 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
14426 // if no recognizer is detecting a thing, it is set to `null`
14427 var curRecognizer = session.curRecognizer;
14428
14429 // reset when the last recognizer is recognized
14430 // or when we're in a new session
14431 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
14432 curRecognizer = session.curRecognizer = null;
14433 }
14434
14435 var i = 0;
14436 while (i < recognizers.length) {
14437 recognizer = recognizers[i];
14438
14439 // find out if we are allowed try to recognize the input for this one.
14440 // 1. allow if the session is NOT forced stopped (see the .stop() method)
14441 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
14442 // that is being recognized.
14443 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
14444 // this can be setup with the `recognizeWith()` method on the recognizer.
14445 if (session.stopped !== FORCED_STOP && ( // 1
14446 !curRecognizer || recognizer == curRecognizer || // 2
14447 recognizer.canRecognizeWith(curRecognizer))) { // 3
14448 recognizer.recognize(inputData);
14449 } else {
14450 recognizer.reset();
14451 }
14452
14453 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
14454 // current active recognizer. but only if we don't already have an active recognizer
14455 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
14456 curRecognizer = session.curRecognizer = recognizer;
14457 }
14458 i++;
14459 }
14460 },
14461
14462 /**
14463 * get a recognizer by its event name.
14464 * @param {Recognizer|String} recognizer
14465 * @returns {Recognizer|Null}
14466 */
14467 get: function(recognizer) {
14468 if (recognizer instanceof Recognizer) {
14469 return recognizer;
14470 }
14471
14472 var recognizers = this.recognizers;
14473 for (var i = 0; i < recognizers.length; i++) {
14474 if (recognizers[i].options.event == recognizer) {
14475 return recognizers[i];
14476 }
14477 }
14478 return null;
14479 },
14480
14481 /**
14482 * add a recognizer to the manager
14483 * existing recognizers with the same event name will be removed
14484 * @param {Recognizer} recognizer
14485 * @returns {Recognizer|Manager}
14486 */
14487 add: function(recognizer) {
14488 if (invokeArrayArg(recognizer, 'add', this)) {
14489 return this;
14490 }
14491
14492 // remove existing
14493 var existing = this.get(recognizer.options.event);
14494 if (existing) {
14495 this.remove(existing);
14496 }
14497
14498 this.recognizers.push(recognizer);
14499 recognizer.manager = this;
14500
14501 this.touchAction.update();
14502 return recognizer;
14503 },
14504
14505 /**
14506 * remove a recognizer by name or instance
14507 * @param {Recognizer|String} recognizer
14508 * @returns {Manager}
14509 */
14510 remove: function(recognizer) {
14511 if (invokeArrayArg(recognizer, 'remove', this)) {
14512 return this;
14513 }
14514
14515 recognizer = this.get(recognizer);
14516
14517 // let's make sure this recognizer exists
14518 if (recognizer) {
14519 var recognizers = this.recognizers;
14520 var index = inArray(recognizers, recognizer);
14521
14522 if (index !== -1) {
14523 recognizers.splice(index, 1);
14524 this.touchAction.update();
14525 }
14526 }
14527
14528 return this;
14529 },
14530
14531 /**
14532 * bind event
14533 * @param {String} events
14534 * @param {Function} handler
14535 * @returns {EventEmitter} this
14536 */
14537 on: function(events, handler) {
14538 if (events === undefined) {
14539 return;
14540 }
14541 if (handler === undefined) {
14542 return;
14543 }
14544
14545 var handlers = this.handlers;
14546 each(splitStr(events), function(event) {
14547 handlers[event] = handlers[event] || [];
14548 handlers[event].push(handler);
14549 });
14550 return this;
14551 },
14552
14553 /**
14554 * unbind event, leave emit blank to remove all handlers
14555 * @param {String} events
14556 * @param {Function} [handler]
14557 * @returns {EventEmitter} this
14558 */
14559 off: function(events, handler) {
14560 if (events === undefined) {
14561 return;
14562 }
14563
14564 var handlers = this.handlers;
14565 each(splitStr(events), function(event) {
14566 if (!handler) {
14567 delete handlers[event];
14568 } else {
14569 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
14570 }
14571 });
14572 return this;
14573 },
14574
14575 /**
14576 * emit event to the listeners
14577 * @param {String} event
14578 * @param {Object} data
14579 */
14580 emit: function(event, data) {
14581 // we also want to trigger dom events
14582 if (this.options.domEvents) {
14583 triggerDomEvent(event, data);
14584 }
14585
14586 // no handlers, so skip it all
14587 var handlers = this.handlers[event] && this.handlers[event].slice();
14588 if (!handlers || !handlers.length) {
14589 return;
14590 }
14591
14592 data.type = event;
14593 data.preventDefault = function() {
14594 data.srcEvent.preventDefault();
14595 };
14596
14597 var i = 0;
14598 while (i < handlers.length) {
14599 handlers[i](data);
14600 i++;
14601 }
14602 },
14603
14604 /**
14605 * destroy the manager and unbinds all events
14606 * it doesn't unbind dom events, that is the user own responsibility
14607 */
14608 destroy: function() {
14609 this.element && toggleCssProps(this, false);
14610
14611 this.handlers = {};
14612 this.session = {};
14613 this.input.destroy();
14614 this.element = null;
14615 }
14616};
14617
14618/**
14619 * add/remove the css properties as defined in manager.options.cssProps
14620 * @param {Manager} manager
14621 * @param {Boolean} add
14622 */
14623function toggleCssProps(manager, add) {
14624 var element = manager.element;
14625 if (!element.style) {
14626 return;
14627 }
14628 var prop;
14629 each(manager.options.cssProps, function(value, name) {
14630 prop = prefixed(element.style, name);
14631 if (add) {
14632 manager.oldCssProps[prop] = element.style[prop];
14633 element.style[prop] = value;
14634 } else {
14635 element.style[prop] = manager.oldCssProps[prop] || '';
14636 }
14637 });
14638 if (!add) {
14639 manager.oldCssProps = {};
14640 }
14641}
14642
14643/**
14644 * trigger dom event
14645 * @param {String} event
14646 * @param {Object} data
14647 */
14648function triggerDomEvent(event, data) {
14649 var gestureEvent = document.createEvent('Event');
14650 gestureEvent.initEvent(event, true, true);
14651 gestureEvent.gesture = data;
14652 data.target.dispatchEvent(gestureEvent);
14653}
14654
14655assign(NGHammer, {
14656 INPUT_START: INPUT_START,
14657 INPUT_MOVE: INPUT_MOVE,
14658 INPUT_END: INPUT_END,
14659 INPUT_CANCEL: INPUT_CANCEL,
14660
14661 STATE_POSSIBLE: STATE_POSSIBLE,
14662 STATE_BEGAN: STATE_BEGAN,
14663 STATE_CHANGED: STATE_CHANGED,
14664 STATE_ENDED: STATE_ENDED,
14665 STATE_RECOGNIZED: STATE_RECOGNIZED,
14666 STATE_CANCELLED: STATE_CANCELLED,
14667 STATE_FAILED: STATE_FAILED,
14668
14669 DIRECTION_NONE: DIRECTION_NONE,
14670 DIRECTION_LEFT: DIRECTION_LEFT,
14671 DIRECTION_RIGHT: DIRECTION_RIGHT,
14672 DIRECTION_UP: DIRECTION_UP,
14673 DIRECTION_DOWN: DIRECTION_DOWN,
14674 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
14675 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
14676 DIRECTION_ALL: DIRECTION_ALL,
14677
14678 Manager: Manager,
14679 Input: Input,
14680 TouchAction: TouchAction,
14681
14682 TouchInput: TouchInput,
14683 MouseInput: MouseInput,
14684 PointerEventInput: PointerEventInput,
14685 TouchMouseInput: TouchMouseInput,
14686 SingleTouchInput: SingleTouchInput,
14687
14688 Recognizer: Recognizer,
14689 AttrRecognizer: AttrRecognizer,
14690 Tap: TapRecognizer,
14691 Pan: PanRecognizer,
14692 Swipe: SwipeRecognizer,
14693 Pinch: PinchRecognizer,
14694 Rotate: RotateRecognizer,
14695 Press: PressRecognizer,
14696
14697 on: addEventListeners,
14698 off: removeEventListeners,
14699 each: each,
14700 merge: merge,
14701 extend: extend,
14702 assign: assign,
14703 inherit: inherit,
14704 bindFn: bindFn,
14705 prefixed: prefixed
14706});
14707
14708// this prevents errors when NGHammer is loaded in the presence of an AMD
14709// style loader but by script tag, not by the loader.
14710var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
14711freeGlobal.NGHammer = NGHammer;
14712
14713if (typeof define === 'function' && define.amdDISABLED) {
14714 define(function() {
14715 return NGHammer;
14716 });
14717} else if (typeof module != 'undefined' && module.exports) {
14718 module.exports = NGHammer;
14719} else {
14720 window[exportName] = NGHammer;
14721}
14722
14723})(window, document, 'NGHammer');
14724
14725
14726
14727
14728
14729
14730// END NANOGALLERY2
14731// }( jQuery )));
14732}));
14733
14734
14735//##########################################################################################################################
14736//##########################################################################################################################
14737//##########################################################################################################################
14738//##########################################################################################################################
14739//##########################################################################################################################
14740
14741// nanogallery2 auto start whithout javascript call
14742(function(){
14743 'use strict';
14744 jQuery(document).ready(function () {
14745
14746 // var t=document.querySelectorAll('[data-nanogallery2-portable]');
14747 // if( t.length > 0 ) {
14748 // portable mode
14749 // var link = document.createElement('link');
14750 // link.setAttribute("rel", "stylesheet");
14751 // link.setAttribute("type", "text/css");
14752 // link.onload = function(){
14753 // for( var i=0; i < t.length; i++ ) {
14754 // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
14755 // }
14756 // }
14757 // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
14758 // document.getElementsByTagName("head")[0].appendChild(link);
14759 // }
14760 // else {
14761 // standard mode
14762 var t=document.querySelectorAll('[data-nanogallery2]');
14763 for( var i=0; i < t.length; i++ ) {
14764 jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
14765 }
14766 // }
14767
14768 });
14769}).call(null);
14770