uranium-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 067e845de1913a5a4ebc2c9f3ad423c2bd221e01
4
+ data.tar.gz: 075a728454dbcae48aa24ce29a5032b39e46747d
5
+ SHA512:
6
+ metadata.gz: c646903c94862e46b5eeb3b2a3c82f12aef78a7e011a937be1df381f860c5ea1ede80b12caf69c6379358292698dbdf46ba3e62ec0450a500a4b82a4f3995b03
7
+ data.tar.gz: 4473961c7eb049c01c67a24b8796d16cf5461889722e20e9d9d321ce3c52b388c22be05369b47f8293ebf292714b67cf1f9959a1f26cbcd34206a26b35d21c6b
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ = Uranium implementation for Ruby on Rails
2
+
3
+ This adds {Uranium}[http://uranium.io/] support to your Rails application.
4
+
5
+ == Installation
6
+
7
+ === As a gem
8
+ gem install uranium-rails
9
+
10
+
11
+ == Configuration
12
+
13
+ rails g uranium-rails:install
14
+
15
+
16
+ == Note on Patches/Pull Requests
17
+
18
+ * Fork the project.
19
+ * Make your feature addition or bug fix.
20
+ * Add tests for it. This is important so I don't break it in a
21
+ future version unintentionally.
22
+ * Commit, do not mess with rakefile, version, or history.
23
+ (if you want to have your own version, that is fine but bump version in a
24
+ commit by itself I can ignore when I pull)
25
+ * Send me a pull request. Bonus points for topic branches.
26
+
27
+
28
+
29
+ ==Author
30
+
31
+ {Sapna Jadon}
32
+
33
+
34
+ == Copyright
35
+
36
+ Copyright (c) 2013
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Setup urnium js assets on your application
3
+
4
+ Example:
5
+ rails g uranium:install
6
+
7
+ This will replace the default application assets:
8
+ /app/assets/javascripts/application.js
@@ -0,0 +1,38 @@
1
+ require 'rails'
2
+
3
+ # Supply generator for Rails 3.0.x or if asset pipeline is not enabled
4
+ if ::Rails.version < "3.1" || !::Rails.application.config.assets.enabled || !File.exist?('app/assets/javascripts/application.js') || !File.exist?('app/assets/javascripts/application.js.coffee')
5
+ module UraniumRails
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+
9
+ desc "This generator installs UraniumRails"
10
+ source_root File.expand_path('../../../../../vendor/assets/javascripts', __FILE__)
11
+
12
+ def copy_uranium
13
+ say_status("copying", "uranium js", :green)
14
+ copy_file "uranium-pretty.js", "public/javascripts/uranium-pretty.js"
15
+ copy_file "uranium.js", "public/javascripts/uranium.js"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ else
21
+ module UraniumRails
22
+ module Generators
23
+ class InstallGenerator < ::Rails::Generators::Base
24
+ desc "This generator add UraniumRails to application.js or application.js.coffee"
25
+ source_root File.expand_path('../../../../../vendor/assets/javascripts', __FILE__)
26
+ def add_assets
27
+ uranium_rails_defaults = ::Rails.env.production? || ::Rails.env.test? ? %w(uranium) : %w(uranium-pretty)
28
+ insert_into_file "app/assets/javascripts/application#{detect_js_format[0]}", "#{detect_js_format[1]} require #{uranium_rails_defaults}\n", :after => "jquery_ujs\n"
29
+ end
30
+
31
+ def detect_js_format
32
+ return ['.js.coffee', '#='] if File.exist?('app/assets/javascripts/application.js.coffee')
33
+ return ['.js', '//='] if File.exist?('app/assets/javascripts/application.js')
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1 @@
1
+ require 'uranium-rails/rails'
@@ -0,0 +1,25 @@
1
+ module ActionDispatch
2
+ module Assertions
3
+ module SelectorAssertions
4
+
5
+ PATTERN_HTML = %Q{"((\\\\\"|[^\"])*)"}
6
+ PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
7
+
8
+ private
9
+
10
+ # Unescapes a JS string.
11
+ def unescape_js(js_string)
12
+ # js encodes double quotes and line breaks.
13
+ unescaped= js_string.gsub('\"', '"')
14
+ unescaped.gsub!('\\\'', "'")
15
+ unescaped.gsub!(/\\\//, '/')
16
+ unescaped.gsub!('\n', "\n")
17
+ unescaped.gsub!('\076', '>')
18
+ unescaped.gsub!('\074', '<')
19
+ # js encodes non-ascii characters.
20
+ unescaped.gsub!(PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
21
+ unescaped
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ require 'rails'
2
+ require 'uranium-rails/assert_select' if ::Rails.env.test?
3
+ require 'uranium-rails/rails/engine' if ::Rails.version >= '3.1'
4
+ require 'uranium-rails/rails/railtie'
5
+ module UraniumRails
6
+ module Rails
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module SlimScroll
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ # Used to ensure that Rails 3.0.x, as well as Rails >= 3.1 with asset pipeline disabled
2
+ # get the minified version of the scripts included into the layout in production.
3
+ module UraniumRails
4
+ module Rails
5
+ class Railtie < ::Rails::Railtie
6
+ config.before_configuration do
7
+ if config.action_view.javascript_expansions
8
+ uranium_rails_defaults = ::Rails.env.production? || ::Rails.env.test? ? %w(uranium) : %w(uranium-pretty)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,1381 @@
1
+ // jQuery.Uranium.js
2
+ // Build out Uranium interactions in jQuery
3
+
4
+ (function ( $ ) {
5
+
6
+ // Keep a unique value for ID initialization
7
+ var uniqueUraniumId = function() {
8
+ var count = 0;
9
+ return function() { return ++count; }
10
+ }();
11
+
12
+ // Find elements for the interactions
13
+ // optional customFn(set, component) for custom creation of object
14
+ function findElements( fragment, type, customFn ) {
15
+ var sets = {};
16
+ var setCss = "[data-ur-set='" + type + "']";
17
+ var compAttr = "data-ur-" + type + "-component";
18
+
19
+ $(fragment).find("[" +compAttr +"]").each(function() {
20
+ if ($(this).data("urCompInit"))
21
+ return;
22
+ var set = $(this).attr("data-ur-id") ? $(this) : $(this).closest(setCss);
23
+ if (set[0] && !set.data("urInit")) {
24
+ $(this).data("urCompInit", true);
25
+ var setId = set.attr("data-ur-id");
26
+ if (!setId) {
27
+ setId = uniqueUraniumId();
28
+ set.attr("data-ur-id", setId);
29
+ }
30
+ sets[setId] = sets[setId] || {};
31
+
32
+ if (set.is(setCss))
33
+ sets[setId].set = set[0];
34
+
35
+ if (customFn)
36
+ customFn(sets[setId], this);
37
+ else {
38
+ var compName = $(this).attr(compAttr);
39
+ sets[setId][compName] = sets[setId][compName] || [];
40
+ sets[setId][compName].push(this);
41
+ }
42
+ }
43
+ });
44
+ return sets;
45
+ }
46
+
47
+ var interactions = {};
48
+
49
+ var touchscreen = "ontouchstart" in window;
50
+ var downEvent = touchscreen ? "touchstart" : "mousedown";
51
+ var moveEvent = touchscreen ? "touchmove" : "mousemove";
52
+ var upEvent = touchscreen ? "touchend" : "mouseup";
53
+
54
+ // Toggler
55
+ interactions.toggler = function( fragment ) {
56
+ var groups = findElements(fragment, "toggler");
57
+
58
+ $.each(groups, function(id, group) {
59
+ if (!group["button"])
60
+ $.error("no button found for toggler with id=" + id);
61
+ if (!group["content"])
62
+ $.error("no content found for toggler with id=" + id);
63
+
64
+ var togglerState = $(group["button"]).attr("data-ur-state") || "disabled";
65
+ $(group["button"]).add(group["content"]).attr("data-ur-state", togglerState);
66
+
67
+ $(group["button"]).click(function(event) {
68
+ event.stopPropagation();
69
+ var newState = $(group["button"]).attr("data-ur-state") == "enabled" ? "disabled" : "enabled";
70
+ $(group["button"]).add(group["content"]).attr("data-ur-state", newState);
71
+ });
72
+
73
+ $(group["set"]).data("urInit", true);
74
+ });
75
+ }
76
+
77
+ // Tabs
78
+ interactions.tabs = function( fragment ) {
79
+ var groups = findElements(fragment, "tabs", function(set, comp) {
80
+ var tabId = $(comp).attr("data-ur-tab-id");
81
+ set.tabs = set.tabs || {};
82
+ set.tabs[tabId] = set.tabs[tabId] || {};
83
+ var compName = $(comp).attr("data-ur-tabs-component");
84
+ set.tabs[tabId][compName] = set.tabs[tabId][compName] || [];
85
+ set.tabs[tabId][compName].push(comp);
86
+ });
87
+
88
+ $.each(groups, function(id, group) {
89
+ group["closeable"] = $(group["set"]).attr("data-ur-closeable") == "true";
90
+
91
+ // Set the state of the tabs
92
+ $.each(group["tabs"], function() {
93
+ var tabState = $(this["button"]).attr("data-ur-state") || "disabled";
94
+ $(this["button"]).add(this["content"]).attr("data-ur-state", tabState);
95
+ });
96
+
97
+ // Set up the button call backs
98
+ $.each(group["tabs"], function(_, tab) {
99
+ $(tab["button"]).click(function() {
100
+ // Is the tab open already?
101
+ var open = $(this).attr("data-ur-state") == "enabled";
102
+ $.each(group["tabs"], function() {
103
+ $(this["button"]).add(this["content"]).attr("data-ur-state", "disabled");
104
+ });
105
+ // If closeable (active tab can be toggled) then make sure it happens.
106
+ if (!open || !groups["closeable"]) {
107
+ $(tab["button"]).add(tab["content"]).attr("data-ur-state", "enabled");
108
+ }
109
+ });
110
+ });
111
+
112
+ $(group["set"]).data("urInit", true);
113
+ });
114
+ }
115
+
116
+ // Input Clear
117
+ interactions.inputClear = function( fragment ) {
118
+ var groups = findElements(fragment, "input-clear");
119
+ $.each(groups, function(id, group) {
120
+ // Create the X div and hide it (even though this should be in CSS)
121
+ var ex = $("<div class='data-ur-input-clear-ex'></div>").hide();
122
+ // Inject it
123
+ $(group['set']).append(ex);
124
+
125
+ // Touch Events
126
+ ex
127
+ .bind(touchscreen ? "touchstart" : "click", function() {
128
+ // remove text in the box
129
+ input[0].value='';
130
+ input[0].focus();
131
+ })
132
+ .bind('touchend', function() {
133
+ // make sure the keyboard doesn't disappear
134
+ input[0].blur();
135
+ });
136
+
137
+ var input = $(group["set"]).find("input");
138
+ input
139
+ .bind('focus', function() {
140
+ if (input[0].value != '') {
141
+ ex.show();
142
+ }
143
+ })
144
+ .bind('keydown', function() {
145
+ ex.show();
146
+ })
147
+ .bind('blur', function() {
148
+ // Delay the hide so that the button can be clicked
149
+ setTimeout(function() { ex.hide();}, 150);
150
+ });
151
+
152
+ $(group["set"]).data("urInit", true);
153
+ });
154
+ }
155
+
156
+ // Geocode
157
+ interactions.geoCode = function ( fragment ) {
158
+ var groups = findElements(fragment, "reverse-geocode", function(set, comp) {
159
+ set["elements"] = set["elements"] || {};
160
+ set["elements"][$(comp).attr("data-ur-reverse-geocode-component")] = comp;
161
+ });
162
+
163
+ $.each(groups, function(id, group) {
164
+ var set = this['set'];
165
+
166
+ var callback = $(set).attr("data-ur-callback");
167
+ var errorCallback = $(set).attr("data-ur-error-callback");
168
+ var geocoder;
169
+ var geocodeObj;
170
+ var currentObj;
171
+
172
+ function selectHelper(elm, value) {
173
+ for (var i=0,j=elm.length; i<j; i++) {
174
+ if (elm[i].value === value.long_name || elm[i].value.toUpperCase() === value.short_name) {
175
+ elm.selectedIndex = i;
176
+ }
177
+ }
178
+ }
179
+
180
+ function fieldHelper(elm, geoInfo, htmlElmType) {
181
+ var index1 = 0;
182
+ var index2 = null; // used for street address
183
+ var need = null;
184
+ var temp = null;
185
+ var self = $(elm).attr("data-ur-reverse-geocode-component");
186
+ switch(self) {
187
+ case 'rg-city':
188
+ need = 'locality';
189
+ break;
190
+ case 'rg-street':
191
+ need = 'street_number';
192
+ break;
193
+ case 'rg-zip':
194
+ need = 'postal_code';
195
+ break;
196
+ case 'rg-state':
197
+ need = 'administrative_area_level_1';
198
+ break;
199
+ case 'rg-country':
200
+ need = 'country';
201
+ break;
202
+ }
203
+ temp=geoInfo[0];
204
+ var myTemp = null;
205
+ for (var i = temp.address_components.length, j=0; j<i; j++) {
206
+ for (var k = temp.address_components[j].types.length, m=0; m<k; m++) {
207
+ myTemp = temp.address_components[j].types[m];
208
+ if (need == myTemp) {
209
+ switch(myTemp) {
210
+ case 'street_number':
211
+ index1 = j;
212
+ index2 = j+1;
213
+ break;
214
+ case 'locality':
215
+ index1 = j;
216
+ break;
217
+ case 'postal_code':
218
+ index1 = j;
219
+ break;
220
+ case 'administrative_area_level_1':
221
+ index1 = j;
222
+ break;
223
+ case 'country':
224
+ index1 = j;
225
+ }
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ if (htmlElmType === "input") {
231
+ if (index2 === null) {
232
+ elm.value = geoInfo[0].address_components[index1].long_name;
233
+ } else {
234
+ elm.value = geoInfo[0].address_components[index1].long_name + " " + geoInfo[0].address_components[index2].long_name;
235
+ }
236
+ } else if (htmlElmType === "select") {
237
+ selectHelper(elm, geoInfo[0].address_components[index1]);
238
+ }
239
+ }
240
+
241
+ function populateFields (geoInfo) {
242
+ var elements = currentObj.elements;
243
+ for (elm in elements) {
244
+ if (elements[elm].localName === "input") {
245
+ fieldHelper(elements[elm], geoInfo, "input")
246
+ }
247
+ else if (elements[elm].localName === "select") {
248
+ fieldHelper(elements[elm], geoInfo, "select")
249
+ }
250
+ }
251
+ }
252
+
253
+ this.setupCallbacks = function () {
254
+ currentObj = this;
255
+ // Set up call back for button to trigger geocoding
256
+ var btn = $(this["elements"]).filter("[data-ur-reverse-geocode-component='rg-button']")
257
+ if (btn.length > 0) {
258
+ $(btn).bind(
259
+ 'click',
260
+ function(obj){
261
+ return function() {
262
+ obj.geocodeInit();
263
+ }
264
+ }(this)
265
+ );
266
+ } else {
267
+ console.warn("no button for triggering reverse geocoding present");
268
+ this.geocodeInit();
269
+ }
270
+ };
271
+
272
+ this.geoSuccess = function( position ){
273
+ var coords = {
274
+ lat: position.coords.latitude,
275
+ lng: position.coords.longitude
276
+ }
277
+
278
+ this.codeLatLng(coords.lat, coords.lng);
279
+ },
280
+
281
+ this.geoError = function( error ){
282
+ console.error("Ur geolocation error -- Error Getting Your Coordinates!");
283
+ switch(error.code)
284
+ {
285
+ case error.TIMEOUT:
286
+ console.error ('Ur geolocation error -- Timeout');
287
+ break;
288
+ case error.POSITION_UNAVAILABLE:
289
+ console.error ('Ur geolocation error -- Position unavailable');
290
+ break;
291
+ case error.PERMISSION_DENIED:
292
+ console.error ('Ur geolocation error -- Permission denied');
293
+ break;
294
+ case error.UNKNOWN_ERROR:
295
+ console.error ('Ur geolocation error -- Unknown error');
296
+ break;
297
+ }
298
+ if(errorCallback !== undefined) {
299
+ eval(errorCallback);
300
+ }
301
+ }
302
+
303
+ this.geoDenied = function() {
304
+ console.error("Ur geolocation error -- User Denied Geolocation");
305
+ }
306
+
307
+ this.codeLatLng = function( lat, lng ) {
308
+ var latlng = new google.maps.LatLng(lat, lng);
309
+ var self = this;
310
+
311
+ geocoder.geocode({'latLng': latlng}, function(results, status) {
312
+ if (status == google.maps.GeocoderStatus.OK) {
313
+ if (results[1]) {
314
+ geocodeObj = results;
315
+ populateFields(geocodeObj);
316
+
317
+ if(self.callback !== undefined) {
318
+ eval(self.callback);
319
+ }
320
+
321
+ return results;
322
+ } else {
323
+ console.error("Geocoder failed due to: " + status);
324
+ }
325
+ }
326
+ });
327
+ }
328
+
329
+ this.geocodeInit = function () {
330
+ if(navigator.geolocation){ //feature detect
331
+ geocoder = new google.maps.Geocoder();
332
+ navigator.geolocation.getCurrentPosition(
333
+ function(obj){
334
+ return function(position){
335
+ obj.geoSuccess(position);
336
+ };
337
+ }(this),
338
+ function(obj) {
339
+ return function(errors){
340
+ obj.geoError(errors);
341
+ };
342
+ }(this),
343
+ this.geoDenied
344
+ );
345
+ }
346
+ }
347
+
348
+ UrGeocode = function( obj ) {
349
+ return function() {
350
+ obj.setupCallbacks();
351
+ };
352
+ }(this);
353
+ var s = document.createElement('script');
354
+ s.type = "text/javascript";
355
+ s.src = "https://maps.googleapis.com/maps/api/js?sensor=true&callback=UrGeocode";
356
+ $('head').append(s);
357
+
358
+ $(group["set"]).data("urInit", true);
359
+ });
360
+ }
361
+
362
+ // Zoom
363
+ interactions.zoom = function ( fragment ) {
364
+ var groups = findElements(fragment, "zoom");
365
+
366
+ // Private shared variables
367
+
368
+ var loadedImgs = []; // sometimes the load event doesn't fire when the image src has been previously loaded
369
+
370
+ var no3d = /Android [12]|Opera/.test(navigator.userAgent);
371
+
372
+ var noTranslate3d = no3d;
373
+ var noScale3d = no3d;
374
+
375
+ var translatePrefix = noTranslate3d ? "translate(" : "translate3d(";
376
+ var translateSuffix = noTranslate3d ? ")" : ", 0)";
377
+
378
+ var scalePrefix = noScale3d ? " scale(" : " scale3d(";
379
+ var scaleSuffix = noScale3d ? ")" : ", 1)";
380
+
381
+
382
+ // Private shared methods
383
+
384
+ // note that this accepts a reversed range
385
+ function bound(num, range) {
386
+ return Math.max(Math.min(range[0], num), range[1]);
387
+ }
388
+
389
+ function stifle(e) {
390
+ e.preventDefault();
391
+ e.stopPropagation();
392
+ }
393
+
394
+ $.each(groups, function(id, group) {
395
+ Uranium.zoom[id] = new Zoom(this);
396
+ $(group["set"]).data("urInit", true);
397
+ });
398
+
399
+ function Zoom(set) {
400
+ var self = this;
401
+ this.container = set["set"];
402
+ this.img = set["img"][0];
403
+ this.prescale = false;
404
+ this.width = this.height = 0;
405
+ this.bigWidth = this.bigHeight = 0;
406
+ this.canvasWidth = this.canvasHeight = 0;
407
+ this.ratio = 1;
408
+ this.state = "disabled";
409
+
410
+ // Optionally:
411
+ this.button = set["button"];
412
+ this.idler = set["loading"];
413
+
414
+ var $img = $(this.img);
415
+ var $idler = $(this.idler);
416
+ var $btn = $(this.button);
417
+
418
+ var boundX, boundY;
419
+ var relX, relY;
420
+ var offsetX = 0, offsetY = 0;
421
+ var touchX = 0, touchY = 0;
422
+ var mouseDown = false; // only used on non-touch browsers
423
+ var mouseDrag = true;
424
+
425
+ loadedImgs.push($img.attr("src"));
426
+
427
+ function initialize() {
428
+ self.canvasWidth = self.canvasWidth || self.container.offsetWidth;
429
+ self.canvasHeight = self.canvasHeight || self.container.offsetHeight;
430
+ self.width = self.width || parseInt($img.attr("width")) || parseInt($img.css("width")) || self.img.width;
431
+ self.height = self.height || parseInt($img.attr("height")) || parseInt($img.css("height")) || self.img.height;
432
+
433
+ self.bigWidth = parseInt($img.attr("data-ur-width")) || self.img.naturalWidth;
434
+ self.bigHeight = parseInt($img.attr("data-ur-height")) || self.img.naturalHeight;
435
+ if (($img.attr("data-ur-width") && $img.attr("data-ur-height")) || $img.attr("src") == $img.attr("data-ur-src"))
436
+ self.prescale = true;
437
+
438
+ self.ratio = self.bigWidth/self.width;
439
+
440
+ boundX = (self.canvasWidth - self.bigWidth)/2; // horizontal translation to view middle of image
441
+ boundY = (self.canvasHeight - self.bigHeight)/2; // vertical translation to view middle of image
442
+ }
443
+
444
+ function panStart(event) {
445
+ if (event.target != self.img)
446
+ return;
447
+ mouseDrag = false;
448
+ touchX = event.pageX;
449
+ touchY = event.pageY;
450
+ mouseDown = true;
451
+ var touches = event.originalEvent.touches;
452
+ if (touches) {
453
+ touchX = touches[0].pageX;
454
+ touchY = touches[0].pageY;
455
+ }
456
+
457
+ var style = self.img.style;
458
+ if (window.WebKitCSSMatrix) {
459
+ var matrix = new WebKitCSSMatrix(style.webkitTransform);
460
+ offsetX = matrix.m41;
461
+ offsetY = matrix.m42;
462
+ }
463
+ else {
464
+ var transform = style.MozTransform || style.msTransform || style.transform || "translate(0, 0)";
465
+ transform = transform.replace(/.*?\(|\)/, "").split(",");
466
+
467
+ offsetX = parseInt(transform[0]);
468
+ offsetY = parseInt(transform[1]);
469
+ }
470
+
471
+ stifle(event);
472
+ }
473
+
474
+ function panMove(event) {
475
+ if (!mouseDown || event.target != self.img) // NOTE: mouseDown should always be true on touch-enabled devices
476
+ return;
477
+
478
+ stifle(event);
479
+ var x = event.pageX;
480
+ var y = event.pageY;
481
+ var touches = event.originalEvent.touches;
482
+ if (touches) {
483
+ x = touches[0].pageX;
484
+ y = touches[0].pageY;
485
+ }
486
+ var dx = x - touchX;
487
+ var dy = y - touchY;
488
+ if (Math.abs(dx) > 5 || Math.abs(dy) > 5)
489
+ mouseDrag = true;
490
+ var newOffsetX = bound(offsetX + dx, [-boundX, boundX]);
491
+ var newOffsetY = bound(offsetY + dy, [-boundY, boundY]);
492
+ transform(newOffsetX, newOffsetY, self.ratio);
493
+ }
494
+
495
+ function panEnd(event) {
496
+ if (!mouseDrag)
497
+ self.zoomOut();
498
+ stifle(event);
499
+ mouseDown = false;
500
+ mouseDrag = true;
501
+ }
502
+
503
+ function transitionEnd() {
504
+ if (self.state == "enabled-in") {
505
+ $img.css({ webkitTransitionDelay: "", MozTransitionDelay: "", OTransitionDelay: "", transitionDelay: "" });
506
+
507
+ self.img.src = $img.attr("data-ur-src");
508
+ if (loadedImgs.indexOf(self.img.getAttribute("data-ur-src")) == -1) {
509
+ setTimeout(function() {
510
+ if (loadedImgs.indexOf(self.img.getAttribute("data-ur-src")) == -1)
511
+ $idler.attr("data-ur-state", "enabled");
512
+ }, 16);
513
+ }
514
+ self.state = "enabled";
515
+ self.container.setAttribute("data-ur-state", self.state);
516
+
517
+ $(self.container)
518
+ .on(downEvent, panStart)
519
+ .on(moveEvent, panMove)
520
+ .on(upEvent, panEnd);
521
+ }
522
+ else if (self.state == "enabled-out") {
523
+ self.state = "disabled";
524
+ self.container.setAttribute("data-ur-state", self.state);
525
+
526
+ $(self.container)
527
+ .unbind(downEvent, panStart)
528
+ .unbind(moveEvent, panMove)
529
+ .unbind(upEvent, panEnd);
530
+ }
531
+ }
532
+
533
+ function zoomHelper(x, y) {
534
+ $btn.attr("data-ur-state", "enabled");
535
+ self.state = "enabled-in";
536
+ self.container.setAttribute("data-ur-state", self.state);
537
+
538
+ x = x ? x : 0;
539
+ y = y ? y : 0;
540
+ transform(x, y, self.ratio);
541
+ }
542
+
543
+ function transform(x, y, scale) {
544
+ var t = "";
545
+ if (x != undefined)
546
+ t = translatePrefix + x + "px, " + y + "px" + translateSuffix;
547
+ if (scale != undefined) {
548
+ if (noScale3d)
549
+ t += " scale(" + scale + ")";
550
+ else
551
+ t += " scale3d(" + scale + ", " + scale + ", 1)";
552
+ }
553
+ return $img.css({ webkitTransform: t, MozTransform: t, msTransform: t, transform: t });
554
+ }
555
+
556
+ // attempts to zoom in centering in on the area that was touched
557
+ this.zoomIn = function(event) {
558
+ if (self.state != "disabled")
559
+ return;
560
+
561
+ if (!self.width) {
562
+ initialize();
563
+ self.img.style.width = self.width + "px";
564
+ self.img.style.height = self.height + "px";
565
+ }
566
+
567
+ var x = event.pageX, y = event.pageY;
568
+ if (event.touches) {
569
+ x = event.touches[0].pageX;
570
+ y = event.touches[0].pageY;
571
+ }
572
+
573
+ // find touch location relative to image
574
+ relX = event.offsetX;
575
+ relY = event.offsetY;
576
+ if (relX == undefined || relY == undefined) {
577
+ var offset = self.img.getBoundingClientRect();
578
+ relX = x - offset.left;
579
+ relY = y - offset.top;
580
+ }
581
+
582
+ if (!self.prescale) {
583
+ self.state = "enabled-in";
584
+ self.img.src = $img.attr("data-ur-src");
585
+ setTimeout(function() {
586
+ if (!self.prescale)
587
+ $idler.attr("data-ur-state", "enabled");
588
+ }, 0);
589
+ }
590
+ else {
591
+ var translateX = bound(self.bigWidth/2 - self.ratio * relX, [-boundX, boundX]);
592
+ var translateY = bound(self.bigHeight/2 - self.ratio * relY, [-boundY, boundY]);
593
+ zoomHelper(translateX, translateY);
594
+ }
595
+ };
596
+
597
+ this.zoomOut = function() {
598
+ if (self.state != "enabled")
599
+ return;
600
+ $btn.attr("data-ur-state", "disabled");
601
+ self.state = "enabled-out";
602
+ self.container.setAttribute("data-ur-state", self.state);
603
+ transform(0, 0, 1);
604
+ };
605
+
606
+ if (self.container.getAttribute("data-ur-touch") != "disabled")
607
+ $(self.container).click(self.zoomIn);
608
+
609
+ $img.load(function() {
610
+ if ($img.attr("src") == $img.attr("data-ur-src"))
611
+ loadedImgs.push($img.attr("src"));
612
+ $idler.attr("data-ur-state", "disabled");
613
+ if (!self.prescale && self.state == "enabled-in") {
614
+ self.prescale = true;
615
+ initialize();
616
+ var translateX = bound(self.bigWidth/2 - self.ratio * relX, [-boundX, boundX]);
617
+ var translateY = bound(self.bigHeight/2 - self.ratio * relY, [-boundY, boundY]);
618
+
619
+ var delay = "0.3s";
620
+ $img.css({ webkitTransitionDelay: delay, MozTransitionDelay: delay, OTransitionDelay: delay, transitionDelay: delay });
621
+
622
+ zoomHelper(translateX, translateY);
623
+ }
624
+ });
625
+
626
+ // zooms in to the center of the image
627
+ this.zoom = function() {
628
+ if (self.state == "disabled") {
629
+ if (!self.width) {
630
+ initialize();
631
+ self.img.style.width = self.width + "px";
632
+ self.img.style.height = self.height + "px";
633
+ }
634
+
635
+ if (self.prescale)
636
+ zoomHelper(0, 0);
637
+ else {
638
+ self.state = "enabled-in";
639
+ self.img.src = $img.attr("data-ur-src");
640
+ setTimeout(function() {
641
+ // if prescale ?
642
+ if (loadedImgs.indexOf(self.img.getAttribute("data-ur-src")) == -1)
643
+ $idler.attr("data-ur-state", "enabled");
644
+ }, 0);
645
+ }
646
+ }
647
+ else
648
+ self.zoomOut();
649
+ };
650
+
651
+ // zoom in/out button, zooms in to the center of the image
652
+ $(self.button).on(touchscreen ? "touchstart" : "click", self.zoom);
653
+
654
+ $.each(["webkitTransitionEnd", "transitionend", "oTransitionEnd"], function(index, eventName) {
655
+ $img.on(eventName, transitionEnd);
656
+ });
657
+
658
+ this.reset = function() {
659
+ self.prescale = false;
660
+ self.width = self.height = 0;
661
+ $img.css({width: "", height: ""});
662
+ transform();
663
+ self.state = "enabled-out";
664
+ transitionEnd();
665
+ $idler.attr("data-ur-state", "disabled");
666
+ $btn.attr("data-ur-state", "disabled");
667
+ };
668
+ }
669
+ }
670
+
671
+ // Carousel
672
+ interactions.carousel = function ( fragment ) {
673
+ var groups = findElements(fragment, "carousel");
674
+
675
+ // for each carousel
676
+ $.each(groups, function(id, group) {
677
+ $(group["buttons"]).each(function() {
678
+ var type = $(this).attr("data-ur-carousel-button-type");
679
+ if(!type) {
680
+ $.error("malformed carousel button type for carousel with id: " + id + ".");
681
+ }
682
+ $(this).attr("data-ur-state", type == "prev" ? "disabled" : "enabled");
683
+ });
684
+ Uranium.carousel[id] = new Carousel(group);
685
+ $(group["set"]).data("urInit", true);
686
+ $(group["set"]).attr("data-ur-state", "enabled"); // should be data-ur-init or fire event
687
+ });
688
+
689
+ // private methods
690
+
691
+ function zeroFloor(num) {
692
+ return num >= 0 ? Math.floor(num) : Math.ceil(num);
693
+ }
694
+
695
+ function stifle(e) {
696
+ e.preventDefault();
697
+ e.stopPropagation();
698
+ }
699
+
700
+ function Carousel(set) {
701
+ var self = this;
702
+ self.container = set["set"];
703
+ self.scroller = set["scroll_container"];
704
+ if (!self.scroller)
705
+ $.error("carousel missing item components");
706
+ self.items = set["item"] || [];
707
+
708
+ // Optionally:
709
+ self.button = {
710
+ prev: $(set["button"]).filter("[data-ur-carousel-button-type='prev']"),
711
+ next: $(set["button"]).filter("[data-ur-carousel-button-type='next']")
712
+ };
713
+ self.counter = set["count"];
714
+ self.dots = set["dots"];
715
+
716
+ self.flag = {
717
+ click: false, // used for determining if item is clicked on touchscreens
718
+ snapping: false, // true if carousel is currently snapping, flag for users' convenience
719
+ lock: null, // used for determining horizontal/vertical dragging motion on touchscreens
720
+ touched: false // true when user is currently touching/dragging
721
+ };
722
+
723
+ self.options = {
724
+ autoscroll: false,
725
+ autoscrollDelay: 5000,
726
+ autoscrollForward: true,
727
+ center: false, // position active item in the middle of the carousel
728
+ cloneLength: 0, // number of clones at back of carousel (or front and back for centered carousels)
729
+ fill: 0, // exactly how many items forced to fit in the viewport, 0 means disabled
730
+ infinite: true, // loops the last item back to first and vice versa
731
+ speed: 1.1, // determines how "fast" carousel snaps, should probably be deprecated
732
+ translate3d: true, // determines if translate3d() or translate() is used
733
+ touch: true, // determines if carousel can be dragged e.g. when user only wants buttons to be used
734
+ verticalScroll: true // determines if dragging carousel vertically scrolls the page on touchscreens, this is almost always true
735
+ };
736
+
737
+ self.count = self.items.length; // number of items (excluding clones)
738
+ self.itemIndex = 0; // index of active item (including clones)
739
+ self.translate = 0; // current numerical css translate value
740
+
741
+ var $container = $(self.container);
742
+ var $items = $(self.items); // all carousel items (including clones)
743
+ var coords = null;
744
+ var prevCoords; // stores previous coords, used for determining swipe direction
745
+ var startCoords = {x: 0, y: 0};
746
+ var shift = 0; // in range [0, 1) or [-0.5, 0.5) for centered carousels showing translate percentage past top/left side of active item
747
+ var dest = $items[0]; // snap destination element
748
+ var destinationOffset; // translate value of destination
749
+ var lastIndex = self.count - 1; // index of last item
750
+ var allItemsWidth; // sum of all items' widths (excluding clones)
751
+ var autoscrollId; // used for autoscrolling timeout
752
+ var momentumId; // used for snapping timeout
753
+
754
+ var viewport = $container.outerWidth();
755
+
756
+ var startingOffset = null;
757
+
758
+ var translatePrefix = "translate3d(", translateSuffix = ", 0px)";
759
+
760
+ function initialize() {
761
+ self.options.translate3d = self.options.translate3d && test3d();
762
+ if (!self.options.translate3d) {
763
+ translatePrefix = "translate(";
764
+ translateSuffix = ")";
765
+ }
766
+
767
+ $items.each(function(i, obj) {
768
+ if ($(obj).attr("data-ur-state") == "active") {
769
+ self.itemIndex = i;
770
+ return false;
771
+ }
772
+ });
773
+
774
+ insertClones();
775
+ updateIndex(self.options.center ? self.itemIndex + self.options.cloneLength : self.itemIndex);
776
+ updateDots();
777
+ self.update();
778
+
779
+ $(self.scroller).on("dragstart", function() { return false; }); // for Firefox
780
+
781
+ if (self.options.touch) {
782
+ $(self.scroller)
783
+ .on(downEvent, startSwipe)
784
+ .on(moveEvent, continueSwipe)
785
+ .on(upEvent, finishSwipe)
786
+ .click(function(e) {if (!self.flag.click) stifle(e);});
787
+ }
788
+
789
+ self.button.prev.click(function() {
790
+ moveTo(1);
791
+ });
792
+ self.button.next.click(function() {
793
+ moveTo(-1);
794
+ });
795
+
796
+ if ("onorientationchange" in window)
797
+ $(window).on("orientationchange", self.update);
798
+ else
799
+ $(window).on("resize", function() {
800
+ if (viewport != $container.outerWidth()) {
801
+ self.update();
802
+ setTimeout(self.update, 100); // sometimes styles haven't updated yet
803
+ }
804
+ });
805
+
806
+ $items.find("img").addBack("img").load(self.update); // after any (late-loaded) images are loaded
807
+
808
+ self.autoscrollStart();
809
+
810
+ }
811
+
812
+ function readAttributes() {
813
+ var oldAndroid = /Android [12]/.test(navigator.userAgent);
814
+ if (oldAndroid) {
815
+ if (($container.attr("data-ur-android3d") || $container.attr("data-ur-translate3d")) != "enabled") {
816
+ self.options.translate3d = false;
817
+ var speed = parseFloat($container.attr("data-ur-speed"));
818
+ self.options.speed = speed > 1 ? speed : 1.3;
819
+ }
820
+ }
821
+ else
822
+ self.options.translate3d = $container.attr("data-ur-translate3d") != "disabled";
823
+ $container.attr("data-ur-translate3d", self.options.translate3d ? "enabled" : "disabled");
824
+
825
+ $container.attr("data-ur-speed", self.options.speed);
826
+
827
+ var fill = parseInt($container.attr("data-ur-fill"));
828
+ if (fill > 0)
829
+ self.options.fill = fill;
830
+ $container.attr("data-ur-fill", self.options.fill);
831
+
832
+ var cloneLength = $container.attr("data-ur-clones");
833
+ if (cloneLength)
834
+ self.options.cloneLength = parseInt(cloneLength);
835
+ $container.attr("data-ur-clones", self.options.cloneLength);
836
+
837
+ var autoscrollDelay = parseInt($container.attr("data-ur-autoscroll-delay"));
838
+ if (autoscrollDelay >= 0)
839
+ self.options.autoscrollDelay = autoscrollDelay;
840
+ $container.attr("data-ur-autoscroll-delay", self.options.autoscrollDelay);
841
+
842
+ self.options.autoscrollForward = $container.attr("data-ur-autoscroll-dir") != "prev";
843
+ $container.attr("data-ur-autoscroll-dir", self.options.autoscrollForward ? "next" : "prev");
844
+
845
+ // read boolean attributes
846
+ $.each(["autoscroll", "center", "infinite", "touch", "verticalScroll"], function(_, name) {
847
+ var dashName = "data-ur-" + name.replace(/[A-Z]/g, function(i) { return "-" + i.toLowerCase()});
848
+ var value = $container.attr(dashName);
849
+ if (value == "enabled")
850
+ self.options[name] = true;
851
+ else if (value == "disabled")
852
+ self.options[name] = false;
853
+
854
+ $container.attr(dashName, self.options[name] ? "enabled" : "disabled");
855
+ });
856
+ }
857
+
858
+ function insertClones() {
859
+ if (!self.options.infinite) {
860
+ self.options.cloneLength = 0;
861
+ $container.attr("data-ur-clones", 0);
862
+ return;
863
+ }
864
+
865
+ if (self.options.cloneLength == 0) {
866
+ if (self.options.fill)
867
+ self.options.cloneLength = self.options.center ? self.options.fill - 1 : self.options.fill;
868
+ else if (self.options.center) {
869
+ // insert enough clones at front and back to never see a blank space
870
+ var cloneLengths = [0, 0];
871
+ var space = viewport/2 + width($items[lastIndex])/2;
872
+ for (var i = lastIndex; space > 0; i = (i - 1 + self.count) % self.count) {
873
+ space -= width($items[i]);
874
+ cloneLengths[0]++;
875
+ }
876
+
877
+ space = viewport/2 + width($items[0])/2;
878
+ for (var i = 0; space > 0; i = (i + 1) % self.count) {
879
+ space -= width($items[i]);
880
+ cloneLengths[1]++;
881
+ }
882
+
883
+ self.options.cloneLength = Math.max(cloneLengths[0], cloneLengths[1]);
884
+ }
885
+ else {
886
+ // insert enough clones at the back to never see a blank space
887
+ var space = viewport;
888
+ var i = 0;
889
+ while (space > 0) {
890
+ space -= width($items[i]);
891
+ self.options.cloneLength++;
892
+ i = (i + 1) % $items.length;
893
+ }
894
+ }
895
+ }
896
+
897
+ $container.attr("data-ur-clones", self.options.cloneLength);
898
+
899
+ var frag = document.createDocumentFragment();
900
+ for (var i = 0; i < self.options.cloneLength; i++) {
901
+ var srcIndex = i % self.count;
902
+ var clone = $items.eq(srcIndex).clone(true).attr("data-ur-clone", srcIndex).attr("data-ur-state", "inactive");
903
+ frag.appendChild(clone[0]);
904
+ }
905
+ $items.parent().append(frag);
906
+
907
+ if (self.options.center) {
908
+ frag = document.createDocumentFragment()
909
+ var offset = self.count - (self.options.cloneLength % self.count);
910
+ for (var i = offset; i < offset + self.options.cloneLength; i++) {
911
+ var srcIndex = i % self.count;
912
+ var clone = $items.eq(srcIndex).clone(true).attr("data-ur-clone", srcIndex).attr("data-ur-state", "inactive");
913
+ frag.appendChild(clone[0]);
914
+ }
915
+ $items.parent().prepend(frag);
916
+ }
917
+
918
+ $items = $(self.scroller).find("[data-ur-carousel-component='item']");
919
+ lastIndex = $items.length - 1;
920
+ }
921
+
922
+ function updateDots() {
923
+ if (self.dots) {
924
+ var existing = $(self.dots).find("[data-ur-carousel-component='dot']");
925
+ if (existing.length != self.count) {
926
+ existing.remove();
927
+ var dot = $("<div data-ur-carousel-component='dot'>");
928
+ var storage = document.createDocumentFragment();
929
+ for (var i = 0; i < self.count; i++) {
930
+ var newdot = dot.clone().attr("data-ur-state", i == self.itemIndex ? "active" : "inactive");
931
+ storage.appendChild(newdot[0]);
932
+ }
933
+ $(self.dots).append(storage);
934
+ }
935
+ }
936
+ }
937
+
938
+ self.update = function() {
939
+ var oldCount = $items.length;
940
+ $items = $(self.scroller).find("[data-ur-carousel-component='item']");
941
+ if (oldCount != $items.length) {
942
+ self.items = $items.filter(":not([data-ur-clone])").toArray();
943
+ self.count = self.items.length;
944
+ lastIndex = $items.length - 1;
945
+
946
+ $items.each(function(i, obj) {
947
+ if ($(obj).attr("data-ur-state") == "active") {
948
+ self.itemIndex = i;
949
+ return false;
950
+ }
951
+ });
952
+
953
+ // in case the previous active item was removed
954
+ if (self.itemIndex >= $items.length - self.options.cloneLength) {
955
+ self.itemIndex = lastIndex - self.options.cloneLength;
956
+ $items.eq(self.itemIndex).attr("data-ur-state", "active");
957
+ }
958
+
959
+ // in the rare case the destination element was (re)moved
960
+ if (!$.contains(self.scroller, dest))
961
+ dest = $items[self.itemIndex];
962
+
963
+ updateDots();
964
+ updateIndex(self.options.center ? self.itemIndex + self.options.cloneLength : self.itemIndex);
965
+ }
966
+
967
+ viewport = $container.outerWidth();
968
+ // Adjust the container to be the necessary width.
969
+ var totalWidth = 0;
970
+
971
+ // pixel-perfect division, slightly inefficient?
972
+ var divisions = [];
973
+ if (self.options.fill > 0) {
974
+ var remainder = viewport;
975
+ for (var i = self.options.fill; i > 0; i--) {
976
+ var length = Math.round(remainder/i);
977
+ divisions.push(length);
978
+ remainder -= length;
979
+ }
980
+ }
981
+
982
+ allItemsWidth = 0;
983
+ for (var i = 0; i < $items.length; i++) {
984
+ if (self.options.fill > 0) {
985
+ var length = divisions[i % self.options.fill];
986
+ var item = $items.eq(i);
987
+ item.outerWidth(length); // could add true param if margins allowed
988
+ totalWidth += length;
989
+ }
990
+ else
991
+ totalWidth += width($items[i]);
992
+
993
+ if (i <= lastIndex - self.options.cloneLength && i >= (self.options.center ? self.options.cloneLength : 0))
994
+ allItemsWidth += width($items[i]);
995
+ }
996
+
997
+ $(self.scroller).width(totalWidth);
998
+
999
+ var currentItem = $items[self.itemIndex];
1000
+ var newTranslate = -(offsetFront(currentItem) + shift * width(currentItem));
1001
+ destinationOffset = -offsetFront(dest);
1002
+ if (self.options.center) {
1003
+ newTranslate += centerOffset(currentItem);
1004
+ destinationOffset += centerOffset(dest);
1005
+ }
1006
+ translateX(newTranslate);
1007
+ };
1008
+
1009
+ self.autoscrollStart = function() {
1010
+ if (!self.options.autoscroll)
1011
+ return;
1012
+
1013
+ autoscrollId = setTimeout(function() {
1014
+ if (viewport != 0) {
1015
+ if (!self.options.infinite && self.itemIndex == lastIndex && self.options.autoscrollForward)
1016
+ self.jumpToIndex(0);
1017
+ else if (!self.options.infinite && self.itemIndex == 0 && !self.options.autoscrollForward)
1018
+ self.jumpToIndex(lastIndex);
1019
+ else
1020
+ moveTo(self.options.autoscrollForward ? -1 : 1);
1021
+ }
1022
+ else
1023
+ self.autoscrollStart();
1024
+ }, self.options.autoscrollDelay);
1025
+ };
1026
+
1027
+ self.autoscrollStop = function() {
1028
+ clearTimeout(autoscrollId);
1029
+ };
1030
+
1031
+ function updateButtons() {
1032
+ if (self.options.infinite)
1033
+ $([self.button.prev, self.button.next]).attr("data-ur-state", "enabled");
1034
+ else {
1035
+ $(self.button.prev).attr("data-ur-state", self.itemIndex == 0 ? "disabled" : "enabled");
1036
+ $(self.button.next).attr("data-ur-state", self.itemIndex == self.count - Math.max(self.options.fill, 1) ? "disabled" : "enabled");
1037
+ }
1038
+ }
1039
+
1040
+ // execute side effects of new index
1041
+ function updateIndex(newIndex) {
1042
+ if (newIndex === undefined)
1043
+ return;
1044
+
1045
+ self.itemIndex = newIndex;
1046
+ if (self.itemIndex < 0)
1047
+ self.itemIndex = 0;
1048
+ else if (self.itemIndex > lastIndex)
1049
+ self.itemIndex = lastIndex;
1050
+
1051
+ var realIndex = self.itemIndex;
1052
+ if (self.options.infinite && self.options.center)
1053
+ realIndex = self.itemIndex - self.options.cloneLength;
1054
+ realIndex = realIndex % self.count;
1055
+ $(self.counter).html(realIndex + 1 + " of " + self.count);
1056
+
1057
+ $items.attr("data-ur-state", "inactive");
1058
+ $items.eq(self.itemIndex).attr("data-ur-state", "active");
1059
+
1060
+ $(self.dots).find("[data-ur-carousel-component='dot']").attr("data-ur-state", "inactive").eq(realIndex).attr("data-ur-state", "active");
1061
+
1062
+ updateButtons();
1063
+ }
1064
+
1065
+ function startSwipe(e) {
1066
+ if (!self.options.verticalScroll)
1067
+ stifle(e);
1068
+ self.autoscrollStop();
1069
+
1070
+ self.flag.touched = true;
1071
+ self.flag.lock = null;
1072
+ self.flag.click = true;
1073
+
1074
+ coords = getEventCoords(e);
1075
+
1076
+ startCoords = prevCoords = coords;
1077
+ startingOffset = getTranslateX();
1078
+ }
1079
+
1080
+ function continueSwipe(e) {
1081
+ if (!self.flag.touched) // for non-touch environments since mousemove fires without mousedown
1082
+ return;
1083
+
1084
+ prevCoords = coords;
1085
+ coords = getEventCoords(e);
1086
+
1087
+ if (Math.abs(startCoords.y - coords.y) + Math.abs(startCoords.x - coords.x) > 0)
1088
+ self.flag.click = false;
1089
+
1090
+ if (touchscreen && self.options.verticalScroll) {
1091
+ var slope = Math.abs((startCoords.y - coords.y)/(startCoords.x - coords.x));
1092
+ if (self.flag.lock) {
1093
+ if (self.flag.lock == "y")
1094
+ return;
1095
+ }
1096
+ else if (slope > 1.2) {
1097
+ self.flag.lock = "y";
1098
+ return;
1099
+ }
1100
+ else if (slope <= 1.2)
1101
+ self.flag.lock = "x";
1102
+ else
1103
+ return;
1104
+ }
1105
+
1106
+ stifle(e);
1107
+
1108
+ if (coords !== null) {
1109
+ var dist = startingOffset + swipeDist(startCoords, coords); // new translate() value, usually negative
1110
+
1111
+ var threshold = -dist;
1112
+ if (self.options.center)
1113
+ threshold += viewport/2;
1114
+ $items.each(function(i, item) {
1115
+ var boundStart = offsetFront(item);
1116
+ var boundEnd = boundStart + width(item);
1117
+ if (boundEnd > threshold) {
1118
+ self.itemIndex = i;
1119
+ shift = (threshold - boundStart)/width(item);
1120
+ if (self.options.center)
1121
+ shift -= 0.5;
1122
+ return false;
1123
+ }
1124
+ });
1125
+
1126
+ if (self.options.infinite) {
1127
+ if (self.options.center) {
1128
+ if (self.itemIndex < self.options.cloneLength) { // at the start of carousel so loop to end
1129
+ startingOffset -= allItemsWidth;
1130
+ dist -= allItemsWidth;
1131
+ self.itemIndex += self.count;
1132
+ }
1133
+ else if (self.itemIndex >= self.count + self.options.cloneLength) { // at the end of carousel so loop to start
1134
+ startingOffset += allItemsWidth;
1135
+ dist += allItemsWidth;
1136
+ self.itemIndex -= self.count;
1137
+ }
1138
+ }
1139
+ else {
1140
+ if (shift < 0) { // at the start of carousel so loop to end
1141
+ startingOffset -= allItemsWidth;
1142
+ dist -= allItemsWidth;
1143
+ self.itemIndex += self.count;
1144
+ var item = $items[self.itemIndex];
1145
+ shift = (-dist - offsetFront(item))/width(item);
1146
+ }
1147
+ else if (self.itemIndex >= self.count) { // at the end of carousel so loop to start
1148
+ var offset = offsetFront($items[self.count]) - offsetFront($items[0]); // length of all original items
1149
+ startingOffset += offset;
1150
+ dist += offset;
1151
+ self.itemIndex -= self.count;
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ translateX(dist);
1157
+ }
1158
+
1159
+ }
1160
+
1161
+ function finishSwipe(e) {
1162
+ if (!self.flag.touched) // for non-touch environments since mouseup fires without mousedown
1163
+ return;
1164
+
1165
+ if (!self.flag.click || self.flag.lock)
1166
+ stifle(e);
1167
+ else if (e.target.tagName == "AREA")
1168
+ location.href = e.target.href;
1169
+
1170
+ self.flag.touched = false;
1171
+
1172
+ var dir = coords.x - prevCoords.x;
1173
+ if (self.options.center) {
1174
+ if (dir < 0 && shift > 0)
1175
+ moveTo(-1)
1176
+ else if (dir > 0 && shift < 0)
1177
+ moveTo(1);
1178
+ else
1179
+ moveTo(0);
1180
+ }
1181
+ else
1182
+ moveTo(dir < 0 ? -1: 0);
1183
+ }
1184
+
1185
+ function moveTo(direction) {
1186
+ self.autoscrollStop();
1187
+
1188
+ // in case prev/next buttons are being spammed
1189
+ clearTimeout(momentumId);
1190
+
1191
+ var newIndex = self.itemIndex - direction;
1192
+ if (!self.options.infinite) {
1193
+ if (self.options.fill > 0)
1194
+ newIndex = bound(newIndex, [0, self.count - self.options.fill]);
1195
+ else
1196
+ newIndex = bound(newIndex, [0, lastIndex]);
1197
+ }
1198
+
1199
+ // when snapping to clone, prepare to snap back to original element
1200
+ if (self.options.infinite) {
1201
+ var transform = getTranslateX();
1202
+ if (self.options.center) {
1203
+ if (newIndex < self.options.cloneLength) { // clone at start of carousel so loop to back
1204
+ translateX(transform - allItemsWidth);
1205
+ newIndex += self.count;
1206
+ self.itemIndex = newIndex + direction;
1207
+ }
1208
+ else if (newIndex >= self.count + self.options.cloneLength) { // clone at end of carousel so loop to front
1209
+ translateX(transform + allItemsWidth);
1210
+ newIndex -= self.count;
1211
+ self.itemIndex = newIndex + direction;
1212
+ }
1213
+
1214
+ }
1215
+ else {
1216
+ if (newIndex < 0) { // at start of carousel so loop to back
1217
+ translateX(transform - allItemsWidth);
1218
+ newIndex += self.count;
1219
+ self.itemIndex = newIndex + direction;
1220
+ }
1221
+ else if (newIndex > self.count) { // clone at end of carousel so loop to start
1222
+ translateX(transform + allItemsWidth);
1223
+ newIndex -= self.count;
1224
+ self.itemIndex = newIndex + direction;
1225
+ }
1226
+
1227
+ }
1228
+ }
1229
+
1230
+ dest = $items[newIndex];
1231
+ $container.trigger("slidestart.ur.carousel", {index: newIndex});
1232
+
1233
+ // timeout needed for mobile safari
1234
+ setTimeout(function() {
1235
+ snapTo();
1236
+ updateIndex(newIndex);
1237
+ }, 0);
1238
+ }
1239
+
1240
+ function snapTo() {
1241
+ destinationOffset = -offsetFront(dest);
1242
+ if (self.options.center)
1243
+ destinationOffset += centerOffset(dest);
1244
+
1245
+ function momentum() {
1246
+ // in case user touched in the middle of snapping
1247
+ if (self.flag.touched)
1248
+ return;
1249
+
1250
+ var translate = getTranslateX();
1251
+ var distance = destinationOffset - translate;
1252
+ var delta = distance - zeroFloor(distance / self.options.speed);
1253
+
1254
+ // Hacky -- this is for the desktop browser only -- to fix rounding errors
1255
+ // Ideally, this is removed at compile time
1256
+ if(Math.abs(delta) < 0.01)
1257
+ delta = 0;
1258
+
1259
+ var newTransform = translate + delta;
1260
+ translateX(newTransform);
1261
+
1262
+ self.flag.snapping = delta != 0;
1263
+ if (self.flag.snapping)
1264
+ momentumId = setTimeout(momentum, 16);
1265
+ else
1266
+ endSnap();
1267
+ }
1268
+
1269
+ momentum();
1270
+ }
1271
+
1272
+ function endSnap() {
1273
+ // infinite, non-centered carousels when swiping from last item back to first can't switch early in moveTo() since no clones at front
1274
+ if (self.options.infinite && !self.options.center && self.itemIndex >= self.count) {
1275
+ translateX(getTranslateX() + allItemsWidth);
1276
+ self.itemIndex -= self.count;
1277
+ }
1278
+ shift = 0;
1279
+ self.autoscrollStart();
1280
+ $container.trigger("slideend.ur.carousel", {index: self.itemIndex});
1281
+ }
1282
+
1283
+ self.jumpToIndex = function(index) {
1284
+ moveTo(self.itemIndex - index);
1285
+ };
1286
+
1287
+ // could be end.y - start.y if vertical option implemented
1288
+ function swipeDist(start, end) {
1289
+ return end.x - start.x;
1290
+ }
1291
+
1292
+ function translateX(x) {
1293
+ self.translate = x;
1294
+ var css = translatePrefix + x + "px, 0px" + translateSuffix;
1295
+ $(self.scroller).css({webkitTransform: css, MozTransform: css, msTransform: css, transform: css});
1296
+ }
1297
+
1298
+ function getTranslateX() {
1299
+ return self.translate;
1300
+ }
1301
+
1302
+ function getEventCoords(event) {
1303
+ var touches = event.originalEvent.touches;
1304
+ event = (touches && touches[0]) || event;
1305
+ return {x: event.clientX, y: event.clientY};
1306
+ }
1307
+
1308
+ // could possibly be $(item).outerWidth(true) if margins are allowed
1309
+ function width(item) {
1310
+ return item.offsetWidth;
1311
+ }
1312
+
1313
+ // .offsetLeft/Top, could includ margin as "part" of the element with - parseInt($(item).css("marginLeft"))
1314
+ function offsetFront(item) {
1315
+ return item.offsetLeft;
1316
+ }
1317
+
1318
+ // offset needed to center element, round since subpixel translation makes images blurry
1319
+ function centerOffset(item) {
1320
+ return Math.floor((viewport - width(item))/2);
1321
+ }
1322
+
1323
+ function bound(num, range) {
1324
+ return Math.min(Math.max(range[0], num), range[1]);
1325
+ }
1326
+
1327
+ function test3d() {
1328
+ var css3d = "translate3d(0, 0, 0)";
1329
+ var test = $("<a>").css({webkitTransform: css3d, MozTransform: css3d, msTransform: css3d, transform: css3d});
1330
+ var wt = test.css("webkitTransform");
1331
+ var mt = test.css("MozTransform");
1332
+ var it = test.css("msTransform");
1333
+ var t = test.css("transform");
1334
+ return (wt + mt + it + t).indexOf("(") != -1;
1335
+ }
1336
+
1337
+ readAttributes();
1338
+
1339
+ // delay initialization until we can figure out number of clones
1340
+ var zeroWidth = false;
1341
+ if (self.options.infinite && !self.options.fill && self.options.cloneLength == 0) {
1342
+ $items.width(function(i, width) {
1343
+ if (width == 0)
1344
+ zeroWidth = true;
1345
+ });
1346
+ }
1347
+ if (zeroWidth) {
1348
+ // wait until (late-loaded) images are loaded or other content inserted
1349
+ var imgs = $items.find("img").addBack("img");
1350
+ var numImgs = imgs.length;
1351
+ if (numImgs > 0)
1352
+ imgs.load(function() {
1353
+ if (--numImgs == 0)
1354
+ initialize();
1355
+ });
1356
+ else
1357
+ $(window).load(initialize);
1358
+ }
1359
+ else
1360
+ initialize();
1361
+
1362
+ }
1363
+ }
1364
+
1365
+ window.Uranium = {};
1366
+ $.each(interactions, function(name) {
1367
+ Uranium[name] = {};
1368
+ });
1369
+
1370
+ $.fn.Uranium = function() {
1371
+ var jqObj = this;
1372
+ $.each(interactions, function() {
1373
+ this(jqObj);
1374
+ });
1375
+ return this;
1376
+ };
1377
+
1378
+ $(document).ready(function() {
1379
+ $("body").Uranium();
1380
+ });
1381
+ })(jQuery);