uranium-rails 0.0.1

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.
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);