PageRenderTime 62ms CodeModel.GetById 20ms RepoModel.GetById 0ms 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

Large files files are truncated, but you can click here to view the full file

  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.clearTim

Large files files are truncated, but you can click here to view the full file