atreides 2.0.3 → 2.0.4

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