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 +7 -0
- data/README.md +36 -0
- data/lib/generators/uranium-rails/install/USAGE +8 -0
- data/lib/generators/uranium-rails/install/install_generator.rb +38 -0
- data/lib/uranium-rails.rb +1 -0
- data/lib/uranium-rails/assert_select.rb +25 -0
- data/lib/uranium-rails/rails.rb +8 -0
- data/lib/uranium-rails/rails/engine.rb +6 -0
- data/lib/uranium-rails/rails/railtie.rb +13 -0
- data/vendor/assets/javascripts/uranium-pretty.js +1381 -0
- data/vendor/assets/javascripts/uranium.js +39 -0
- metadata +54 -0
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,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,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);
|