PageRenderTime 137ms CodeModel.GetById 46ms RepoModel.GetById 6ms app.codeStats 1ms

/wojilu.Web/static/js/galleria/galleria-1.2.4.js

https://bitbucket.org/kingshine/wojilu
JavaScript | 4754 lines | 2796 code | 999 blank | 959 comment | 386 complexity | cf71846c52bb89258f17f867092deef9 MD5 | raw file
Possible License(s): MIT
  1. /**
  2. * @preserve Galleria v 1.2.4 2011-06-07
  3. * http://galleria.aino.se
  4. *
  5. * Copyright (c) 2011, Aino
  6. * Licensed under the MIT license.
  7. */
  8. /*global jQuery, navigator, Galleria, Image */
  9. (function( $ ) {
  10. // some references
  11. var undef,
  12. window = this,
  13. doc = window.document,
  14. $doc = $( doc ),
  15. $win = $( window ),
  16. // internal constants
  17. DEBUG = true,
  18. NAV = navigator.userAgent.toLowerCase(),
  19. HASH = window.location.hash.replace(/#\//, ''),
  20. IE = (function() {
  21. var v = 3,
  22. div = doc.createElement( 'div' ),
  23. all = div.getElementsByTagName( 'i' );
  24. do {
  25. div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
  26. } while ( all[0] );
  27. return v > 4 ? v : undef;
  28. }() ),
  29. DOM = function() {
  30. return {
  31. html: doc.documentElement,
  32. body: doc.body,
  33. head: doc.getElementsByTagName('head')[0],
  34. title: doc.title
  35. };
  36. },
  37. // list of Galleria events
  38. _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' +
  39. 'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' +
  40. 'lightbox_open lightbox_close lightbox_image',
  41. _events = (function() {
  42. var evs = [];
  43. $.each( _eventlist.split(' '), function( i, ev ) {
  44. evs.push( ev );
  45. // legacy events
  46. if ( /_/.test( ev ) ) {
  47. evs.push( ev.replace( /_/g, '' ) );
  48. }
  49. });
  50. return evs;
  51. }()),
  52. // legacy options
  53. // allows the old my_setting syntax and converts it to camel case
  54. _legacyOptions = function( options ) {
  55. var n;
  56. if ( typeof options !== 'object' ) {
  57. // return whatever it was...
  58. return options;
  59. }
  60. $.each( options, function( key, value ) {
  61. if ( /^[a-z]+_/.test( key ) ) {
  62. n = '';
  63. $.each( key.split('_'), function( i, k ) {
  64. n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k;
  65. });
  66. options[ n ] = value;
  67. delete options[ key ];
  68. }
  69. });
  70. return options;
  71. },
  72. _patchEvent = function( type ) {
  73. // allow 'image' instead of Galleria.IMAGE
  74. if ( $.inArray( type, _events ) > -1 ) {
  75. return Galleria[ type.toUpperCase() ];
  76. }
  77. return type;
  78. },
  79. // the internal timeouts object
  80. // provides helper methods for controlling timeouts
  81. _timeouts = {
  82. trunk: {},
  83. add: function( id, fn, delay, loop ) {
  84. loop = loop || false;
  85. this.clear( id );
  86. if ( loop ) {
  87. var old = fn;
  88. fn = function() {
  89. old();
  90. _timeouts.add( id, fn, delay );
  91. };
  92. }
  93. this.trunk[ id ] = window.setTimeout( fn, delay );
  94. },
  95. clear: function( id ) {
  96. var del = function( i ) {
  97. window.clearTimeout( this.trunk[ i ] );
  98. delete this.trunk[ i ];
  99. }, i;
  100. if ( !!id && id in this.trunk ) {
  101. del.call( _timeouts, id );
  102. } else if ( typeof id === 'undefined' ) {
  103. for ( i in this.trunk ) {
  104. if ( this.trunk.hasOwnProperty( i ) ) {
  105. del.call( _timeouts, i );
  106. }
  107. }
  108. }
  109. }
  110. },
  111. // the internal gallery holder
  112. _galleries = [],
  113. // the internal instance holder
  114. _instances = [],
  115. // flag for errors
  116. _hasError = false,
  117. // canvas holder
  118. _canvas = false,
  119. // the Utils singleton
  120. Utils = (function() {
  121. return {
  122. array : function( obj ) {
  123. return Array.prototype.slice.call(obj);
  124. },
  125. create : function( className, nodeName ) {
  126. nodeName = nodeName || 'div';
  127. var elem = doc.createElement( nodeName );
  128. elem.className = className;
  129. return elem;
  130. },
  131. // CSS3 transitions, added in 1.2.4
  132. animate : (function() {
  133. // detect transition
  134. var transition = (function( style ) {
  135. var props = 'transition WebkitTransition MozTransition OTransition'.split(' '),
  136. i;
  137. for ( i = 0; props[i]; i++ ) {
  138. if ( typeof style[ props[ i ] ] !== 'undefined' ) {
  139. return props[ i ];
  140. }
  141. }
  142. return false;
  143. }(( document.body || document.documentElement).style ));
  144. // map transitionend event
  145. var endEvent = {
  146. MozTransition: 'transitionend',
  147. OTransition: 'oTransitionEnd',
  148. WebkitTransition: 'webkitTransitionEnd',
  149. transition: 'transitionend'
  150. }[ transition ];
  151. // map bezier easing conversions
  152. var easings = {
  153. _default: [0.25, 0.1, 0.25, 1],
  154. galleria: [0.645, 0.045, 0.355, 1],
  155. galleriaIn: [0.55, 0.085, 0.68, 0.53],
  156. galleriaOut: [0.25, 0.46, 0.45, 0.94],
  157. ease: [0.25, 0, 0.25, 1],
  158. linear: [0.25, 0.25, 0.75, 0.75],
  159. 'ease-in': [0.42, 0, 1, 1],
  160. 'ease-out': [0, 0, 0.58, 1],
  161. 'ease-in-out': [0.42, 0, 0.58, 1]
  162. };
  163. // function for setting transition css for all browsers
  164. var setStyle = function( elem, value, suffix ) {
  165. var css = {};
  166. suffix = suffix || 'transition';
  167. $.each( 'webkit moz ms o'.split(' '), function() {
  168. css[ '-' + this + '-' + suffix ] = value;
  169. });
  170. elem.css( css );
  171. };
  172. // clear styles
  173. var clearStyle = function( elem ) {
  174. setStyle( elem, 'none', 'transition' );
  175. if ( Galleria.WEBKIT ) {
  176. setStyle( elem, 'translate3d(0,0,0)', 'transform' );
  177. if ( elem.data('revert') ) {
  178. elem.css( elem.data('revert') );
  179. elem.data('revert', null);
  180. }
  181. }
  182. };
  183. // various variables
  184. var change, strings, easing, syntax, revert, form, css;
  185. // the actual animation method
  186. return function( elem, to, options ) {
  187. // extend defaults
  188. options = $.extend({
  189. duration: 400,
  190. complete: function(){},
  191. stop: false
  192. }, options);
  193. // cache jQuery instance
  194. elem = $( elem );
  195. if ( !options.duration ) {
  196. elem.css( to );
  197. options.complete.call( elem[0] );
  198. return;
  199. }
  200. // fallback to jQuery’s animate if transition is not supported
  201. if ( !transition ) {
  202. elem.animate(to, options);
  203. return;
  204. }
  205. // stop
  206. if ( options.stop ) {
  207. // clear the animation
  208. elem.unbind( endEvent );
  209. clearStyle( elem );
  210. }
  211. // see if there is a change
  212. change = false;
  213. $.each( to, function( key, val ) {
  214. css = elem.css( key );
  215. if ( Utils.parseValue( css ) != Utils.parseValue( val ) ) {
  216. change = true;
  217. }
  218. // also add computed styles for FF
  219. elem.css( key, css );
  220. });
  221. if ( !change ) {
  222. window.setTimeout( function() {
  223. options.complete.call( elem[0] );
  224. }, options.duration );
  225. return;
  226. }
  227. // the css strings to be applied
  228. strings = [];
  229. // the easing bezier
  230. easing = options.easing in easings ? easings[ options.easing ] : easings._default;
  231. // the syntax
  232. syntax = ' ' + options.duration + 'ms' + ' cubic-bezier(' + easing.join(',') + ')';
  233. // add a tiny timeout so that the browsers catches any css changes before animating
  234. window.setTimeout(function() {
  235. // attach the end event
  236. elem.one(endEvent, (function( elem ) {
  237. return function() {
  238. // clear the animation
  239. clearStyle(elem);
  240. // run the complete method
  241. options.complete.call(elem[0]);
  242. };
  243. }( elem )));
  244. // do the webkit translate3d for better performance on iOS
  245. if( Galleria.WEBKIT && Galleria.TOUCH ) {
  246. revert = {};
  247. form = [0,0,0];
  248. $.each( ['left', 'top'], function(i, m) {
  249. if ( m in to ) {
  250. form[ i ] = ( Utils.parseValue( to[ m ] ) - Utils.parseValue(elem.css( m )) ) + 'px';
  251. revert[ m ] = to[ m ];
  252. delete to[ m ];
  253. }
  254. });
  255. if ( form[0] || form[1]) {
  256. elem.data('revert', revert);
  257. strings.push('-webkit-transform' + syntax);
  258. // 3d animate
  259. setStyle( elem, 'translate3d(' + form.join(',') + ')', 'transform');
  260. }
  261. }
  262. // push the animation props
  263. $.each(to, function( p, val ) {
  264. strings.push(p + syntax);
  265. });
  266. // set the animation styles
  267. setStyle( elem, strings.join(',') );
  268. // animate
  269. elem.css( to );
  270. },1 );
  271. };
  272. }()),
  273. forceStyles : function( elem, styles ) {
  274. elem = $(elem);
  275. if ( elem.attr( 'style' ) ) {
  276. elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' );
  277. }
  278. elem.css( styles );
  279. },
  280. revertStyles : function() {
  281. $.each( Utils.array( arguments ), function( i, elem ) {
  282. elem = $( elem );
  283. elem.removeAttr( 'style' );
  284. elem.attr('style',''); // "fixes" webkit bug
  285. if ( elem.data( 'styles' ) ) {
  286. elem.attr( 'style', elem.data('styles') ).data( 'styles', null );
  287. }
  288. });
  289. },
  290. moveOut : function( elem ) {
  291. Utils.forceStyles( elem, {
  292. position: 'absolute',
  293. left: -10000
  294. });
  295. },
  296. moveIn : function() {
  297. Utils.revertStyles.apply( Utils, Utils.array( arguments ) );
  298. },
  299. hide : function( elem, speed, callback ) {
  300. elem = $(elem);
  301. // save the value if not exist
  302. if (! elem.data('opacity') ) {
  303. elem.data('opacity', elem.css('opacity') );
  304. }
  305. // always hide
  306. var style = { opacity: 0 };
  307. if (speed) {
  308. Utils.animate( elem, style, {
  309. duration: speed,
  310. complete: callback,
  311. stop: true
  312. });
  313. } else {
  314. elem.css( style );
  315. }
  316. },
  317. show : function( elem, speed, callback ) {
  318. elem = $(elem);
  319. // bring back saved opacity
  320. var saved = parseFloat( elem.data('opacity') ) || 1,
  321. style = { opacity: saved };
  322. // animate or toggle
  323. if (speed) {
  324. Utils.animate( elem, style, {
  325. duration: speed,
  326. complete: callback,
  327. stop: true
  328. });
  329. } else {
  330. elem.css( style );
  331. }
  332. },
  333. // enhanced click for mobile devices
  334. // we bind a touchstart and hijack any click event in the bubble
  335. // then we execute the click directly and save it in a separate data object for later
  336. optimizeTouch: (function() {
  337. var node,
  338. evs,
  339. fakes,
  340. travel,
  341. evt = {},
  342. handler = function( e ) {
  343. e.preventDefault();
  344. evt = $.extend({}, e, true);
  345. },
  346. attach = function() {
  347. this.evt = evt;
  348. },
  349. fake = function() {
  350. this.handler.call(node, this.evt);
  351. };
  352. return function( elem ) {
  353. $(elem).bind('touchstart', function( e ) {
  354. node = e.target;
  355. travel = true;
  356. while( node.parentNode && node != e.currentTarget && travel ) {
  357. evs = $(node).data('events');
  358. fakes = $(node).data('fakes');
  359. if (evs && 'click' in evs) {
  360. travel = false;
  361. e.preventDefault();
  362. // fake the click and save the event object
  363. $(node).click(handler).click();
  364. // remove the faked click
  365. evs.click.pop();
  366. // attach the faked event
  367. $.each( evs.click, attach);
  368. // save the faked clicks in a new data object
  369. $(node).data('fakes', evs.click);
  370. // remove all clicks
  371. delete evs.click;
  372. } else if ( fakes ) {
  373. travel = false;
  374. e.preventDefault();
  375. // fake all clicks
  376. $.each( fakes, fake );
  377. }
  378. // bubble
  379. node = node.parentNode;
  380. }
  381. });
  382. };
  383. }()),
  384. addTimer : function() {
  385. _timeouts.add.apply( _timeouts, Utils.array( arguments ) );
  386. return this;
  387. },
  388. clearTimer : function() {
  389. _timeouts.clear.apply( _timeouts, Utils.array( arguments ) );
  390. return this;
  391. },
  392. wait : function(options) {
  393. options = $.extend({
  394. until : function() { return false; },
  395. success : function() {},
  396. error : function() { Galleria.raise('Could not complete wait function.'); },
  397. timeout: 3000
  398. }, options);
  399. var start = Utils.timestamp(),
  400. elapsed,
  401. now,
  402. fn = function() {
  403. now = Utils.timestamp();
  404. elapsed = now - start;
  405. if ( options.until( elapsed ) ) {
  406. options.success();
  407. return false;
  408. }
  409. if (now >= start + options.timeout) {
  410. options.error();
  411. return false;
  412. }
  413. window.setTimeout(fn, 2);
  414. };
  415. window.setTimeout(fn, 2);
  416. },
  417. toggleQuality : function( img, force ) {
  418. if ( ( IE !== 7 && IE !== 8 ) || !img ) {
  419. return;
  420. }
  421. if ( typeof force === 'undefined' ) {
  422. force = img.style.msInterpolationMode === 'nearest-neighbor';
  423. }
  424. img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
  425. },
  426. insertStyleTag : function( styles ) {
  427. var style = doc.createElement( 'style' );
  428. DOM().head.appendChild( style );
  429. if ( style.styleSheet ) { // IE
  430. style.styleSheet.cssText = styles;
  431. } else {
  432. var cssText = doc.createTextNode( styles );
  433. style.appendChild( cssText );
  434. }
  435. },
  436. // a loadscript method that works for local scripts
  437. loadScript: function( url, callback ) {
  438. var done = false,
  439. script = $('<scr'+'ipt>').attr({
  440. src: url,
  441. async: true
  442. }).get(0);
  443. // Attach handlers for all browsers
  444. script.onload = script.onreadystatechange = function() {
  445. if ( !done && (!this.readyState ||
  446. this.readyState === 'loaded' || this.readyState === 'complete') ) {
  447. done = true;
  448. // Handle memory leak in IE
  449. script.onload = script.onreadystatechange = null;
  450. if (typeof callback === 'function') {
  451. callback.call( this, this );
  452. }
  453. }
  454. };
  455. DOM().head.appendChild( script );
  456. },
  457. // parse anything into a number
  458. parseValue: function( val ) {
  459. if (typeof val === 'number') {
  460. return val;
  461. } else if (typeof val === 'string') {
  462. var arr = val.match(/\-?\d|\./g);
  463. return arr && arr.constructor === Array ? arr.join('')*1 : 0;
  464. } else {
  465. return 0;
  466. }
  467. },
  468. // timestamp abstraction
  469. timestamp: function() {
  470. return new Date().getTime();
  471. },
  472. // this is pretty crap, but works for now
  473. // it will add a callback, but it can't guarantee that the styles can be fetched
  474. // using getComputedStyle further checking needed, possibly a dummy element
  475. loadCSS : function( href, id, callback ) {
  476. var link,
  477. ready = false,
  478. length;
  479. // look for manual css
  480. $('link[rel=stylesheet]').each(function() {
  481. if ( new RegExp( href ).test( this.href ) ) {
  482. link = this;
  483. return false;
  484. }
  485. });
  486. if ( typeof id === 'function' ) {
  487. callback = id;
  488. id = undef;
  489. }
  490. callback = callback || function() {}; // dirty
  491. // if already present, return
  492. if ( link ) {
  493. callback.call( link, link );
  494. return link;
  495. }
  496. // save the length of stylesheets to check against
  497. length = doc.styleSheets.length;
  498. // add timestamp if DEBUG is true
  499. if ( DEBUG ) {
  500. href += '?' + Utils.timestamp();
  501. }
  502. // check for existing id
  503. if( $('#'+id).length ) {
  504. $('#'+id).attr('href', href);
  505. length--;
  506. ready = true;
  507. } else {
  508. link = $( '<link>' ).attr({
  509. rel: 'stylesheet',
  510. href: href,
  511. id: id
  512. }).get(0);
  513. window.setTimeout(function() {
  514. var styles = $('link[rel="stylesheet"], style');
  515. if ( styles.length ) {
  516. styles.get(0).parentNode.insertBefore( link, styles[0] );
  517. } else {
  518. DOM().head.appendChild( link );
  519. }
  520. if ( IE ) {
  521. // IE has a limit of 31 stylesheets in one document
  522. if( length >= 31 ) {
  523. Galleria.raise( 'You have reached the browser stylesheet limit (31)', true );
  524. return;
  525. }
  526. // todo: test if IE really needs the readyState
  527. link.onreadystatechange = function(e) {
  528. if ( !ready && (!this.readyState ||
  529. this.readyState === 'loaded' || this.readyState === 'complete') ) {
  530. ready = true;
  531. }
  532. };
  533. } else {
  534. // final test via ajax if not local
  535. if ( !( new RegExp('file://','i').test( href ) ) ) {
  536. $.ajax({
  537. url: href,
  538. success: function() {
  539. ready = true;
  540. },
  541. error: function(e) {
  542. // pass if origin is rejected in chrome for some reason
  543. if( e.isRejected() && Galleria.WEBKIT ) {
  544. ready = true;
  545. }
  546. }
  547. });
  548. } else {
  549. ready = true;
  550. }
  551. }
  552. }, 10);
  553. }
  554. if ( typeof callback === 'function' ) {
  555. Utils.wait({
  556. until: function() {
  557. return ready && doc.styleSheets.length > length;
  558. },
  559. success: function() {
  560. window.setTimeout( function() {
  561. callback.call( link, link );
  562. }, 100);
  563. },
  564. error: function() {
  565. Galleria.raise( 'Theme CSS could not load', true );
  566. },
  567. timeout: 10000
  568. });
  569. }
  570. return link;
  571. }
  572. };
  573. }()),
  574. // the transitions holder
  575. _transitions = (function() {
  576. var _slide = function(params, complete, fade, door) {
  577. var easing = this.getOptions('easing'),
  578. distance = this.getStageWidth(),
  579. from = { left: distance * ( params.rewind ? -1 : 1 ) },
  580. to = { left: 0 };
  581. if ( fade ) {
  582. from.opacity = 0;
  583. to.opacity = 1;
  584. }
  585. $(params.next).css(from);
  586. Utils.animate(params.next, to, {
  587. duration: params.speed,
  588. complete: (function( elems ) {
  589. return function() {
  590. complete();
  591. elems.css({
  592. left: 0
  593. });
  594. };
  595. }( $( params.next ).add( params.prev ) )),
  596. queue: false,
  597. easing: easing
  598. });
  599. if (door) {
  600. params.rewind = !params.rewind;
  601. }
  602. if (params.prev) {
  603. from = { left: 0 };
  604. to = { left: distance * ( params.rewind ? 1 : -1 ) };
  605. if ( fade ) {
  606. from.opacity = 1;
  607. to.opacity = 0;
  608. }
  609. $(params.prev).css(from);
  610. Utils.animate(params.prev, to, {
  611. duration: params.speed,
  612. queue: false,
  613. easing: easing,
  614. complete: function() {
  615. $(this).css('opacity', 0);
  616. }
  617. });
  618. }
  619. };
  620. return {
  621. fade: function(params, complete) {
  622. $(params.next).css('opacity',0).show();
  623. Utils.animate(params.next, {
  624. opacity: 1
  625. },{
  626. duration: params.speed,
  627. complete: complete
  628. });
  629. if (params.prev) {
  630. $(params.prev).css('opacity',1).show();
  631. Utils.animate(params.prev, {
  632. opacity: 0
  633. },{
  634. duration: params.speed
  635. });
  636. }
  637. },
  638. flash: function(params, complete) {
  639. $(params.next).css('opacity', 0);
  640. if (params.prev) {
  641. Utils.animate( params.prev, {
  642. opacity: 0
  643. },{
  644. duration: params.speed/2,
  645. complete: function() {
  646. Utils.animate( params.next, {
  647. opacity:1
  648. },{
  649. duration: params.speed,
  650. complete: complete
  651. });
  652. }
  653. });
  654. } else {
  655. Utils.animate( params.next, {
  656. opacity: 1
  657. },{
  658. duration: params.speed,
  659. complete: complete
  660. });
  661. }
  662. },
  663. pulse: function(params, complete) {
  664. if (params.prev) {
  665. $(params.prev).hide();
  666. }
  667. $(params.next).css('opacity', 0).show();
  668. Utils.animate(params.next, {
  669. opacity:1
  670. },{
  671. duration: params.speed,
  672. complete: complete
  673. });
  674. },
  675. slide: function(params, complete) {
  676. _slide.apply( this, Utils.array( arguments ) );
  677. },
  678. fadeslide: function(params, complete) {
  679. _slide.apply( this, Utils.array( arguments ).concat( [true] ) );
  680. },
  681. doorslide: function(params, complete) {
  682. _slide.apply( this, Utils.array( arguments ).concat( [false, true] ) );
  683. }
  684. };
  685. })();
  686. /**
  687. The main Galleria class
  688. @class
  689. @constructor
  690. @example var gallery = new Galleria();
  691. @author http://aino.se
  692. @requires jQuery
  693. */
  694. var Galleria = function() {
  695. var self = this;
  696. // the theme used
  697. this._theme = undef;
  698. // internal options
  699. this._options = {};
  700. // flag for controlling play/pause
  701. this._playing = false;
  702. // internal interval for slideshow
  703. this._playtime = 5000;
  704. // internal variable for the currently active image
  705. this._active = null;
  706. // the internal queue, arrayified
  707. this._queue = { length: 0 };
  708. // the internal data array
  709. this._data = [];
  710. // the internal dom collection
  711. this._dom = {};
  712. // the internal thumbnails array
  713. this._thumbnails = [];
  714. // internal init flag
  715. this._initialized = false;
  716. // internal firstrun flag
  717. this._firstrun = false;
  718. // global stagewidth/height
  719. this._stageWidth = 0;
  720. this._stageHeight = 0;
  721. // target holder
  722. this._target = undef;
  723. // instance id
  724. this._id = Math.random();
  725. // add some elements
  726. var divs = 'container stage images image-nav image-nav-left image-nav-right ' +
  727. 'info info-text info-title info-description ' +
  728. 'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
  729. 'loader counter tooltip',
  730. spans = 'current total';
  731. $.each( divs.split(' '), function( i, elemId ) {
  732. self._dom[ elemId ] = Utils.create( 'galleria-' + elemId );
  733. });
  734. $.each( spans.split(' '), function( i, elemId ) {
  735. self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' );
  736. });
  737. // the internal keyboard object
  738. // keeps reference of the keybinds and provides helper methods for binding keys
  739. var keyboard = this._keyboard = {
  740. keys : {
  741. 'UP': 38,
  742. 'DOWN': 40,
  743. 'LEFT': 37,
  744. 'RIGHT': 39,
  745. 'RETURN': 13,
  746. 'ESCAPE': 27,
  747. 'BACKSPACE': 8,
  748. 'SPACE': 32
  749. },
  750. map : {},
  751. bound: false,
  752. press: function(e) {
  753. var key = e.keyCode || e.which;
  754. if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) {
  755. keyboard.map[key].call(self, e);
  756. }
  757. },
  758. attach: function(map) {
  759. var key, up;
  760. for( key in map ) {
  761. if ( map.hasOwnProperty( key ) ) {
  762. up = key.toUpperCase();
  763. if ( up in keyboard.keys ) {
  764. keyboard.map[ keyboard.keys[up] ] = map[key];
  765. } else {
  766. keyboard.map[ up ] = map[key];
  767. }
  768. }
  769. }
  770. if ( !keyboard.bound ) {
  771. keyboard.bound = true;
  772. $doc.bind('keydown', keyboard.press);
  773. }
  774. },
  775. detach: function() {
  776. keyboard.bound = false;
  777. keyboard.map = {};
  778. $doc.unbind('keydown', keyboard.press);
  779. }
  780. };
  781. // internal controls for keeping track of active / inactive images
  782. var controls = this._controls = {
  783. 0: undef,
  784. 1: undef,
  785. active : 0,
  786. swap : function() {
  787. controls.active = controls.active ? 0 : 1;
  788. },
  789. getActive : function() {
  790. return controls[ controls.active ];
  791. },
  792. getNext : function() {
  793. return controls[ 1 - controls.active ];
  794. }
  795. };
  796. // internal carousel object
  797. var carousel = this._carousel = {
  798. // shortcuts
  799. next: self.$('thumb-nav-right'),
  800. prev: self.$('thumb-nav-left'),
  801. // cache the width
  802. width: 0,
  803. // track the current position
  804. current: 0,
  805. // cache max value
  806. max: 0,
  807. // save all hooks for each width in an array
  808. hooks: [],
  809. // update the carousel
  810. // you can run this method anytime, f.ex on window.resize
  811. update: function() {
  812. var w = 0,
  813. h = 0,
  814. hooks = [0];
  815. $.each( self._thumbnails, function( i, thumb ) {
  816. if ( thumb.ready ) {
  817. w += thumb.outerWidth || $( thumb.container ).outerWidth( true );
  818. hooks[ i+1 ] = w;
  819. h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) );
  820. }
  821. });
  822. self.$( 'thumbnails' ).css({
  823. width: w,
  824. height: h
  825. });
  826. carousel.max = w;
  827. carousel.hooks = hooks;
  828. carousel.width = self.$( 'thumbnails-list' ).width();
  829. carousel.setClasses();
  830. self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width );
  831. // one extra calculation
  832. carousel.width = self.$( 'thumbnails-list' ).width();
  833. // todo: fix so the carousel moves to the left
  834. },
  835. bindControls: function() {
  836. var i;
  837. carousel.next.bind( 'click', function(e) {
  838. e.preventDefault();
  839. if ( self._options.carouselSteps === 'auto' ) {
  840. for ( i = carousel.current; i < carousel.hooks.length; i++ ) {
  841. if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) {
  842. carousel.set(i - 2);
  843. break;
  844. }
  845. }
  846. } else {
  847. carousel.set( carousel.current + self._options.carouselSteps);
  848. }
  849. });
  850. carousel.prev.bind( 'click', function(e) {
  851. e.preventDefault();
  852. if ( self._options.carouselSteps === 'auto' ) {
  853. for ( i = carousel.current; i >= 0; i-- ) {
  854. if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) {
  855. carousel.set( i + 2 );
  856. break;
  857. } else if ( i === 0 ) {
  858. carousel.set( 0 );
  859. break;
  860. }
  861. }
  862. } else {
  863. carousel.set( carousel.current - self._options.carouselSteps );
  864. }
  865. });
  866. },
  867. // calculate and set positions
  868. set: function( i ) {
  869. i = Math.max( i, 0 );
  870. while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) {
  871. i--;
  872. }
  873. carousel.current = i;
  874. carousel.animate();
  875. },
  876. // get the last position
  877. getLast: function(i) {
  878. return ( i || carousel.current ) - 1;
  879. },
  880. // follow the active image
  881. follow: function(i) {
  882. //don't follow if position fits
  883. if ( i === 0 || i === carousel.hooks.length - 2 ) {
  884. carousel.set( i );
  885. return;
  886. }
  887. // calculate last position
  888. var last = carousel.current;
  889. while( carousel.hooks[last] - carousel.hooks[ carousel.current ] <
  890. carousel.width && last <= carousel.hooks.length ) {
  891. last ++;
  892. }
  893. // set position
  894. if ( i - 1 < carousel.current ) {
  895. carousel.set( i - 1 );
  896. } else if ( i + 2 > last) {
  897. carousel.set( i - last + carousel.current + 2 );
  898. }
  899. },
  900. // helper for setting disabled classes
  901. setClasses: function() {
  902. carousel.prev.toggleClass( 'disabled', !carousel.current );
  903. carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max );
  904. },
  905. // the animation method
  906. animate: function(to) {
  907. carousel.setClasses();
  908. var num = carousel.hooks[ carousel.current ] * -1;
  909. if ( isNaN( num ) ) {
  910. return;
  911. }
  912. Utils.animate(self.get( 'thumbnails' ), {
  913. left: num
  914. },{
  915. duration: self._options.carouselSpeed,
  916. easing: self._options.easing,
  917. queue: false
  918. });
  919. }
  920. };
  921. // tooltip control
  922. // added in 1.2
  923. var tooltip = this._tooltip = {
  924. initialized : false,
  925. open: false,
  926. init: function() {
  927. tooltip.initialized = true;
  928. var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3' +
  929. 'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}';
  930. Utils.insertStyleTag(css);
  931. self.$( 'tooltip' ).css('opacity', 0.8);
  932. Utils.hide( self.get('tooltip') );
  933. },
  934. // move handler
  935. move: function( e ) {
  936. var mouseX = self.getMousePosition(e).x,
  937. mouseY = self.getMousePosition(e).y,
  938. $elem = self.$( 'tooltip' ),
  939. x = mouseX,
  940. y = mouseY,
  941. height = $elem.outerHeight( true ) + 1,
  942. width = $elem.outerWidth( true ),
  943. limitY = height + 15;
  944. var maxX = self.$( 'container').width() - width - 2,
  945. maxY = self.$( 'container').height() - height - 2;
  946. if ( !isNaN(x) && !isNaN(y) ) {
  947. x += 10;
  948. y -= 30;
  949. x = Math.max( 0, Math.min( maxX, x ) );
  950. y = Math.max( 0, Math.min( maxY, y ) );
  951. if( mouseY < limitY ) {
  952. y = limitY;
  953. }
  954. $elem.css({ left: x, top: y });
  955. }
  956. },
  957. // bind elements to the tooltip
  958. // you can bind multiple elementIDs using { elemID : function } or { elemID : string }
  959. // you can also bind single DOM elements using bind(elem, string)
  960. bind: function( elem, value ) {
  961. // todo: revise if alternative tooltip is needed for mobile devices
  962. if (Galleria.TOUCH) {
  963. return;
  964. }
  965. if (! tooltip.initialized ) {
  966. tooltip.init();
  967. }
  968. var hover = function( elem, value) {
  969. tooltip.define( elem, value );
  970. $( elem ).hover(function() {
  971. Utils.clearTimer('switch_tooltip');
  972. self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' );
  973. tooltip.show( elem );
  974. Galleria.utils.addTimer( 'tooltip', function() {
  975. self.$( 'tooltip' ).stop().show().animate({
  976. opacity:1
  977. });
  978. tooltip.open = true;
  979. }, tooltip.open ? 0 : 500);
  980. }, function() {
  981. self.$( 'container' ).unbind( 'mousemove', tooltip.move );
  982. Utils.clearTimer( 'tooltip' );
  983. self.$( 'tooltip' ).stop().animate({
  984. opacity: 0
  985. }, 200, function() {
  986. self.$( 'tooltip' ).hide();
  987. Utils.addTimer('switch_tooltip', function() {
  988. tooltip.open = false;
  989. }, 1000);
  990. });
  991. });
  992. };
  993. if ( typeof value === 'string' ) {
  994. hover( ( elem in self._dom ? self.get( elem ) : elem ), value );
  995. } else {
  996. // asume elemID here
  997. $.each( elem, function( elemID, val ) {
  998. hover( self.get(elemID), val );
  999. });
  1000. }
  1001. },
  1002. show: function( elem ) {
  1003. elem = $( elem in self._dom ? self.get(elem) : elem );
  1004. var text = elem.data( 'tt' ),
  1005. mouseup = function( e ) {
  1006. // attach a tiny settimeout to make sure the new tooltip is filled
  1007. window.setTimeout( (function( ev ) {
  1008. return function() {
  1009. tooltip.move( ev );
  1010. };
  1011. }( e )), 10);
  1012. elem.unbind( 'mouseup', mouseup );
  1013. };
  1014. text = typeof text === 'function' ? text() : text;
  1015. if ( ! text ) {
  1016. return;
  1017. }
  1018. self.$( 'tooltip' ).html( text.replace(/\s/, '&nbsp;') );
  1019. // trigger mousemove on mouseup in case of click
  1020. elem.bind( 'mouseup', mouseup );
  1021. },
  1022. define: function( elem, value ) {
  1023. // we store functions, not strings
  1024. if (typeof value !== 'function') {
  1025. var s = value;
  1026. value = function() {
  1027. return s;
  1028. };
  1029. }
  1030. elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value);
  1031. tooltip.show( elem );
  1032. }
  1033. };
  1034. // internal fullscreen control
  1035. // added in 1.195
  1036. // still kind of experimental
  1037. var fullscreen = this._fullscreen = {
  1038. scrolled: 0,
  1039. active: false,
  1040. keymap: self._keyboard.map,
  1041. enter: function(callback) {
  1042. fullscreen.active = true;
  1043. // hide the image until rescale is complete
  1044. Utils.hide( self.getActiveImage() );
  1045. self.$( 'container' ).addClass( 'fullscreen' );
  1046. fullscreen.scrolled = $win.scrollTop();
  1047. // begin styleforce
  1048. Utils.forceStyles(self.get('container'), {
  1049. position: 'fixed',
  1050. top: 0,
  1051. left: 0,
  1052. width: '100%',
  1053. height: '100%',
  1054. zIndex: 10000
  1055. });
  1056. var htmlbody = {
  1057. height: '100%',
  1058. overflow: 'hidden',
  1059. margin:0,
  1060. padding:0
  1061. },
  1062. data = self.getData();
  1063. Utils.forceStyles( DOM().html, htmlbody );
  1064. Utils.forceStyles( DOM().body, htmlbody );
  1065. // temporarily attach some keys
  1066. // save the old ones first in a cloned object
  1067. fullscreen.keymap = $.extend({}, self._keyboard.map);
  1068. self.attachKeyboard({
  1069. escape: self.exitFullscreen,
  1070. right: self.next,
  1071. left: self.prev
  1072. });
  1073. // swap to big image if it’s different from the display image
  1074. if ( data && data.big && data.image !== data.big ) {
  1075. var big = new Galleria.Picture(),
  1076. cached = big.isCached( data.big ),
  1077. index = self.getIndex(),
  1078. thumb = self._thumbnails[ index ];
  1079. self.trigger( {
  1080. type: Galleria.LOADSTART,
  1081. cached: cached,
  1082. index: index,
  1083. imageTarget: self.getActiveImage(),
  1084. thumbTarget: thumb
  1085. });
  1086. big.load( data.big, function( big ) {
  1087. self._scaleImage( big, {
  1088. complete: function( big ) {
  1089. self.trigger({
  1090. type: Galleria.LOADFINISH,
  1091. cached: cached,
  1092. index: index,
  1093. imageTarget: big.image,
  1094. thumbTarget: thumb
  1095. });
  1096. var image = self._controls.getActive().image;
  1097. if ( image ) {
  1098. $( image ).width( big.image.width ).height( big.image.height )
  1099. .attr( 'style', $( big.image ).attr('style') )
  1100. .attr( 'src', big.image.src );
  1101. }
  1102. }
  1103. });
  1104. });
  1105. }
  1106. // init the first rescale and attach callbacks
  1107. self.rescale(function() {
  1108. Utils.addTimer('fullscreen_enter', function() {
  1109. // show the image after 50 ms
  1110. Utils.show( self.getActiveImage() );
  1111. if (typeof callback === 'function') {
  1112. callback.call( self );
  1113. }
  1114. }, 100);
  1115. self.trigger( Galleria.FULLSCREEN_ENTER );
  1116. });
  1117. // bind the scaling to the resize event
  1118. $win.resize( function() {
  1119. fullscreen.scale();
  1120. } );
  1121. },
  1122. scale : function() {
  1123. self.rescale();
  1124. },
  1125. exit: function(callback) {
  1126. fullscreen.active = false;
  1127. Utils.hide( self.getActiveImage() );
  1128. self.$('container').removeClass( 'fullscreen' );
  1129. // revert all styles
  1130. Utils.revertStyles( self.get('container'), DOM().html, DOM().body );
  1131. // scroll back
  1132. window.scrollTo(0, fullscreen.scrolled);
  1133. // detach all keyboard events and apply the old keymap
  1134. self.detachKeyboard();
  1135. self.attachKeyboard( fullscreen.keymap );
  1136. self.rescale(function() {
  1137. Utils.addTimer('fullscreen_exit', function() {
  1138. // show the image after 50 ms
  1139. Utils.show( self.getActiveImage() );
  1140. if ( typeof callback === 'function' ) {
  1141. callback.call( self );
  1142. }
  1143. }, 50);
  1144. self.trigger( Galleria.FULLSCREEN_EXIT );
  1145. });
  1146. $win.unbind('resize', fullscreen.scale);
  1147. }
  1148. };
  1149. // the internal idle object for controlling idle states
  1150. var idle = this._idle = {
  1151. trunk: [],
  1152. bound: false,
  1153. add: function(elem, to) {
  1154. if (!elem) {
  1155. return;
  1156. }
  1157. if (!idle.bound) {
  1158. idle.addEvent();
  1159. }
  1160. elem = $(elem);
  1161. var from = {},
  1162. style;
  1163. for ( style in to ) {
  1164. if ( to.hasOwnProperty( style ) ) {
  1165. from[ style ] = elem.css( style );
  1166. }
  1167. }
  1168. elem.data('idle', {
  1169. from: from,
  1170. to: to,
  1171. complete: true,
  1172. busy: false
  1173. });
  1174. idle.addTimer();
  1175. idle.trunk.push(elem);
  1176. },
  1177. remove: function(elem) {
  1178. elem = jQuery(elem);
  1179. $.each(idle.trunk, function(i, el) {
  1180. if ( el.length && !el.not(elem).length ) {
  1181. self._idle.show(elem);
  1182. self._idle.trunk.splice(i, 1);
  1183. }
  1184. });
  1185. if (!idle.trunk.length) {
  1186. idle.removeEvent();
  1187. Utils.clearTimer('idle');
  1188. }
  1189. },
  1190. addEvent : function() {
  1191. idle.bound = true;
  1192. self.$('container').bind('mousemove click', idle.showAll );
  1193. },
  1194. removeEvent : function() {
  1195. idle.bound = false;
  1196. self.$('container').unbind('mousemove click', idle.showAll );
  1197. },
  1198. addTimer : function() {
  1199. Utils.addTimer('idle', function() {
  1200. self._idle.hide();
  1201. }, self._options.idleTime );
  1202. },
  1203. hide : function() {
  1204. if ( !self._options.idleMode ) {
  1205. return;
  1206. }
  1207. self.trigger( Galleria.IDLE_ENTER );
  1208. $.each( idle.trunk, function(i, elem) {
  1209. var data = elem.data('idle');
  1210. if (! data) {
  1211. return;
  1212. }
  1213. elem.data('idle').complete = false;
  1214. Utils.animate( elem, data.to, {
  1215. duration: self._options.idleSpeed
  1216. });
  1217. });
  1218. },
  1219. showAll : function() {
  1220. Utils.clearTimer('idle');
  1221. $.each(self._idle.trunk, function( i, elem ) {
  1222. self._idle.show( elem );
  1223. });
  1224. },
  1225. show: function(elem) {
  1226. var data = elem.data('idle');
  1227. if (!data.busy && !data.complete) {
  1228. data.busy = true;
  1229. self.trigger( Galleria.IDLE_EXIT );
  1230. Utils.clearTimer( 'idle' );
  1231. Utils.animate( elem, data.from, {
  1232. duration: self._options.idleSpeed/2,
  1233. complete: function() {
  1234. $(this).data('idle').busy = false;
  1235. $(this).data('idle').complete = true;
  1236. }
  1237. });
  1238. }
  1239. idle.addTimer();
  1240. }
  1241. };
  1242. // internal lightbox object
  1243. // creates a predesigned lightbox for simple popups of images in galleria
  1244. var lightbox = this._lightbox = {
  1245. width : 0,
  1246. height : 0,
  1247. initialized : false,
  1248. active : null,
  1249. image : null,
  1250. elems : {},
  1251. keymap: false,
  1252. init : function() {
  1253. // trigger the event
  1254. self.trigger( Galleria.LIGHTBOX_OPEN );
  1255. if ( lightbox.initialized ) {
  1256. return;
  1257. }
  1258. lightbox.initialized = true;
  1259. // create some elements to work with
  1260. var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image',
  1261. el = {},
  1262. op = self._options,
  1263. css = '',
  1264. abs = 'position:absolute;',
  1265. prefix = 'lightbox-',
  1266. cssMap = {
  1267. overlay: 'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+
  1268. ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990',
  1269. box: 'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991',
  1270. shadow: abs+'background:#000;width:100%;height:100%;',
  1271. content: abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden',
  1272. info: abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px',
  1273. close: abs+'top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999',
  1274. image: abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;',
  1275. prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;',
  1276. nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;',
  1277. prev: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;text-align:center;color:#000;font:bold 16px/36px arial,sans-serif',
  1278. next: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;font:bold 16px/36px arial,sans-serif;text-align:center;color:#000',
  1279. title: 'float:left',
  1280. counter: 'float:right;margin-left:8px;'
  1281. },
  1282. hover = function(elem) {
  1283. return elem.hover(
  1284. function() { $(this).css( 'color', '#bbb' ); },
  1285. function() { $(this).css( 'color', '#444' ); }
  1286. );
  1287. },
  1288. appends = {};
  1289. // IE8 fix for IE's transparent background event "feature"
  1290. if ( IE === 8 ) {
  1291. cssMap.nextholder += 'background:#000;filter:alpha(opacity=0);';
  1292. cssMap.prevholder += 'background:#000;filter:alpha(opacity=0);';
  1293. }
  1294. // create and insert CSS
  1295. $.each(cssMap, function( key, value ) {
  1296. css += '.galleria-'+prefix+key+'{'+value+'}';
  1297. });
  1298. Utils.insertStyleTag( css );
  1299. // create the elements
  1300. $.each(elems.split(' '), function( i, elemId ) {
  1301. self.addElement( 'lightbox-' + elemId );
  1302. el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId );
  1303. });
  1304. // initiate the image
  1305. lightbox.image = new Galleria.Picture();
  1306. // append the elements
  1307. $.each({
  1308. box: 'shadow content close prevholder nextholder',
  1309. info: 'title counter',
  1310. content: 'info image',
  1311. prevholder: 'prev',
  1312. nextholder: 'next'
  1313. }, function( key, val ) {
  1314. var arr = [];
  1315. $.each( val.split(' '), function( i, prop ) {
  1316. arr.push( prefix + prop );
  1317. });
  1318. appends[ prefix+key ] = arr;
  1319. });
  1320. self.append( appends );
  1321. $( el.image ).append( lightbox.image.container );
  1322. $( DOM().body ).append( el.overlay, el.box );
  1323. Utils.optimizeTouch( el.box );
  1324. // add the prev/next nav and bind some controls
  1325. hover( $( el.close ).bind( 'click', lightbox.hide ).html('&#215;') );
  1326. $.each( ['Prev','Next'], function(i, dir) {
  1327. var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '&#8249;&nbsp;' : '&nbsp;&#8250;' ),
  1328. $e = $( el[ dir.toLowerCase()+'holder'] );
  1329. $e.bind( 'click', function() {
  1330. lightbox[ 'show' + dir ]();
  1331. });
  1332. // IE7 and touch devices will simply show the nav
  1333. if ( IE < 8 || Galleria.TOUCH ) {
  1334. $d.show();
  1335. return;
  1336. }
  1337. $e.hover( function() {
  1338. $d.show();
  1339. }, function(e) {
  1340. $d.stop().fadeOut( 200 );
  1341. });
  1342. });
  1343. $( el.overlay ).bind( 'click', lightbox.hide );
  1344. // the lightbox animation is slow on ipad
  1345. if ( Galleria.IPAD ) {
  1346. self._options.lightboxTransitionSpeed = 0;
  1347. }
  1348. },
  1349. rescale: function(event) {
  1350. // calculate
  1351. var width = Math.min( $win.width()-40, lightbox.width ),
  1352. height = Math.min( $win.height()-60, lightbox.height ),
  1353. ratio = Math.min( width / lightbox.width, height / lightbox.height ),
  1354. destWidth = Math.round( lightbox.width * ratio ) + 40,
  1355. destHeight = Math.round( lightbox.height * ratio ) + 60,
  1356. to = {
  1357. width: destWidth,
  1358. height: destHeight,
  1359. 'margin-top': Math.ceil( destHeight / 2 ) *- 1,
  1360. 'margin-left': Math.ceil( destWidth / 2 ) *- 1
  1361. };
  1362. // if rescale event, don't animate
  1363. if ( event ) {
  1364. $( lightbox.elems.box ).css( to );
  1365. } else {
  1366. $( lightbox.elems.box ).animate( to, {
  1367. duration: self._options.lightboxTransitionSpeed,
  1368. easing: self._options.easing,
  1369. complete: function() {
  1370. var image = lightbox.image,
  1371. speed = self._options.lightboxFadeSpeed;
  1372. self.trigger({
  1373. type: Galleria.LIGHTBOX_IMAGE,
  1374. imageTarget: image.image
  1375. });
  1376. $( image.container ).show();
  1377. Utils.show( image.image, speed );
  1378. Utils.show( lightbox.elems.info, speed );
  1379. }
  1380. });
  1381. }
  1382. },
  1383. hide: function() {
  1384. // remove the image
  1385. lightbox.image.image = null;
  1386. $win.unbind('resize', lightbox.rescale);
  1387. $( lightbox.elems.box ).hide();
  1388. Utils.hide( lightbox.elems.info );
  1389. self.detachKeyboard();
  1390. self.attachKeyboard( lightbox.keymap );
  1391. lightbox.keymap = false;
  1392. Utils.hide( lightbox.elems.overlay, 200, function() {
  1393. $( this ).hide().css( 'opacity', self._options.overlayOpacity );
  1394. self.trigger( Galleria.LIGHTBOX_CLOSE );
  1395. });
  1396. },
  1397. showNext: function() {
  1398. lightbox.show( self.getNext( lightbox.active ) );
  1399. },
  1400. showPrev: function() {
  1401. lightbox.show( self.getPrev( lightbox.active ) );
  1402. },
  1403. show: function(index) {
  1404. lightbox.active = index = typeof index === 'number' ? index : self.getIndex();
  1405. if ( !lightbox.initialized ) {
  1406. lightbox.init();
  1407. }
  1408. // temporarily attach some keys
  1409. // save the old ones first in a cloned object
  1410. if ( !lightbox.keymap ) {
  1411. lightbox.keymap = $.extend({}, self._keyboard.map);
  1412. self.attachKeyboard({
  1413. escape: lightbox.hide,
  1414. right: lightbox.showNext,
  1415. left: lightbox.showPrev
  1416. });
  1417. }
  1418. $win.unbind('resize', lightbox.rescale );
  1419. var data = self.getData(index),
  1420. total = self.getDataLength();
  1421. Utils.hide( lightbox.elems.info );
  1422. lightbox.image.load( data.big || data.image, function( image ) {
  1423. lightbox.width = image.original.width;
  1424. lightbox.height = image.original.height;
  1425. $( image.image ).css({
  1426. width: '100.5%',
  1427. height: '100.5%',
  1428. top: 0,
  1429. zIndex: 99998
  1430. });
  1431. Utils.hide( image.image );
  1432. lightbox.elems.title.innerHTML = data.title || '';
  1433. lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total;
  1434. $win.resize( lightbox.rescale );
  1435. lightbox.rescale();
  1436. });
  1437. $( lightbox.elems.overlay ).show();
  1438. $( lightbox.elems.box ).show();
  1439. }
  1440. };
  1441. return this;
  1442. };
  1443. // end Galleria constructor
  1444. Galleria.prototype = {
  1445. // bring back the constructor reference
  1446. constructor: Galleria,
  1447. /**
  1448. Use this function to initialize the gallery and start loading.
  1449. Should only be called once per instance.
  1450. @param {HTMLElement} target The target element
  1451. @param {Object} options The gallery options
  1452. @returns Instance
  1453. */
  1454. init: function( target, options ) {
  1455. var self = this;
  1456. options = _legacyOptions( options );
  1457. // save the original ingredients
  1458. this._original = {
  1459. target: target,
  1460. options: options,
  1461. data: null
  1462. };
  1463. // save the target here
  1464. this._target = this._dom.target = target.nodeName ? target : $( target ).get(0);
  1465. // push the instance
  1466. _instances.push( this );
  1467. // raise error if no target is detected
  1468. if ( !this._target ) {
  1469. Galleria.raise('Target not found.', true);
  1470. return;
  1471. }
  1472. // apply options
  1473. this._options = {
  1474. autoplay: false,
  1475. carousel: true,
  1476. carouselFollow: true,
  1477. carouselSpeed: 400,
  1478. carouselSteps: 'auto',
  1479. clicknext: false,
  1480. dataConfig : function( elem ) { return {}; },
  1481. dataSelector: 'img',
  1482. dataSource: this._target,
  1483. debug: undef,
  1484. easing: 'galleria',
  1485. extend: function(options) {},
  1486. fullscreenDoubleTap: true, // 1.2.4 toggles fullscreen on double-tap for touch devices
  1487. height: 'auto',
  1488. idleMode: true, // 1.2.4 toggles idleMode
  1489. idleTime: 3000,
  1490. idleSpeed: 200,
  1491. imageCrop: false,
  1492. imageMargin: 0,
  1493. imagePan: false,
  1494. imagePanSmoothness: 12,
  1495. imagePosition: '50%',
  1496. initialTransition: undef, // 1.2.4, replaces transitionInitial
  1497. keepSource: false,
  1498. lightbox: false, // 1.2.3
  1499. lightboxFadeSpeed: 200,
  1500. lightboxTransitionSpeed: 200,
  1501. linkSourceTmages: true,
  1502. maxScaleRatio: undef,
  1503. minScaleRatio: undef,
  1504. overlayOpacity: 0.85,
  1505. overlayBackground: '#0b0b0b',
  1506. pauseOnInteraction: true,
  1507. popupLinks: false,
  1508. preload: 2,
  1509. protect: false,
  1510. queue: true,
  1511. show: 0,
  1512. showInfo: true,
  1513. showCounter: true,
  1514. showImagenav: true,
  1515. swipe: true, // 1.2.4
  1516. thumbCrop: true,
  1517. thumbEventType: 'click',
  1518. thumbFit: true,
  1519. thumbMargin: 0,
  1520. thumbQuality: 'auto',
  1521. thumbnails: true,
  1522. transition: 'fade',
  1523. transitionInitial: undef, // legacy, deprecate in 1.3. Use initialTransition instead.
  1524. transitionSpeed: 400,
  1525. useCanvas: false, // 1.2.4
  1526. width: 'auto'
  1527. };
  1528. // legacy support for transitionInitial
  1529. this._options.initialTransition = this._options.initialTransition || this._options.transitionInitial;
  1530. // turn off debug
  1531. if ( options && options.debug === false ) {
  1532. DEBUG = false;
  1533. }
  1534. // hide all content
  1535. $( this._target ).children().hide();
  1536. // now we just have to wait for the theme...
  1537. // is 5 seconds enough?
  1538. if ( typeof Galleria.theme === 'object' ) {
  1539. this._init();
  1540. } else {
  1541. Utils.wait({
  1542. until: function() {
  1543. return typeof Galleria.theme === 'object';
  1544. },
  1545. success: function() {
  1546. self._init.call( self );
  1547. },
  1548. error: function() {
  1549. Galleria.raise( 'No theme found.', true );
  1550. },
  1551. timeout: 5000
  1552. });
  1553. }
  1554. },
  1555. // this method should only be called once per instance
  1556. // for manipulation of data, use the .load method
  1557. _init: function() {
  1558. var self = this;
  1559. if ( this._initialized ) {
  1560. Galleria.raise( 'Init failed: Gallery instance already initialized.' );
  1561. return this;
  1562. }
  1563. this._initialized = true;
  1564. if ( !Galleria.theme ) {
  1565. Galleria.raise( 'Init failed: No theme found.' );
  1566. return this;
  1567. }
  1568. // merge the theme & caller options
  1569. $.extend( true, this._options, Galleria.theme.defaults, this._original.options );
  1570. // check for canvas support
  1571. (function( can ) {
  1572. if ( !( 'getContext' in can ) ) {
  1573. can = null;
  1574. return;
  1575. }
  1576. _canvas = _canvas || {
  1577. elem: can,
  1578. context: can.getContext( '2d' ),
  1579. cache: {},
  1580. length: 0
  1581. };
  1582. }( doc.createElement( 'canvas' ) ) );
  1583. // bind the gallery to run when data is ready
  1584. this.bind( Galleria.DATA, function() {
  1585. // save the new data
  1586. this._original.data = this._data;
  1587. // lets show the counter here
  1588. this.get('total').innerHTML = this.getDataLength();
  1589. // cache the container
  1590. var $container = this.$( 'container' );
  1591. // the gallery is ready, let's just wait for the css
  1592. var num = { width: 0, height: 0 };
  1593. var testHeight = function() {
  1594. return self.$( 'stage' ).height();
  1595. };
  1596. // check container and thumbnail height
  1597. Utils.wait({
  1598. until: function() {
  1599. // keep trying to get the value
  1600. $.each(['width', 'height'], function( i, m ) {
  1601. // first check if options is set
  1602. if ( self._options[ m ] && typeof self._options[ m ] === 'number' ) {
  1603. num[ m ] = self._options[ m ];
  1604. } else {
  1605. // else extract the measures from different sources and grab the highest value
  1606. num[ m ] = Math.max(
  1607. Utils.parseValue( $container.css( m ) ), // 1. the container css
  1608. Utils.parseValue( self.$( 'target' ).css( m ) ), // 2. the target css
  1609. $container[ m ](), // 3. the container jQuery method
  1610. self.$( 'target' )[ m ]() // 4. the container jQuery method
  1611. );
  1612. }
  1613. // apply the new measures
  1614. $container[ m ]( num[ m ] );
  1615. });
  1616. return testHeight() && num.width && num.height > 10;
  1617. },
  1618. success: function() {
  1619. // for some strange reason, webkit needs a single setTimeout to play ball
  1620. if ( Galleria.WEBKIT ) {
  1621. window.setTimeout( function() {
  1622. self._run();
  1623. }, 1);
  1624. } else {
  1625. self._run();
  1626. }
  1627. },
  1628. error: function() {
  1629. // Height was probably not set, raise hard errors
  1630. if ( testHeight() ) {
  1631. Galleria.raise('Could not extract sufficient width/height of the gallery container. Traced measures: width:' + num.width + 'px, height: ' + num.height + 'px.', true);
  1632. } else {
  1633. Galleria.raise('Could not extract a stage height from the CSS. Traced height: ' + testHeight() + 'px.', true);
  1634. }
  1635. },
  1636. timeout: 2000
  1637. });
  1638. });
  1639. // build the gallery frame
  1640. this.append({
  1641. 'info-text' :
  1642. ['info-title', 'info-description'],
  1643. 'info' :
  1644. ['info-text'],
  1645. 'image-nav' :
  1646. ['image-nav-right', 'image-nav-left'],
  1647. 'stage' :
  1648. ['images', 'loader', 'counter', 'image-nav'],
  1649. 'thumbnails-list' :
  1650. ['thumbnails'],
  1651. 'thumbnails-container' :
  1652. ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
  1653. 'container' :
  1654. ['stage', 'thumbnails-container', 'info', 'tooltip']
  1655. });
  1656. Utils.hide( this.$( 'counter' ).append(
  1657. this.get( 'current' ),
  1658. ' / ',
  1659. this.get( 'total' )
  1660. ) );
  1661. this.setCounter('&#8211;');
  1662. Utils.hide( self.get('tooltip') );
  1663. // add a notouch class on the container to prevent unwanted :hovers on touch devices
  1664. this.$( 'container' ).addClass( Galleria.TOUCH ? 'touch' : 'notouch' );
  1665. // add images to the controls
  1666. $.each( new Array(2), function(i) {
  1667. // create a new Picture instance
  1668. var image = new Galleria.Picture();
  1669. // apply some styles
  1670. $( image.container ).css({
  1671. position: 'absolute',
  1672. top: 0,
  1673. left: 0
  1674. });
  1675. // append the image
  1676. self.$( 'images' ).append( image.container );
  1677. // reload the controls
  1678. self._controls[i] = image;
  1679. });
  1680. // some forced generic styling
  1681. this.$( 'images' ).css({
  1682. position: 'relative',
  1683. top: 0,
  1684. left: 0,
  1685. width: '100%',
  1686. height: '100%'
  1687. });
  1688. this.$( 'thumbnails, thumbnails-list' ).css({
  1689. overflow: 'hidden',
  1690. position: 'relative'
  1691. });
  1692. // bind image navigation arrows
  1693. this.$( 'image-nav-right, image-nav-left' ).bind( 'click', function(e) {
  1694. // tune the clicknext option
  1695. if ( self._options.clicknext ) {
  1696. e.stopPropagation();
  1697. }
  1698. // pause if options is set
  1699. if ( self._options.pauseOnInteraction ) {
  1700. self.pause();
  1701. }
  1702. // navigate
  1703. var fn = /right/.test( this.className ) ? 'next' : 'prev';
  1704. self[ fn ]();
  1705. });
  1706. // hide controls if chosen to
  1707. $.each( ['info','counter','image-nav'], function( i, el ) {
  1708. if ( self._options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) {
  1709. Utils.moveOut( self.get( el.toLowerCase() ) );
  1710. }
  1711. });
  1712. // load up target content
  1713. this.load();
  1714. // now it's usually safe to remove the content
  1715. // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway)
  1716. if ( !this._options.keep_source && !IE ) {
  1717. this._target.innerHTML = '';
  1718. }
  1719. // append the gallery frame
  1720. this.$( 'target' ).append( this.get( 'container' ) );
  1721. // parse the carousel on each thumb load
  1722. if ( this._options.carousel ) {
  1723. this.bind( Galleria.THUMBNAIL, function() {
  1724. this.updateCarousel();
  1725. });
  1726. }
  1727. // bind swipe gesture
  1728. if ( this._options.swipe ) {
  1729. (function( images ) {
  1730. var swipeStart = [0,0],
  1731. swipeStop = [0,0],
  1732. limitX = 30,
  1733. limitY = 100,
  1734. multi = false,
  1735. tid = 0,
  1736. data,
  1737. ev = {
  1738. start: 'touchstart',
  1739. move: 'touchmove',
  1740. stop: 'touchend'
  1741. },
  1742. getData = function(e) {
  1743. return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
  1744. },
  1745. moveHandler = function( e ) {
  1746. if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
  1747. return;
  1748. }
  1749. data = getData( e );
  1750. swipeStop = [ data.pageX, data.pageY ];
  1751. if ( !swipeStart[0] ) {
  1752. swipeStart = swipeStop;
  1753. }
  1754. if ( Math.abs( swipeStart[0] - swipeStop[0] ) > 10 ) {
  1755. e.preventDefault();
  1756. }
  1757. },
  1758. upHandler = function( e ) {
  1759. images.unbind( ev.move, moveHandler );
  1760. // if multitouch (possibly zooming), abort
  1761. if ( ( e.originalEvent.touches && e.originalEvent.touches.length ) || multi ) {
  1762. multi = !multi;
  1763. return;
  1764. }
  1765. if ( Utils.timestamp() - tid < 1000 &&
  1766. Math.abs( swipeStart[0] - swipeStop[0] ) > limitX &&
  1767. Math.abs( swipeStart[1] - swipeStop[1] ) < limitY ) {
  1768. e.preventDefault();
  1769. self[ swipeStart[0] > swipeStop[0] ? 'next' : 'prev' ]();
  1770. }
  1771. swipeStart = swipeStop = [0,0];
  1772. };
  1773. images.bind(ev.start, function(e) {
  1774. if ( e.originalEvent.touches && e.originalEvent.touches.length > 1 ) {
  1775. return;
  1776. }
  1777. data = getData(e);
  1778. tid = Utils.timestamp();
  1779. swipeStart = swipeStop = [ data.pageX, data.pageY ];
  1780. images.bind(ev.move, moveHandler ).one(ev.stop, upHandler);
  1781. });
  1782. }( self.$( 'images' ) ));
  1783. // double-tap/click fullscreen toggle
  1784. if ( this._options.fullscreenDoubleTap ) {
  1785. this.$('stage').bind('touchstart', (function() {
  1786. var last, cx, cy, lx, ly, now,
  1787. getData = function(e) {
  1788. return e.originalEvent.touches ? e.originalEvent.touches[0] : e;
  1789. };
  1790. return function(e) {
  1791. now = Galleria.utils.timestamp();
  1792. cx = getData(e).pageX;
  1793. cy = getData(e).pageY;
  1794. if ( ( now - last < 500 ) && ( cx - lx < 20) && ( cy - ly < 20) ) {
  1795. self.toggleFullscreen();
  1796. e.preventDefault();
  1797. self.$('stage').unbind( 'touchend', arguments.callee );
  1798. return;
  1799. }
  1800. last = now;
  1801. lx = cx;
  1802. ly = cy;
  1803. };
  1804. }()));
  1805. }
  1806. }
  1807. // optimize touch for container
  1808. Utils.optimizeTouch(this.get('container'));
  1809. return this;
  1810. },
  1811. // Creates the thumbnails and carousel
  1812. // can be used at any time, f.ex when the data object is manipulated
  1813. _createThumbnails : function() {
  1814. this.get('total').innerHTML = this.getDataLength();
  1815. var i,
  1816. src,
  1817. thumb,
  1818. data,
  1819. $container,
  1820. self = this,
  1821. o = this._options,
  1822. // get previously active thumbnail, if exists
  1823. active = (function() {
  1824. var a = self.$('thumbnails').find('.active');
  1825. if ( !a.length ) {
  1826. return false;
  1827. }
  1828. return a.find('img').attr('src');
  1829. }()),
  1830. // cache the thumbnail option
  1831. optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null,
  1832. // move some data into the instance
  1833. // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery
  1834. // so we resort to getComputedStyle for browsers who support it
  1835. getStyle = function( prop ) {
  1836. return doc.defaultView && doc.defaultView.getComputedStyle ?
  1837. doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] :
  1838. $container.css( prop );
  1839. },
  1840. fake = function(image, index, container) {
  1841. return function() {
  1842. $( container ).append( image );
  1843. self.trigger({
  1844. type: Galleria.THUMBNAIL,
  1845. thumbTarget: image,
  1846. index: index
  1847. });
  1848. };
  1849. },
  1850. onThumbEvent = function( e ) {
  1851. // pause if option is set
  1852. if ( o.pauseOnInteraction ) {
  1853. self.pause();
  1854. }
  1855. // extract the index from the data
  1856. var index = $( e.currentTarget ).data( 'index' );
  1857. if ( self.getIndex() !== index ) {
  1858. self.show( index );
  1859. }
  1860. e.preventDefault();
  1861. },
  1862. onThumbLoad = function( thumb ) {
  1863. // scale when ready
  1864. thumb.scale({
  1865. width: thumb.data.width,
  1866. height: thumb.data.height,
  1867. crop: o.thumbCrop,
  1868. margin: o.thumbMargin,
  1869. canvas: o.useCanvas,
  1870. complete: function( thumb ) {
  1871. // shrink thumbnails to fit
  1872. var top = ['left', 'top'],
  1873. arr = ['Width', 'Height'],
  1874. m,
  1875. css;
  1876. // calculate shrinked positions
  1877. $.each(arr, function( i, measure ) {
  1878. m = measure.toLowerCase();
  1879. if ( (o.thumbCrop !== true || o.thumbCrop === m ) && o.thumbFit ) {
  1880. css = {};
  1881. css[ m ] = thumb[ m ];
  1882. $( thumb.container ).css( css );
  1883. css = {};
  1884. css[ top[ i ] ] = 0;
  1885. $( thumb.image ).css( css );
  1886. }
  1887. // cache outer measures
  1888. thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true );
  1889. });
  1890. // set high quality if downscale is moderate
  1891. Utils.toggleQuality( thumb.image,
  1892. o.thumbQuality === true ||
  1893. ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 )
  1894. );
  1895. // trigger the THUMBNAIL event
  1896. self.trigger({
  1897. type: Galleria.THUMBNAIL,
  1898. thumbTarget: thumb.image,
  1899. index: thumb.data.order
  1900. });
  1901. }
  1902. });
  1903. };
  1904. this._thumbnails = [];
  1905. this.$( 'thumbnails' ).empty();
  1906. // loop through data and create thumbnails
  1907. for( i = 0; this._data[ i ]; i++ ) {
  1908. data = this._data[ i ];
  1909. if ( o.thumbnails === true ) {
  1910. // add a new Picture instance
  1911. thumb = new Galleria.Picture(i);
  1912. // get source from thumb or image
  1913. src = data.thumb || data.image;
  1914. // append the thumbnail
  1915. this.$( 'thumbnails' ).append( thumb.container );
  1916. // cache the container
  1917. $container = $( thumb.container );
  1918. thumb.data = {
  1919. width : Utils.parseValue( getStyle( 'width' ) ),
  1920. height : Utils.parseValue( getStyle( 'height' ) ),
  1921. order : i
  1922. };
  1923. // grab & reset size for smoother thumbnail loads
  1924. if ( o.thumbFit && o.thumbCrop !== true ) {
  1925. $container.css( { width: 0, height: 0 } );
  1926. } else {
  1927. $container.css( { width: thumb.data.width, height: thumb.data.height } );
  1928. }
  1929. // load the thumbnail
  1930. thumb.load( src, onThumbLoad );
  1931. // preload all images here
  1932. if ( o.preload === 'all' ) {
  1933. thumb.add( data.image );
  1934. }
  1935. // create empty spans if thumbnails is set to 'empty'
  1936. } else if ( optval === 'empty' || optval === 'numbers' ) {
  1937. thumb = {
  1938. container: Utils.create( 'galleria-image' ),
  1939. image: Utils.create( 'img', 'span' ),
  1940. ready: true
  1941. };
  1942. // create numbered thumbnails
  1943. if ( optval === 'numbers' ) {
  1944. $( thumb.image ).text( i + 1 );
  1945. }
  1946. this.$( 'thumbnails' ).append( thumb.container );
  1947. // we need to "fake" a loading delay before we append and trigger
  1948. // 50+ should be enough
  1949. window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) );
  1950. // create null object to silent errors
  1951. } else {
  1952. thumb = {
  1953. container: null,
  1954. image: null
  1955. };
  1956. }
  1957. // add events for thumbnails
  1958. // you can control the event type using thumb_event_type
  1959. // we'll add the same event to the source if it's kept
  1960. $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null )
  1961. .data('index', i).bind( o.thumbEventType, onThumbEvent );
  1962. if (active === src) {
  1963. $( thumb.container ).addClass( 'active' );
  1964. }
  1965. this._thumbnails.push( thumb );
  1966. }
  1967. },
  1968. // the internal _run method should be called after loading data into galleria
  1969. // makes sure the gallery has proper measurements before postrun & ready
  1970. _run : function() {
  1971. var self = this;
  1972. self._createThumbnails();
  1973. // make sure we have a stageHeight && stageWidth
  1974. Utils.wait({
  1975. until: function() {
  1976. // Opera crap
  1977. if ( Galleria.OPERA ) {
  1978. self.$( 'stage' ).css( 'display', 'inline-block' );
  1979. }
  1980. self._stageWidth = self.$( 'stage' ).width();
  1981. self._stageHeight = self.$( 'stage' ).height();
  1982. return( self._stageWidth &&
  1983. self._stageHeight > 50 ); // what is an acceptable height?
  1984. },
  1985. success: function() {
  1986. // save the instance
  1987. _galleries.push( self );
  1988. // postrun some stuff after the gallery is ready
  1989. // show counter
  1990. Utils.show( self.get('counter') );
  1991. // bind carousel nav
  1992. if ( self._options.carousel ) {
  1993. self._carousel.bindControls();
  1994. }
  1995. // start autoplay
  1996. if ( self._options.autoplay ) {
  1997. self.pause();
  1998. if ( typeof self._options.autoplay === 'number' ) {
  1999. self._playtime = self._options.autoplay;
  2000. }
  2001. self.trigger( Galleria.PLAY );
  2002. self._playing = true;
  2003. }
  2004. // if second load, just do the show and return
  2005. if ( self._firstrun ) {
  2006. if ( typeof self._options.show === 'number' ) {
  2007. self.show( self._options.show );
  2008. }
  2009. return;
  2010. }
  2011. self._firstrun = true;
  2012. // bind clicknext
  2013. if ( self._options.clicknext && !Galleria.TOUCH ) {
  2014. $.each( self._data, function( i, data ) {
  2015. delete data.link;
  2016. });
  2017. self.$( 'stage' ).css({ cursor : 'pointer' }).bind( 'click', function(e) {
  2018. // pause if options is set
  2019. if ( self._options.pauseOnInteraction ) {
  2020. self.pause();
  2021. }
  2022. self.next();
  2023. });
  2024. }
  2025. // initialize the History plugin
  2026. if ( Galleria.History ) {
  2027. // bind the show method
  2028. Galleria.History.change(function(e) {
  2029. // grab history ID
  2030. var val = parseInt( e.value.replace( /\//, '' ), 10 );
  2031. // if ID is NaN, the user pressed back from the first image
  2032. // return to previous address
  2033. if (isNaN(val)) {
  2034. window.history.go(-1);
  2035. // else show the image
  2036. } else {
  2037. self.show( val, undef, true );
  2038. }
  2039. });
  2040. }
  2041. // Trigger Galleria.ready
  2042. $.each( Galleria.ready.callbacks, function() {
  2043. this.call( self, self._options );
  2044. });
  2045. self.trigger( Galleria.READY );
  2046. // call the theme init method
  2047. Galleria.theme.init.call( self, self._options );
  2048. // call the extend option
  2049. self._options.extend.call( self, self._options );
  2050. // show the initial image
  2051. // first test for permalinks in history
  2052. if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) {
  2053. self.show( HASH, undef, true );
  2054. } else if( self._data[ self._options.show ] ) {
  2055. self.show( self._options.show );
  2056. }
  2057. },
  2058. error: function() {
  2059. Galleria.raise('Stage width or height is too small to show the gallery. Traced measures: width:' + self._stageWidth + 'px, height: ' + self._stageHeight + 'px.', true);
  2060. }
  2061. });
  2062. },
  2063. /**
  2064. Loads data into the gallery.
  2065. You can call this method on an existing gallery to reload the gallery with new data.
  2066. @param {Array|string} source Optional JSON array of data or selector of where to find data in the document.
  2067. Defaults to the Galleria target or dataSource option.
  2068. @param {string} selector Optional element selector of what elements to parse.
  2069. Defaults to 'img'.
  2070. @param {Function} [config] Optional function to modify the data extraction proceedure from the selector.
  2071. See the data_config option for more information.
  2072. @returns Instance
  2073. */
  2074. load : function( source, selector, config ) {
  2075. var self = this;
  2076. // empty the data array
  2077. this._data = [];
  2078. // empty the thumbnails
  2079. this._thumbnails = [];
  2080. this.$('thumbnails').empty();
  2081. // shorten the arguments
  2082. if ( typeof selector === 'function' ) {
  2083. config = selector;
  2084. selector = null;
  2085. }
  2086. // use the source set by target
  2087. source = source || this._options.dataSource;
  2088. // use selector set by option
  2089. selector = selector || this._options.dataSelector;
  2090. // use the data_config set by option
  2091. config = config || this._options.dataConfig;
  2092. // if source is a true object, make it into an array
  2093. if( /^function Object/.test( source.constructor ) ) {
  2094. source = [source];
  2095. }
  2096. // check if the data is an array already
  2097. if ( source.constructor === Array ) {
  2098. if ( this.validate( source ) ) {
  2099. this._data = source;
  2100. this._parseData().trigger( Galleria.DATA );
  2101. } else {
  2102. Galleria.raise( 'Load failed: JSON Array not valid.' );
  2103. }
  2104. return this;
  2105. }
  2106. // loop through images and set data
  2107. $( source ).find( selector ).each( function( i, img ) {
  2108. img = $( img );
  2109. var data = {},
  2110. parent = img.parent(),
  2111. href = parent.attr( 'href' ),
  2112. rel = parent.attr( 'rel' ),
  2113. reg = /\.(png|gif|jpg|jpeg)(\?.*)?$/i;
  2114. // check if it's a link to another image
  2115. if ( reg.test( href ) ) {
  2116. data.image = href;
  2117. if ( reg.test( rel ) ) {
  2118. data.big = rel;
  2119. } else {
  2120. data.big = href;
  2121. }
  2122. // else assign the href as a link if it exists
  2123. } else if ( href ) {
  2124. data.link = href;
  2125. }
  2126. // mix default extractions with the hrefs and config
  2127. // and push it into the data array
  2128. self._data.push( $.extend({
  2129. title: img.attr('title') || '',
  2130. thumb: img.attr('src'),
  2131. image: img.attr('src'),
  2132. big: img.attr('src'),
  2133. description: img.attr('alt') || '',
  2134. link: img.attr('longdesc'),
  2135. original: img.get(0) // saved as a reference
  2136. }, data, config( img ) ) );
  2137. });
  2138. // trigger the DATA event and return
  2139. if ( this.getDataLength() ) {
  2140. this.trigger( Galleria.DATA );
  2141. } else {
  2142. Galleria.raise('Load failed: no data found.');
  2143. }
  2144. return this;
  2145. },
  2146. // make sure the data works properly
  2147. _parseData : function() {
  2148. var self = this;
  2149. $.each( this._data, function( i, data ) {
  2150. // copy image as thumb if no thumb exists
  2151. if ( 'thumb' in data === false ) {
  2152. self._data[ i ].thumb = data.image;
  2153. }
  2154. // copy image as big image if no biggie exists
  2155. if ( !'big' in data ) {
  2156. self._data[ i ].big = data.image;
  2157. }
  2158. });
  2159. return this;
  2160. },
  2161. /**
  2162. Adds and/or removes images from the gallery
  2163. Works just like Array.splice
  2164. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
  2165. @example this.splice( 2, 4 ); // removes 4 images after the second image
  2166. @returns Instance
  2167. */
  2168. splice: function() {
  2169. Array.prototype.splice.apply( this._data, Utils.array( arguments ) );
  2170. return this._parseData()._createThumbnails();
  2171. },
  2172. /**
  2173. Append images to the gallery
  2174. Works just like Array.push
  2175. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push
  2176. @example this.push({
  2177. image: 'image1.jpg'
  2178. }); // appends the image to the gallery
  2179. @returns Instance
  2180. */
  2181. push: function() {
  2182. Array.prototype.push.apply( this._data, Utils.array( arguments ) );
  2183. return this._parseData()._createThumbnails();
  2184. },
  2185. _getActive: function() {
  2186. return this._controls.getActive();
  2187. },
  2188. validate : function( data ) {
  2189. // todo: validate a custom data array
  2190. return true;
  2191. },
  2192. /**
  2193. Bind any event to Galleria
  2194. @param {string} type The Event type to listen for
  2195. @param {Function} fn The function to execute when the event is triggered
  2196. @example this.bind( 'image', function() { Galleria.log('image shown') });
  2197. @returns Instance
  2198. */
  2199. bind : function(type, fn) {
  2200. // allow 'image' instead of Galleria.IMAGE
  2201. type = _patchEvent( type );
  2202. this.$( 'container' ).bind( type, this.proxy(fn) );
  2203. return this;
  2204. },
  2205. /**
  2206. Unbind any event to Galleria
  2207. @param {string} type The Event type to forget
  2208. @returns Instance
  2209. */
  2210. unbind : function(type) {
  2211. type = _patchEvent( type );
  2212. this.$( 'container' ).unbind( type );
  2213. return this;
  2214. },
  2215. /**
  2216. Manually trigger a Galleria event
  2217. @param {string} type The Event to trigger
  2218. @returns Instance
  2219. */
  2220. trigger : function( type ) {
  2221. type = typeof type === 'object' ?
  2222. $.extend( type, { scope: this } ) :
  2223. { type: _patchEvent( type ), scope: this };
  2224. this.$( 'container' ).trigger( type );
  2225. return this;
  2226. },
  2227. /**
  2228. Assign an "idle state" to any element.
  2229. The idle state will be applied after a certain amount of idle time
  2230. Useful to hide f.ex navigation when the gallery is inactive
  2231. @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to
  2232. @param {Object} styles the CSS styles to apply
  2233. @example addIdleState( this.get('image-nav'), { opacity: 0 });
  2234. @example addIdleState( '.galleria-image-nav', { top: -200 });
  2235. @returns Instance
  2236. */
  2237. addIdleState: function( elem, styles ) {
  2238. this._idle.add.apply( this._idle, Utils.array( arguments ) );
  2239. return this;
  2240. },
  2241. /**
  2242. Removes any idle state previously set using addIdleState()
  2243. @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from.
  2244. @returns Instance
  2245. */
  2246. removeIdleState: function( elem ) {
  2247. this._idle.remove.apply( this._idle, Utils.array( arguments ) );
  2248. return this;
  2249. },
  2250. /**
  2251. Force Galleria to enter idle mode.
  2252. @returns Instance
  2253. */
  2254. enterIdleMode: function() {
  2255. this._idle.hide();
  2256. return this;
  2257. },
  2258. /**
  2259. Force Galleria to exit idle mode.
  2260. @returns Instance
  2261. */
  2262. exitIdleMode: function() {
  2263. this._idle.showAll();
  2264. return this;
  2265. },
  2266. /**
  2267. Enter FullScreen mode
  2268. @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
  2269. @returns Instance
  2270. */
  2271. enterFullscreen: function( callback ) {
  2272. this._fullscreen.enter.apply( this, Utils.array( arguments ) );
  2273. return this;
  2274. },
  2275. /**
  2276. Exits FullScreen mode
  2277. @param {Function} callback the function to be executed when the fullscreen mode is fully applied.
  2278. @returns Instance
  2279. */
  2280. exitFullscreen: function( callback ) {
  2281. this._fullscreen.exit.apply( this, Utils.array( arguments ) );
  2282. return this;
  2283. },
  2284. /**
  2285. Toggle FullScreen mode
  2286. @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed.
  2287. @returns Instance
  2288. */
  2289. toggleFullscreen: function( callback ) {
  2290. this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) );
  2291. return this;
  2292. },
  2293. /**
  2294. Adds a tooltip to any element.
  2295. You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples)
  2296. @param {HTMLElement} elem The DOM Node to attach the event to
  2297. @param {string|Function} value The tooltip message. Can also be a function that returns a string.
  2298. @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails');
  2299. @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' });
  2300. @example this.bindTooltip( { image_nav: 'Navigation' });
  2301. @returns Instance
  2302. */
  2303. bindTooltip: function( elem, value ) {
  2304. this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) );
  2305. return this;
  2306. },
  2307. /**
  2308. Note: this method is deprecated. Use refreshTooltip() instead.
  2309. Redefine a tooltip.
  2310. Use this if you want to re-apply a tooltip value to an already bound tooltip element.
  2311. @param {HTMLElement} elem The DOM Node to attach the event to
  2312. @param {string|Function} value The tooltip message. Can also be a function that returns a string.
  2313. @returns Instance
  2314. */
  2315. defineTooltip: function( elem, value ) {
  2316. this._tooltip.define.apply( this._tooltip, Utils.array(arguments) );
  2317. return this;
  2318. },
  2319. /**
  2320. Refresh a tooltip value.
  2321. Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle.
  2322. @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed
  2323. @returns Instance
  2324. */
  2325. refreshTooltip: function( elem ) {
  2326. this._tooltip.show.apply( this._tooltip, Utils.array(arguments) );
  2327. return this;
  2328. },
  2329. /**
  2330. Open a pre-designed lightbox with the currently active image.
  2331. You can control some visuals using gallery options.
  2332. @returns Instance
  2333. */
  2334. openLightbox: function() {
  2335. this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) );
  2336. return this;
  2337. },
  2338. /**
  2339. Close the lightbox.
  2340. @returns Instance
  2341. */
  2342. closeLightbox: function() {
  2343. this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) );
  2344. return this;
  2345. },
  2346. /**
  2347. Get the currently active image element.
  2348. @returns {HTMLElement} The image element
  2349. */
  2350. getActiveImage: function() {
  2351. return this._getActive().image || undef;
  2352. },
  2353. /**
  2354. Get the currently active thumbnail element.
  2355. @returns {HTMLElement} The thumbnail element
  2356. */
  2357. getActiveThumb: function() {
  2358. return this._thumbnails[ this._active ].image || undef;
  2359. },
  2360. /**
  2361. Get the mouse position relative to the gallery container
  2362. @param e The mouse event
  2363. @example
  2364. var gallery = this;
  2365. $(document).mousemove(function(e) {
  2366. console.log( gallery.getMousePosition(e).x );
  2367. });
  2368. @returns {Object} Object with x & y of the relative mouse postion
  2369. */
  2370. getMousePosition : function(e) {
  2371. return {
  2372. x: e.pageX - this.$( 'container' ).offset().left,
  2373. y: e.pageY - this.$( 'container' ).offset().top
  2374. };
  2375. },
  2376. /**
  2377. Adds a panning effect to the image
  2378. @param img The optional image element. If not specified it takes the currently active image
  2379. @returns Instance
  2380. */
  2381. addPan : function( img ) {
  2382. if ( this._options.imageCrop === false ) {
  2383. return;
  2384. }
  2385. img = $( img || this.getActiveImage() );
  2386. // define some variables and methods
  2387. var self = this,
  2388. x = img.width() / 2,
  2389. y = img.height() / 2,
  2390. destX = parseInt( img.css( 'left' ), 10 ),
  2391. destY = parseInt( img.css( 'top' ), 10 ),
  2392. curX = destX || 0,
  2393. curY = destY || 0,
  2394. distX = 0,
  2395. distY = 0,
  2396. active = false,
  2397. ts = Utils.timestamp(),
  2398. cache = 0,
  2399. move = 0,
  2400. // positions the image
  2401. position = function( dist, cur, pos ) {
  2402. if ( dist > 0 ) {
  2403. move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) );
  2404. if ( cache !== move ) {
  2405. cache = move;
  2406. if ( IE === 8 ) { // scroll is faster for IE
  2407. img.parent()[ 'scroll' + pos ]( move * -1 );
  2408. } else {
  2409. var css = {};
  2410. css[ pos.toLowerCase() ] = move;
  2411. img.css(css);
  2412. }
  2413. }
  2414. }
  2415. },
  2416. // calculates mouse position after 50ms
  2417. calculate = function(e) {
  2418. if (Utils.timestamp() - ts < 50) {
  2419. return;
  2420. }
  2421. active = true;
  2422. x = self.getMousePosition(e).x;
  2423. y = self.getMousePosition(e).y;
  2424. },
  2425. // the main loop to check
  2426. loop = function(e) {
  2427. if (!active) {
  2428. return;
  2429. }
  2430. distX = img.width() - self._stageWidth;
  2431. distY = img.height() - self._stageHeight;
  2432. destX = x / self._stageWidth * distX * -1;
  2433. destY = y / self._stageHeight * distY * -1;
  2434. curX += ( destX - curX ) / self._options.imagePanSmoothness;
  2435. curY += ( destY - curY ) / self._options.imagePanSmoothness;
  2436. position( distY, curY, 'Top' );
  2437. position( distX, curX, 'Left' );
  2438. };
  2439. // we need to use scroll in IE8 to speed things up
  2440. if ( IE === 8 ) {
  2441. img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 );
  2442. img.css({
  2443. top: 0,
  2444. left: 0
  2445. });
  2446. }
  2447. // unbind and bind event
  2448. this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate );
  2449. // loop the loop
  2450. Utils.addTimer('pan', loop, 50, true);
  2451. return this;
  2452. },
  2453. /**
  2454. Brings the scope into any callback
  2455. @param fn The callback to bring the scope into
  2456. @param scope Optional scope to bring
  2457. @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) )
  2458. @returns {Function} Return the callback with the gallery scope
  2459. */
  2460. proxy : function( fn, scope ) {
  2461. if ( typeof fn !== 'function' ) {
  2462. return function() {};
  2463. }
  2464. scope = scope || this;
  2465. return function() {
  2466. return fn.apply( scope, Utils.array( arguments ) );
  2467. };
  2468. },
  2469. /**
  2470. Removes the panning effect set by addPan()
  2471. @returns Instance
  2472. */
  2473. removePan: function() {
  2474. // todo: doublecheck IE8
  2475. this.$( 'stage' ).unbind( 'mousemove' );
  2476. Utils.clearTimer( 'pan' );
  2477. return this;
  2478. },
  2479. /**
  2480. Adds an element to the Galleria DOM array.
  2481. When you add an element here, you can access it using element ID in many API calls
  2482. @param {string} id The element ID you wish to use. You can add many elements by adding more arguments.
  2483. @example addElement('mybutton');
  2484. @example addElement('mybutton','mylink');
  2485. @returns Instance
  2486. */
  2487. addElement : function( id ) {
  2488. var dom = this._dom;
  2489. $.each( Utils.array(arguments), function( i, blueprint ) {
  2490. dom[ blueprint ] = Utils.create( 'galleria-' + blueprint );
  2491. });
  2492. return this;
  2493. },
  2494. /**
  2495. Attach keyboard events to Galleria
  2496. @param {Object} map The map object of events.
  2497. Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'.
  2498. @example
  2499. this.attachKeyboard({
  2500. right: this.next,
  2501. left: this.prev,
  2502. up: function() {
  2503. console.log( 'up key pressed' )
  2504. }
  2505. });
  2506. @returns Instance
  2507. */
  2508. attachKeyboard : function( map ) {
  2509. this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) );
  2510. return this;
  2511. },
  2512. /**
  2513. Detach all keyboard events to Galleria
  2514. @returns Instance
  2515. */
  2516. detachKeyboard : function() {
  2517. this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) );
  2518. return this;
  2519. },
  2520. /**
  2521. Fast helper for appending galleria elements that you added using addElement()
  2522. @param {string} parentID The parent element ID where the element will be appended
  2523. @param {string} childID the element ID that should be appended
  2524. @example this.addElement('myElement');
  2525. this.appendChild( 'info', 'myElement' );
  2526. @returns Instance
  2527. */
  2528. appendChild : function( parentID, childID ) {
  2529. this.$( parentID ).append( this.get( childID ) || childID );
  2530. return this;
  2531. },
  2532. /**
  2533. Fast helper for prepending galleria elements that you added using addElement()
  2534. @param {string} parentID The parent element ID where the element will be prepended
  2535. @param {string} childID the element ID that should be prepended
  2536. @example
  2537. this.addElement('myElement');
  2538. this.prependChild( 'info', 'myElement' );
  2539. @returns Instance
  2540. */
  2541. prependChild : function( parentID, childID ) {
  2542. this.$( parentID ).prepend( this.get( childID ) || childID );
  2543. return this;
  2544. },
  2545. /**
  2546. Remove an element by blueprint
  2547. @param {string} elemID The element to be removed.
  2548. You can remove multiple elements by adding arguments.
  2549. @returns Instance
  2550. */
  2551. remove : function( elemID ) {
  2552. this.$( Utils.array( arguments ).join(',') ).remove();
  2553. return this;
  2554. },
  2555. // a fast helper for building dom structures
  2556. // leave this out of the API for now
  2557. append : function( data ) {
  2558. var i, j;
  2559. for( i in data ) {
  2560. if ( data.hasOwnProperty( i ) ) {
  2561. if ( data[i].constructor === Array ) {
  2562. for( j = 0; data[i][j]; j++ ) {
  2563. this.appendChild( i, data[i][j] );
  2564. }
  2565. } else {
  2566. this.appendChild( i, data[i] );
  2567. }
  2568. }
  2569. }
  2570. return this;
  2571. },
  2572. // an internal helper for scaling according to options
  2573. _scaleImage : function( image, options ) {
  2574. options = $.extend({
  2575. width: this._stageWidth,
  2576. height: this._stageHeight,
  2577. crop: this._options.imageCrop,
  2578. max: this._options.maxScaleRatio,
  2579. min: this._options.minScaleRatio,
  2580. margin: this._options.imageMargin,
  2581. position: this._options.imagePosition
  2582. }, options );
  2583. ( image || this._controls.getActive() ).scale( options );
  2584. return this;
  2585. },
  2586. /**
  2587. Updates the carousel,
  2588. useful if you resize the gallery and want to re-check if the carousel nav is needed.
  2589. @returns Instance
  2590. */
  2591. updateCarousel : function() {
  2592. this._carousel.update();
  2593. return this;
  2594. },
  2595. /**
  2596. Rescales the gallery
  2597. @param {number} width The target width
  2598. @param {number} height The target height
  2599. @param {Function} complete The callback to be called when the scaling is complete
  2600. @returns Instance
  2601. */
  2602. rescale : function( width, height, complete ) {
  2603. var self = this;
  2604. // allow rescale(fn)
  2605. if ( typeof width === 'function' ) {
  2606. complete = width;
  2607. width = undef;
  2608. }
  2609. var scale = function() {
  2610. // set stagewidth
  2611. self._stageWidth = width || self.$( 'stage' ).width();
  2612. self._stageHeight = height || self.$( 'stage' ).height();
  2613. // scale the active image
  2614. self._scaleImage();
  2615. if ( self._options.carousel ) {
  2616. self.updateCarousel();
  2617. }
  2618. self.trigger( Galleria.RESCALE );
  2619. if ( typeof complete === 'function' ) {
  2620. complete.call( self );
  2621. }
  2622. };
  2623. if ( Galleria.WEBKIT && !width && !height ) {
  2624. Utils.addTimer( 'scale', scale, 10 );// webkit is too fast
  2625. } else {
  2626. scale.call( self );
  2627. }
  2628. return this;
  2629. },
  2630. /**
  2631. Refreshes the gallery.
  2632. Useful if you change image options at runtime and want to apply the changes to the active image.
  2633. @returns Instance
  2634. */
  2635. refreshImage : function() {
  2636. this._scaleImage();
  2637. if ( this._options.imagePan ) {
  2638. this.addPan();
  2639. }
  2640. return this;
  2641. },
  2642. /**
  2643. Shows an image by index
  2644. @param {number|boolean} index The index to show
  2645. @param {Boolean} rewind A boolean that should be true if you want the transition to go back
  2646. @returns Instance
  2647. */
  2648. show : function( index, rewind, _history ) {
  2649. // do nothing if index is false or queue is false and transition is in progress
  2650. if ( index === false || ( !this._options.queue && this._queue.stalled ) ) {
  2651. return;
  2652. }
  2653. index = Math.max( 0, Math.min( parseInt( index, 10 ), this.getDataLength() - 1 ) );
  2654. rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex();
  2655. _history = _history || false;
  2656. // do the history thing and return
  2657. if ( !_history && Galleria.History ) {
  2658. Galleria.History.value( index.toString() );
  2659. return;
  2660. }
  2661. this._active = index;
  2662. Array.prototype.push.call( this._queue, {
  2663. index : index,
  2664. rewind : rewind
  2665. });
  2666. if ( !this._queue.stalled ) {
  2667. this._show();
  2668. }
  2669. return this;
  2670. },
  2671. // the internal _show method does the actual showing
  2672. _show : function() {
  2673. // shortcuts
  2674. var self = this,
  2675. queue = this._queue[ 0 ],
  2676. data = this.getData( queue.index );
  2677. if ( !data ) {
  2678. return;
  2679. }
  2680. var src = this.isFullscreen() && 'big' in data ? data.big : data.image, // use big image if fullscreen mode
  2681. active = this._controls.getActive(),
  2682. next = this._controls.getNext(),
  2683. cached = next.isCached( src ),
  2684. thumb = this._thumbnails[ queue.index ];
  2685. // to be fired when loading & transition is complete:
  2686. var complete = (function( data, next, active, queue, thumb ) {
  2687. return function() {
  2688. var win;
  2689. // remove stalled
  2690. self._queue.stalled = false;
  2691. // optimize quality
  2692. Utils.toggleQuality( next.image, self._options.imageQuality );
  2693. // swap
  2694. $( active.container ).css({
  2695. zIndex: 0,
  2696. opacity: 0
  2697. }).show();
  2698. $( next.container ).css({
  2699. zIndex: 1,
  2700. opacity: 1
  2701. }).show();
  2702. self._controls.swap();
  2703. // add pan according to option
  2704. if ( self._options.imagePan ) {
  2705. self.addPan( next.image );
  2706. }
  2707. // make the image link or add lightbox
  2708. // link takes precedence over lightbox if both are detected
  2709. if ( data.link || self._options.lightbox ) {
  2710. $( next.image ).css({
  2711. cursor: 'pointer'
  2712. }).bind( 'mouseup', function() {
  2713. // popup link
  2714. if ( data.link ) {
  2715. if ( self._options.popupLinks ) {
  2716. win = window.open( data.link, '_blank' );
  2717. } else {
  2718. window.location.href = data.link;
  2719. }
  2720. return;
  2721. }
  2722. self.openLightbox();
  2723. });
  2724. }
  2725. // remove the queued image
  2726. Array.prototype.shift.call( self._queue );
  2727. // if we still have images in the queue, show it
  2728. if ( self._queue.length ) {
  2729. self._show();
  2730. }
  2731. // check if we are playing
  2732. self._playCheck();
  2733. // trigger IMAGE event
  2734. self.trigger({
  2735. type: Galleria.IMAGE,
  2736. index: queue.index,
  2737. imageTarget: next.image,
  2738. thumbTarget: thumb.image
  2739. });
  2740. };
  2741. }( data, next, active, queue, thumb ));
  2742. // let the carousel follow
  2743. if ( this._options.carousel && this._options.carouselFollow ) {
  2744. this._carousel.follow( queue.index );
  2745. }
  2746. // preload images
  2747. if ( this._options.preload ) {
  2748. var p, i,
  2749. n = this.getNext(),
  2750. ndata;
  2751. try {
  2752. for ( i = this._options.preload; i > 0; i-- ) {
  2753. p = new Galleria.Picture();
  2754. ndata = self.getData( n );
  2755. p.add( this.isFullscreen() && 'big' in ndata ? ndata.big : ndata.image );
  2756. n = self.getNext( n );
  2757. }
  2758. } catch(e) {}
  2759. }
  2760. // show the next image, just in case
  2761. Utils.show( next.container );
  2762. // add active classes
  2763. $( self._thumbnails[ queue.index ].container )
  2764. .addClass( 'active' )
  2765. .siblings( '.active' )
  2766. .removeClass( 'active' );
  2767. // trigger the LOADSTART event
  2768. self.trigger( {
  2769. type: Galleria.LOADSTART,
  2770. cached: cached,
  2771. index: queue.index,
  2772. imageTarget: next.image,
  2773. thumbTarget: thumb.image
  2774. });
  2775. // begin loading the next image
  2776. next.load( src, function( next ) {
  2777. self._scaleImage( next, {
  2778. complete: function( next ) {
  2779. // toggle low quality for IE
  2780. if ( 'image' in active ) {
  2781. Utils.toggleQuality( active.image, false );
  2782. }
  2783. Utils.toggleQuality( next.image, false );
  2784. // stall the queue
  2785. self._queue.stalled = true;
  2786. // remove the image panning, if applied
  2787. // TODO: rethink if this is necessary
  2788. self.removePan();
  2789. // set the captions and counter
  2790. self.setInfo( queue.index );
  2791. self.setCounter( queue.index );
  2792. // trigger the LOADFINISH event
  2793. self.trigger({
  2794. type: Galleria.LOADFINISH,
  2795. cached: cached,
  2796. index: queue.index,
  2797. imageTarget: next.image,
  2798. thumbTarget: self._thumbnails[ queue.index ].image
  2799. });
  2800. var transition = active.image === null && self._options.initialTransition !== undef ?
  2801. self._options.initialTransition : self._options.transition;
  2802. // validate the transition
  2803. if ( transition in _transitions === false ) {
  2804. complete();
  2805. } else {
  2806. var params = {
  2807. prev: active.container,
  2808. next: next.container,
  2809. rewind: queue.rewind,
  2810. speed: self._options.transitionSpeed || 400
  2811. };
  2812. // call the transition function and send some stuff
  2813. _transitions[ transition ].call(self, params, complete );
  2814. }
  2815. }
  2816. });
  2817. });
  2818. },
  2819. /**
  2820. Gets the next index
  2821. @param {number} base Optional starting point
  2822. @returns {number} the next index, or the first if you are at the first (looping)
  2823. */
  2824. getNext : function( base ) {
  2825. base = typeof base === 'number' ? base : this.getIndex();
  2826. return base === this.getDataLength() - 1 ? 0 : base + 1;
  2827. },
  2828. /**
  2829. Gets the previous index
  2830. @param {number} base Optional starting point
  2831. @returns {number} the previous index, or the last if you are at the first (looping)
  2832. */
  2833. getPrev : function( base ) {
  2834. base = typeof base === 'number' ? base : this.getIndex();
  2835. return base === 0 ? this.getDataLength() - 1 : base - 1;
  2836. },
  2837. /**
  2838. Shows the next image in line
  2839. @returns Instance
  2840. */
  2841. next : function() {
  2842. if ( this.getDataLength() > 1 ) {
  2843. this.show( this.getNext(), false );
  2844. }
  2845. return this;
  2846. },
  2847. /**
  2848. Shows the previous image in line
  2849. @returns Instance
  2850. */
  2851. prev : function() {
  2852. if ( this.getDataLength() > 1 ) {
  2853. this.show( this.getPrev(), true );
  2854. }
  2855. return this;
  2856. },
  2857. /**
  2858. Retrieve a DOM element by element ID
  2859. @param {string} elemId The delement ID to fetch
  2860. @returns {HTMLElement} The elements DOM node or null if not found.
  2861. */
  2862. get : function( elemId ) {
  2863. return elemId in this._dom ? this._dom[ elemId ] : null;
  2864. },
  2865. /**
  2866. Retrieve a data object
  2867. @param {number} index The data index to retrieve.
  2868. If no index specified it will take the currently active image
  2869. @returns {Object} The data object
  2870. */
  2871. getData : function( index ) {
  2872. return index in this._data ?
  2873. this._data[ index ] : this._data[ this._active ];
  2874. },
  2875. /**
  2876. Retrieve the number of data items
  2877. @returns {number} The data length
  2878. */
  2879. getDataLength : function() {
  2880. return this._data.length;
  2881. },
  2882. /**
  2883. Retrieve the currently active index
  2884. @returns {number|boolean} The active index or false if none found
  2885. */
  2886. getIndex : function() {
  2887. return typeof this._active === 'number' ? this._active : false;
  2888. },
  2889. /**
  2890. Retrieve the stage height
  2891. @returns {number} The stage height
  2892. */
  2893. getStageHeight : function() {
  2894. return this._stageHeight;
  2895. },
  2896. /**
  2897. Retrieve the stage width
  2898. @returns {number} The stage width
  2899. */
  2900. getStageWidth : function() {
  2901. return this._stageWidth;
  2902. },
  2903. /**
  2904. Retrieve the option
  2905. @param {string} key The option key to retrieve. If no key specified it will return all options in an object.
  2906. @returns option or options
  2907. */
  2908. getOptions : function( key ) {
  2909. return typeof key === 'undefined' ? this._options : this._options[ key ];
  2910. },
  2911. /**
  2912. Set options to the instance.
  2913. You can set options using a key & value argument or a single object argument (see examples)
  2914. @param {string} key The option key
  2915. @param {string} value the the options value
  2916. @example setOptions( 'autoplay', true )
  2917. @example setOptions({ autoplay: true });
  2918. @returns Instance
  2919. */
  2920. setOptions : function( key, value ) {
  2921. if ( typeof key === 'object' ) {
  2922. $.extend( this._options, key );
  2923. } else {
  2924. this._options[ key ] = value;
  2925. }
  2926. return this;
  2927. },
  2928. /**
  2929. Starts playing the slideshow
  2930. @param {number} delay Sets the slideshow interval in milliseconds.
  2931. If you set it once, you can just call play() and get the same interval the next time.
  2932. @returns Instance
  2933. */
  2934. play : function( delay ) {
  2935. this._playing = true;
  2936. this._playtime = delay || this._playtime;
  2937. this._playCheck();
  2938. this.trigger( Galleria.PLAY );
  2939. return this;
  2940. },
  2941. /**
  2942. Stops the slideshow if currently playing
  2943. @returns Instance
  2944. */
  2945. pause : function() {
  2946. this._playing = false;
  2947. this.trigger( Galleria.PAUSE );
  2948. return this;
  2949. },
  2950. /**
  2951. Toggle between play and pause events.
  2952. @param {number} delay Sets the slideshow interval in milliseconds.
  2953. @returns Instance
  2954. */
  2955. playToggle : function( delay ) {
  2956. return ( this._playing ) ? this.pause() : this.play( delay );
  2957. },
  2958. /**
  2959. Checks if the gallery is currently playing
  2960. @returns {Boolean}
  2961. */
  2962. isPlaying : function() {
  2963. return this._playing;
  2964. },
  2965. /**
  2966. Checks if the gallery is currently in fullscreen mode
  2967. @returns {Boolean}
  2968. */
  2969. isFullscreen : function() {
  2970. return this._fullscreen.active;
  2971. },
  2972. _playCheck : function() {
  2973. var self = this,
  2974. played = 0,
  2975. interval = 20,
  2976. now = Utils.timestamp(),
  2977. timer_id = 'play' + this._id;
  2978. if ( this._playing ) {
  2979. Utils.clearTimer( timer_id );
  2980. var fn = function() {
  2981. played = Utils.timestamp() - now;
  2982. if ( played >= self._playtime && self._playing ) {
  2983. Utils.clearTimer( timer_id );
  2984. self.next();
  2985. return;
  2986. }
  2987. if ( self._playing ) {
  2988. // trigger the PROGRESS event
  2989. self.trigger({
  2990. type: Galleria.PROGRESS,
  2991. percent: Math.ceil( played / self._playtime * 100 ),
  2992. seconds: Math.floor( played / 1000 ),
  2993. milliseconds: played
  2994. });
  2995. Utils.addTimer( timer_id, fn, interval );
  2996. }
  2997. };
  2998. Utils.addTimer( timer_id, fn, interval );
  2999. }
  3000. },
  3001. setIndex: function( val ) {
  3002. this._active = val;
  3003. return this;
  3004. },
  3005. /**
  3006. Manually modify the counter
  3007. @param {number} index Optional data index to fectch,
  3008. if no index found it assumes the currently active index
  3009. @returns Instance
  3010. */
  3011. setCounter: function( index ) {
  3012. if ( typeof index === 'number' ) {
  3013. index++;
  3014. } else if ( typeof index === 'undefined' ) {
  3015. index = this.getIndex()+1;
  3016. }
  3017. this.get( 'current' ).innerHTML = index;
  3018. if ( IE ) { // weird IE bug
  3019. var count = this.$( 'counter' ),
  3020. opacity = count.css( 'opacity' ),
  3021. style = count.attr('style');
  3022. if ( style && parseInt( opacity, 10 ) === 1) {
  3023. count.attr('style', style.replace(/filter[^\;]+\;/i,''));
  3024. } else {
  3025. this.$( 'counter' ).css( 'opacity', opacity );
  3026. }
  3027. }
  3028. return this;
  3029. },
  3030. /**
  3031. Manually set captions
  3032. @param {number} index Optional data index to fectch and apply as caption,
  3033. if no index found it assumes the currently active index
  3034. @returns Instance
  3035. */
  3036. setInfo : function( index ) {
  3037. var self = this,
  3038. data = this.getData( index );
  3039. $.each( ['title','description'], function( i, type ) {
  3040. var elem = self.$( 'info-' + type );
  3041. if ( !!data[type] ) {
  3042. elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] );
  3043. } else {
  3044. elem.empty().hide();
  3045. }
  3046. });
  3047. return this;
  3048. },
  3049. /**
  3050. Checks if the data contains any captions
  3051. @param {number} index Optional data index to fectch,
  3052. if no index found it assumes the currently active index.
  3053. @returns {boolean}
  3054. */
  3055. hasInfo : function( index ) {
  3056. var check = 'title description'.split(' '),
  3057. i;
  3058. for ( i = 0; check[i]; i++ ) {
  3059. if ( !!this.getData( index )[ check[i] ] ) {
  3060. return true;
  3061. }
  3062. }
  3063. return false;
  3064. },
  3065. jQuery : function( str ) {
  3066. var self = this,
  3067. ret = [];
  3068. $.each( str.split(','), function( i, elemId ) {
  3069. elemId = $.trim( elemId );
  3070. if ( self.get( elemId ) ) {
  3071. ret.push( elemId );
  3072. }
  3073. });
  3074. var jQ = $( self.get( ret.shift() ) );
  3075. $.each( ret, function( i, elemId ) {
  3076. jQ = jQ.add( self.get( elemId ) );
  3077. });
  3078. return jQ;
  3079. },
  3080. /**
  3081. Converts element IDs into a jQuery collection
  3082. You can call for multiple IDs separated with commas.
  3083. @param {string} str One or more element IDs (comma-separated)
  3084. @returns jQuery
  3085. @example this.$('info,container').hide();
  3086. */
  3087. $ : function( str ) {
  3088. return this.jQuery.apply( this, Utils.array( arguments ) );
  3089. }
  3090. };
  3091. // End of Galleria prototype
  3092. // Add events as static variables
  3093. $.each( _events, function( i, ev ) {
  3094. // legacy events
  3095. var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev;
  3096. Galleria[ ev.toUpperCase() ] = 'galleria.'+type;
  3097. } );
  3098. $.extend( Galleria, {
  3099. // Browser helpers
  3100. IE9: IE === 9,
  3101. IE8: IE === 8,
  3102. IE7: IE === 7,
  3103. IE6: IE === 6,
  3104. IE: !!IE,
  3105. WEBKIT: /webkit/.test( NAV ),
  3106. SAFARI: /safari/.test( NAV ),
  3107. CHROME: /chrome/.test( NAV ),
  3108. QUIRK: ( IE && doc.compatMode && doc.compatMode === "BackCompat" ),
  3109. MAC: /mac/.test( navigator.platform.toLowerCase() ),
  3110. OPERA: !!window.opera,
  3111. IPHONE: /iphone/.test( NAV ),
  3112. IPAD: /ipad/.test( NAV ),
  3113. ANDROID: /android/.test( NAV ),
  3114. TOUCH: ('ontouchstart' in document)
  3115. });
  3116. // Galleria static methods
  3117. /**
  3118. Adds a theme that you can use for your Gallery
  3119. @param {Object} theme Object that should contain all your theme settings.
  3120. <ul>
  3121. <li>name – name of the theme</li>
  3122. <li>author - name of the author</li>
  3123. <li>css - css file name (not path)</li>
  3124. <li>defaults - default options to apply, including theme-specific options</li>
  3125. <li>init - the init function</li>
  3126. </ul>
  3127. @returns {Object} theme
  3128. */
  3129. Galleria.addTheme = function( theme ) {
  3130. // make sure we have a name
  3131. if ( !theme.name ) {
  3132. Galleria.raise('No theme name specified');
  3133. }
  3134. if ( typeof theme.defaults !== 'object' ) {
  3135. theme.defaults = {};
  3136. } else {
  3137. theme.defaults = _legacyOptions( theme.defaults );
  3138. }
  3139. var css = false,
  3140. reg;
  3141. if ( typeof theme.css === 'string' ) {
  3142. // look for manually added CSS
  3143. $('link').each(function( i, link ) {
  3144. reg = new RegExp( theme.css );
  3145. if ( reg.test( link.href ) ) {
  3146. // we found the css
  3147. css = true;
  3148. Galleria.theme = theme;
  3149. return false;
  3150. }
  3151. });
  3152. // else look for the absolute path and load the CSS dynamic
  3153. if ( !css ) {
  3154. $('script').each(function( i, script ) {
  3155. // look for the theme script
  3156. reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' );
  3157. if( reg.test( script.src )) {
  3158. // we have a match
  3159. css = script.src.replace(/[^\/]*$/, '') + theme.css;
  3160. Utils.addTimer( "css", function() {
  3161. Utils.loadCSS( css, 'galleria-theme', function() {
  3162. Galleria.theme = theme;
  3163. });
  3164. }, 1);
  3165. }
  3166. });
  3167. }
  3168. if ( !css ) {
  3169. Galleria.raise('No theme CSS loaded');
  3170. }
  3171. } else {
  3172. // pass
  3173. Galleria.theme = theme;
  3174. }
  3175. return theme;
  3176. };
  3177. /**
  3178. loadTheme loads a theme js file and attaches a load event to Galleria
  3179. @param {string} src The relative path to the theme source file
  3180. @param {Object} [options] Optional options you want to apply
  3181. */
  3182. Galleria.loadTheme = function( src, options ) {
  3183. var loaded = false,
  3184. length = _galleries.length;
  3185. // first clear the current theme, if exists
  3186. Galleria.theme = undef;
  3187. // load the theme
  3188. Utils.loadScript( src, function() {
  3189. loaded = true;
  3190. } );
  3191. // set a 2 sec timeout, then display a hard error if no theme is loaded
  3192. Utils.wait({
  3193. until: function() {
  3194. return loaded;
  3195. },
  3196. error: function() {
  3197. Galleria.raise( "Theme at " + src + " could not load, check theme path.", true );
  3198. },
  3199. success: function() {
  3200. // check for existing galleries and reload them with the new theme
  3201. if ( length ) {
  3202. // temporary save the new galleries
  3203. var refreshed = [];
  3204. // refresh all instances
  3205. // when adding a new theme to an existing gallery, all options will be resetted but the data will be kept
  3206. // you can apply new options as a second argument
  3207. $.each( Galleria.get(), function(i, instance) {
  3208. // mix the old data and options into the new instance
  3209. var op = $.extend( instance._original.options, {
  3210. data_source: instance._data
  3211. }, options);
  3212. // remove the old container
  3213. instance.$('container').remove();
  3214. // create a new instance
  3215. var g = new Galleria();
  3216. // move the id
  3217. g._id = instance._id;
  3218. // initialize the new instance
  3219. g.init( instance._original.target, op );
  3220. // push the new instance
  3221. refreshed.push( g );
  3222. });
  3223. // now overwrite the old holder with the new instances
  3224. _galleries = refreshed;
  3225. }
  3226. },
  3227. timeout: 2000
  3228. });
  3229. };
  3230. /**
  3231. Retrieves a Galleria instance.
  3232. @param {number} [index] Optional index to retrieve.
  3233. If no index is supplied, the method will return all instances in an array.
  3234. @returns Instance or Array of instances
  3235. */
  3236. Galleria.get = function( index ) {
  3237. if ( !!_instances[ index ] ) {
  3238. return _instances[ index ];
  3239. } else if ( typeof index !== 'number' ) {
  3240. return _instances;
  3241. } else {
  3242. Galleria.raise('Gallery index ' + index + ' not found');
  3243. }
  3244. };
  3245. /**
  3246. Creates a transition to be used in your gallery
  3247. @param {string} name The name of the transition that you will use as an option
  3248. @param {Function} fn The function to be executed in the transition.
  3249. The function contains two arguments, params and complete.
  3250. Use the params Object to integrate the transition, and then call complete when you are done.
  3251. */
  3252. Galleria.addTransition = function( name, fn ) {
  3253. _transitions[name] = fn;
  3254. };
  3255. Galleria.utils = Utils;
  3256. /**
  3257. A helper metod for cross-browser logging.
  3258. It uses the console log if available otherwise it falls back to the opera
  3259. debugger and finally <code>alert()</code>
  3260. @example Galleria.log("hello", document.body, [1,2,3]);
  3261. */
  3262. Galleria.log = function() {
  3263. try {
  3264. window.console.log.apply( window.console, Utils.array( arguments ) );
  3265. } catch( e ) {
  3266. try {
  3267. window.opera.postError.apply( window.opera, arguments );
  3268. } catch( er ) {
  3269. window.alert( Utils.array( arguments ).split(', ') );
  3270. }
  3271. }
  3272. };
  3273. /**
  3274. A ready method for adding callbacks when a gallery is ready
  3275. Each method is call before the extend option for every instance
  3276. @param {function} callback The function to call
  3277. */
  3278. Galleria.ready = function( fn ) {
  3279. Galleria.ready.callbacks.push( fn );
  3280. };
  3281. Galleria.ready.callbacks = [];
  3282. /**
  3283. Method for raising errors
  3284. @param {string} msg The message to throw
  3285. @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error
  3286. */
  3287. Galleria.raise = function( msg, fatal ) {
  3288. var type = fatal ? 'Fatal error' : 'Error',
  3289. self = this,
  3290. echo = function( msg ) {
  3291. var html = '<div style="padding:4px;margin:0 0 2px;background:#' +
  3292. ( fatal ? '811' : '222' ) + '";>' +
  3293. ( fatal ? '<strong>' + type + ': </strong>' : '' ) +
  3294. msg + '</div>';
  3295. $.each( _instances, function() {
  3296. var cont = this.$( 'errors' ),
  3297. target = this.$( 'target' );
  3298. if ( !cont.length ) {
  3299. target.css( 'position', 'relative' );
  3300. cont = this.addElement( 'errors' ).appendChild( 'target', 'errors' ).$( 'errors' ).css({
  3301. color: '#fff',
  3302. position: 'absolute',
  3303. top: 0,
  3304. left: 0,
  3305. zIndex: 100000
  3306. });
  3307. }
  3308. cont.append( html );
  3309. });
  3310. };
  3311. // if debug is on, display errors and throw exception if fatal
  3312. if ( DEBUG ) {
  3313. echo( msg );
  3314. if ( fatal ) {
  3315. throw new Error(type + ': ' + msg);
  3316. }
  3317. // else just echo a silent generic error if fatal
  3318. } else if ( fatal ) {
  3319. if ( _hasError ) {
  3320. return;
  3321. }
  3322. _hasError = true;
  3323. fatal = false;
  3324. echo( 'Image gallery could not load.' );
  3325. }
  3326. };
  3327. /**
  3328. Adds preload, cache, scale and crop functionality
  3329. @constructor
  3330. @requires jQuery
  3331. @param {number} [id] Optional id to keep track of instances
  3332. */
  3333. Galleria.Picture = function( id ) {
  3334. // save the id
  3335. this.id = id || null;
  3336. // the image should be null until loaded
  3337. this.image = null;
  3338. // Create a new container
  3339. this.container = Utils.create('galleria-image');
  3340. // add container styles
  3341. $( this.container ).css({
  3342. overflow: 'hidden',
  3343. position: 'relative' // for IE Standards mode
  3344. });
  3345. // saves the original measurements
  3346. this.original = {
  3347. width: 0,
  3348. height: 0
  3349. };
  3350. // flag when the image is ready
  3351. this.ready = false;
  3352. // flag when the image is loaded
  3353. this.loaded = false;
  3354. };
  3355. Galleria.Picture.prototype = {
  3356. // the inherited cache object
  3357. cache: {},
  3358. // creates a new image and adds it to cache when loaded
  3359. add: function( src ) {
  3360. var i = 0,
  3361. self = this,
  3362. // create the image
  3363. image = new Image(),
  3364. onload = function() {
  3365. // force chrome to reload the image in case of cache bug
  3366. // set a limit just in case
  3367. if ( ( !this.width || !this.height ) && i < 1000 ) {
  3368. i++;
  3369. $( image ).load( onload ).attr( 'src', src+'?'+new Date().getTime() );
  3370. }
  3371. self.original = {
  3372. height: this.height,
  3373. width: this.width
  3374. };
  3375. self.cache[ src ] = src; // will override old cache
  3376. self.loaded = true;
  3377. };
  3378. // force a block display
  3379. $( image ).css( 'display', 'block');
  3380. if ( self.cache[ src ] ) {
  3381. // no need to onload if the image is cached
  3382. image.src = src;
  3383. onload.call( image );
  3384. return image;
  3385. }
  3386. // begin preload and insert in cache when done
  3387. $( image ).load( onload ).error( function() {
  3388. Galleria.raise('image could not load: '+ src);
  3389. }).attr( 'src', src );
  3390. return image;
  3391. },
  3392. // show the image on stage
  3393. show: function() {
  3394. Utils.show( this.image );
  3395. },
  3396. // hide the image
  3397. hide: function() {
  3398. Utils.moveOut( this.image );
  3399. },
  3400. clear: function() {
  3401. this.image = null;
  3402. },
  3403. /**
  3404. Checks if an image is in cache
  3405. @param {string} src The image source path, ex '/path/to/img.jpg'
  3406. @returns {boolean}
  3407. */
  3408. isCached: function( src ) {
  3409. return !!this.cache[src];
  3410. },
  3411. /**
  3412. Loads an image and call the callback when ready.
  3413. Will also add the image to cache.
  3414. @param {string} src The image source path, ex '/path/to/img.jpg'
  3415. @param {Function} callback The function to be executed when the image is loaded & scaled
  3416. @returns The image container (jQuery object)
  3417. */
  3418. load: function(src, callback) {
  3419. // save the instance
  3420. var self = this;
  3421. $( this.container ).empty(true);
  3422. // add the image to cache and hide it
  3423. this.image = this.add( src );
  3424. Utils.hide( this.image );
  3425. // append the image into the container
  3426. $( this.container ).append( this.image );
  3427. // check for loaded image using a timeout
  3428. Utils.wait({
  3429. until: function() {
  3430. // TODO this should be properly tested in Opera
  3431. return self.loaded && self.image.complete && self.original.width && self.image.width;
  3432. },
  3433. success: function() {
  3434. // call success
  3435. window.setTimeout(function() { callback.call( self, self ); }, 1 );
  3436. },
  3437. error: function() {
  3438. window.setTimeout(function() { callback.call( self, self ); }, 1 );
  3439. Galleria.raise('image not loaded in 30 seconds: '+ src);
  3440. },
  3441. timeout: 30000
  3442. });
  3443. // return the container
  3444. return this.container;
  3445. },
  3446. /**
  3447. Scales and crops the image
  3448. @param {Object} options The method takes an object with a number of options:
  3449. <ul>
  3450. <li>width - width of the container</li>
  3451. <li>height - height of the container</li>
  3452. <li>min - minimum scale ratio</li>
  3453. <li>max - maximum scale ratio</li>
  3454. <li>margin - distance in pixels from the image border to the container</li>
  3455. <li>complete - a callback that fires when scaling is complete</li>
  3456. <li>position - positions the image, works like the css background-image property.</li>
  3457. <li>crop - defines how to crop. Can be true, false, 'width' or 'height'</li>
  3458. <li>canvas - set to true to try a canvas-based rescale</li>
  3459. </ul>
  3460. @returns The image container object (jQuery)
  3461. */
  3462. scale: function( options ) {
  3463. // extend some defaults
  3464. options = $.extend({
  3465. width: 0,
  3466. height: 0,
  3467. min: undef,
  3468. max: undef,
  3469. margin: 0,
  3470. complete: function() {},
  3471. position: 'center',
  3472. crop: false,
  3473. canvas: false
  3474. }, options);
  3475. // return the element if no image found
  3476. if (!this.image) {
  3477. return this.container;
  3478. }
  3479. // store locale variables
  3480. var width,
  3481. height,
  3482. self = this,
  3483. $container = $( self.container ),
  3484. data;
  3485. // wait for the width/height
  3486. Utils.wait({
  3487. until: function() {
  3488. width = options.width ||
  3489. $container.width() ||
  3490. Utils.parseValue( $container.css('width') );
  3491. height = options.height ||
  3492. $container.height() ||
  3493. Utils.parseValue( $container.css('height') );
  3494. return width && height;
  3495. },
  3496. success: function() {
  3497. // calculate some cropping
  3498. var newWidth = ( width - options.margin * 2 ) / self.original.width,
  3499. newHeight = ( height - options.margin * 2 ) / self.original.height,
  3500. cropMap = {
  3501. 'true' : Math.max( newWidth, newHeight ),
  3502. 'width' : newWidth,
  3503. 'height': newHeight,
  3504. 'false' : Math.min( newWidth, newHeight )
  3505. },
  3506. ratio = cropMap[ options.crop.toString() ],
  3507. canvasKey = '';
  3508. // allow max_scale_ratio
  3509. if ( options.max ) {
  3510. ratio = Math.min( options.max, ratio );
  3511. }
  3512. // allow min_scale_ratio
  3513. if ( options.min ) {
  3514. ratio = Math.max( options.min, ratio );
  3515. }
  3516. $.each( ['width','height'], function( i, m ) {
  3517. $( self.image )[ m ]( self[ m ] = self.image[ m ] = Math.round( self.original[ m ] * ratio ) );
  3518. });
  3519. $( self.container ).width( width ).height( height );
  3520. if ( options.canvas && _canvas ) {
  3521. _canvas.elem.width = self.width;
  3522. _canvas.elem.height = self.height;
  3523. canvasKey = self.image.src + ':' + self.width + 'x' + self.height;
  3524. self.image.src = _canvas.cache[ canvasKey ] || (function( key ) {
  3525. _canvas.context.drawImage(self.image, 0, 0, self.original.width*ratio, self.original.height*ratio);
  3526. try {
  3527. data = _canvas.elem.toDataURL();
  3528. _canvas.length += data.length;
  3529. _canvas.cache[ key ] = data;
  3530. return data;
  3531. } catch( e ) {
  3532. return self.image.src;
  3533. }
  3534. }( canvasKey ) );
  3535. }
  3536. // calculate image_position
  3537. var pos = {},
  3538. mix = {},
  3539. getPosition = function(value, measure, margin) {
  3540. var result = 0;
  3541. if (/\%/.test(value)) {
  3542. var flt = parseInt( value, 10 ) / 100,
  3543. m = self.image[ measure ] || $( self.image )[ measure ]();
  3544. result = Math.ceil( m * -1 * flt + margin * flt );
  3545. } else {
  3546. result = Utils.parseValue( value );
  3547. }
  3548. return result;
  3549. },
  3550. positionMap = {
  3551. 'top': { top: 0 },
  3552. 'left': { left: 0 },
  3553. 'right': { left: '100%' },
  3554. 'bottom': { top: '100%' }
  3555. };
  3556. $.each( options.position.toLowerCase().split(' '), function( i, value ) {
  3557. if ( value === 'center' ) {
  3558. value = '50%';
  3559. }
  3560. pos[i ? 'top' : 'left'] = value;
  3561. });
  3562. $.each( pos, function( i, value ) {
  3563. if ( positionMap.hasOwnProperty( value ) ) {
  3564. $.extend( mix, positionMap[ value ] );
  3565. }
  3566. });
  3567. pos = pos.top ? $.extend( pos, mix ) : mix;
  3568. pos = $.extend({
  3569. top: '50%',
  3570. left: '50%'
  3571. }, pos);
  3572. // apply position
  3573. $( self.image ).css({
  3574. position : 'relative',
  3575. top : getPosition(pos.top, 'height', height),
  3576. left : getPosition(pos.left, 'width', width)
  3577. });
  3578. // show the image
  3579. self.show();
  3580. // flag ready and call the callback
  3581. self.ready = true;
  3582. options.complete.call( self, self );
  3583. },
  3584. error: function() {
  3585. Galleria.raise('Could not scale image: '+self.image.src);
  3586. },
  3587. timeout: 1000
  3588. });
  3589. return this;
  3590. }
  3591. };
  3592. // our own easings
  3593. $.extend( $.easing, {
  3594. galleria: function (_, t, b, c, d) {
  3595. if ((t/=d/2) < 1) {
  3596. return c/2*t*t*t + b;
  3597. }
  3598. return c/2*((t-=2)*t*t + 2) + b;
  3599. },
  3600. galleriaIn: function (_, t, b, c, d) {
  3601. return c*(t/=d)*t + b;
  3602. },
  3603. galleriaOut: function (_, t, b, c, d) {
  3604. return -c *(t/=d)*(t-2) + b;
  3605. }
  3606. });
  3607. // the plugin initializer
  3608. $.fn.galleria = function( options ) {
  3609. return this.each(function() {
  3610. var gallery = new Galleria();
  3611. gallery.init( this, options );
  3612. });
  3613. };
  3614. // Expose
  3615. window.Galleria = Galleria;
  3616. // phew
  3617. }( jQuery ) );