jquery-countdown-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +41 -0
  6. data/Rakefile +1 -0
  7. data/jquery-countdown-rails.gemspec +24 -0
  8. data/lib/jquery-countdown-rails.rb +15 -0
  9. data/lib/jquery-countdown-rails/engine.rb +7 -0
  10. data/lib/jquery-countdown-rails/railtie.rb +7 -0
  11. data/lib/jquery-countdown-rails/sprockets.rb +8 -0
  12. data/lib/jquery-countdown-rails/version.rb +4 -0
  13. data/vendor/assets/javascripts/jquery.countdown-ar.js +13 -0
  14. data/vendor/assets/javascripts/jquery.countdown-bg.js +13 -0
  15. data/vendor/assets/javascripts/jquery.countdown-bn.js +0 -0
  16. data/vendor/assets/javascripts/jquery.countdown-bs.js +16 -0
  17. data/vendor/assets/javascripts/jquery.countdown-ca.js +13 -0
  18. data/vendor/assets/javascripts/jquery.countdown-cs.js +16 -0
  19. data/vendor/assets/javascripts/jquery.countdown-cy.js +1 -0
  20. data/vendor/assets/javascripts/jquery.countdown-da.js +13 -0
  21. data/vendor/assets/javascripts/jquery.countdown-de.js +13 -0
  22. data/vendor/assets/javascripts/jquery.countdown-el.js +13 -0
  23. data/vendor/assets/javascripts/jquery.countdown-es.js +13 -0
  24. data/vendor/assets/javascripts/jquery.countdown-et.js +13 -0
  25. data/vendor/assets/javascripts/jquery.countdown-fa.js +14 -0
  26. data/vendor/assets/javascripts/jquery.countdown-fi.js +13 -0
  27. data/vendor/assets/javascripts/jquery.countdown-fr.js +15 -0
  28. data/vendor/assets/javascripts/jquery.countdown-gl.js +13 -0
  29. data/vendor/assets/javascripts/jquery.countdown-gu.js +13 -0
  30. data/vendor/assets/javascripts/jquery.countdown-he.js +13 -0
  31. data/vendor/assets/javascripts/jquery.countdown-hr.js +16 -0
  32. data/vendor/assets/javascripts/jquery.countdown-hu.js +13 -0
  33. data/vendor/assets/javascripts/jquery.countdown-hy.js +13 -0
  34. data/vendor/assets/javascripts/jquery.countdown-id.js +13 -0
  35. data/vendor/assets/javascripts/jquery.countdown-it.js +13 -0
  36. data/vendor/assets/javascripts/jquery.countdown-ja.js +13 -0
  37. data/vendor/assets/javascripts/jquery.countdown-kn.js +13 -0
  38. data/vendor/assets/javascripts/jquery.countdown-ko.js +14 -0
  39. data/vendor/assets/javascripts/jquery.countdown-lt.js +13 -0
  40. data/vendor/assets/javascripts/jquery.countdown-lv.js +13 -0
  41. data/vendor/assets/javascripts/jquery.countdown-ml.js +14 -0
  42. data/vendor/assets/javascripts/jquery.countdown-ms.js +13 -0
  43. data/vendor/assets/javascripts/jquery.countdown-my.js +13 -0
  44. data/vendor/assets/javascripts/jquery.countdown-nb.js +13 -0
  45. data/vendor/assets/javascripts/jquery.countdown-nl.js +13 -0
  46. data/vendor/assets/javascripts/jquery.countdown-pl.js +18 -0
  47. data/vendor/assets/javascripts/jquery.countdown-pt-BR.js +14 -0
  48. data/vendor/assets/javascripts/jquery.countdown-ro.js +13 -0
  49. data/vendor/assets/javascripts/jquery.countdown-ru.js +19 -0
  50. data/vendor/assets/javascripts/jquery.countdown-sk.js +16 -0
  51. data/vendor/assets/javascripts/jquery.countdown-sl.js +13 -0
  52. data/vendor/assets/javascripts/jquery.countdown-sq.js +13 -0
  53. data/vendor/assets/javascripts/jquery.countdown-sr-SR.js +16 -0
  54. data/vendor/assets/javascripts/jquery.countdown-sr.js +16 -0
  55. data/vendor/assets/javascripts/jquery.countdown-sv.js +13 -0
  56. data/vendor/assets/javascripts/jquery.countdown-th.js +13 -0
  57. data/vendor/assets/javascripts/jquery.countdown-tr.js +13 -0
  58. data/vendor/assets/javascripts/jquery.countdown-uk.js +16 -0
  59. data/vendor/assets/javascripts/jquery.countdown-uz.js +13 -0
  60. data/vendor/assets/javascripts/jquery.countdown-vi.js +13 -0
  61. data/vendor/assets/javascripts/jquery.countdown-zh-CN.js +13 -0
  62. data/vendor/assets/javascripts/jquery.countdown-zh-TW.js +13 -0
  63. data/vendor/assets/javascripts/jquery.countdown.js +838 -0
  64. data/vendor/assets/stylesheets/jquery.countdown.css +51 -0
  65. metadata +138 -0
@@ -0,0 +1,13 @@
1
+ /* http://keith-wood.name/countdown.html
2
+ Simplified Chinese initialisation for the jQuery countdown extension
3
+ Written by Cloudream (cloudream@gmail.com). */
4
+ (function($) {
5
+ $.countdown.regional['zh-CN'] = {
6
+ labels: ['年', '月', '周', '天', '时', '分', '秒'],
7
+ labels1: ['年', '月', '周', '天', '时', '分', '秒'],
8
+ compactLabels: ['年', '月', '周', '天'], compactLabels1: ['年', '月', '周', '天'],
9
+ whichLabels: null,
10
+ digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
11
+ timeSeparator: ':', isRTL: false};
12
+ $.countdown.setDefaults($.countdown.regional['zh-CN']);
13
+ })(jQuery);
@@ -0,0 +1,13 @@
1
+ /* http://keith-wood.name/countdown.html
2
+ Traditional Chinese initialisation for the jQuery countdown extension
3
+ Written by Cloudream (cloudream@gmail.com). */
4
+ (function($) {
5
+ $.countdown.regional['zh-TW'] = {
6
+ labels: ['年', '月', '周', '天', '時', '分', '秒'],
7
+ labels1: ['年', '月', '周', '天', '時', '分', '秒'],
8
+ compactLabels: ['年', '月', '周', '天'], compactLabels1: ['年', '月', '周', '天'],
9
+ whichLabels: null,
10
+ digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
11
+ timeSeparator: ':', isRTL: false};
12
+ $.countdown.setDefaults($.countdown.regional['zh-TW']);
13
+ })(jQuery);
@@ -0,0 +1,838 @@
1
+ /* http://keith-wood.name/countdown.html
2
+ Countdown for jQuery v1.6.3.
3
+ Written by Keith Wood (kbwood{at}iinet.com.au) January 2008.
4
+ Available under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license.
5
+ Please attribute the author if you use it. */
6
+
7
+ /* Display a countdown timer.
8
+ Attach it with options like:
9
+ $('div selector').countdown(
10
+ {until: new Date(2009, 1 - 1, 1, 0, 0, 0), onExpiry: happyNewYear}); */
11
+
12
+ (function($) { // Hide scope, no $ conflict
13
+
14
+ /* Countdown manager. */
15
+ function Countdown() {
16
+ this.regional = []; // Available regional settings, indexed by language code
17
+ this.regional[''] = { // Default regional settings
18
+ // The display texts for the counters
19
+ labels: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds'],
20
+ // The display texts for the counters if only one
21
+ labels1: ['Year', 'Month', 'Week', 'Day', 'Hour', 'Minute', 'Second'],
22
+ compactLabels: ['y', 'm', 'w', 'd'], // The compact texts for the counters
23
+ whichLabels: null, // Function to determine which labels to use
24
+ digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], // The digits to display
25
+ timeSeparator: ':', // Separator for time periods
26
+ isRTL: false // True for right-to-left languages, false for left-to-right
27
+ };
28
+ this._defaults = {
29
+ until: null, // new Date(year, mth - 1, day, hr, min, sec) - date/time to count down to
30
+ // or numeric for seconds offset, or string for unit offset(s):
31
+ // 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds
32
+ since: null, // new Date(year, mth - 1, day, hr, min, sec) - date/time to count up from
33
+ // or numeric for seconds offset, or string for unit offset(s):
34
+ // 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds
35
+ timezone: null, // The timezone (hours or minutes from GMT) for the target times,
36
+ // or null for client local
37
+ serverSync: null, // A function to retrieve the current server time for synchronisation
38
+ format: 'dHMS', // Format for display - upper case for always, lower case only if non-zero,
39
+ // 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds
40
+ layout: '', // Build your own layout for the countdown
41
+ compact: false, // True to display in a compact format, false for an expanded one
42
+ significant: 0, // The number of periods with values to show, zero for all
43
+ description: '', // The description displayed for the countdown
44
+ expiryUrl: '', // A URL to load upon expiry, replacing the current page
45
+ expiryText: '', // Text to display upon expiry, replacing the countdown
46
+ alwaysExpire: false, // True to trigger onExpiry even if never counted down
47
+ onExpiry: null, // Callback when the countdown expires -
48
+ // receives no parameters and 'this' is the containing division
49
+ onTick: null, // Callback when the countdown is updated -
50
+ // receives int[7] being the breakdown by period (based on format)
51
+ // and 'this' is the containing division
52
+ tickInterval: 1 // Interval (seconds) between onTick callbacks
53
+ };
54
+ $.extend(this._defaults, this.regional['']);
55
+ this._serverSyncs = [];
56
+ var now = (typeof Date.now == 'function' ? Date.now :
57
+ function() { return new Date().getTime(); });
58
+ var perfAvail = (window.performance && typeof window.performance.now == 'function');
59
+ // Shared timer for all countdowns
60
+ function timerCallBack(timestamp) {
61
+ var drawStart = (timestamp < 1e12 ? // New HTML5 high resolution timer
62
+ (perfAvail ? (performance.now() + performance.timing.navigationStart) : now()) :
63
+ // Integer milliseconds since unix epoch
64
+ timestamp || now());
65
+ if (drawStart - animationStartTime >= 1000) {
66
+ plugin._updateTargets();
67
+ animationStartTime = drawStart;
68
+ }
69
+ requestAnimationFrame(timerCallBack);
70
+ }
71
+ var requestAnimationFrame = window.requestAnimationFrame ||
72
+ window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
73
+ window.oRequestAnimationFrame || window.msRequestAnimationFrame || null;
74
+ // This is when we expect a fall-back to setInterval as it's much more fluid
75
+ var animationStartTime = 0;
76
+ if (!requestAnimationFrame || $.noRequestAnimationFrame) {
77
+ $.noRequestAnimationFrame = null;
78
+ setInterval(function() { plugin._updateTargets(); }, 980); // Fall back to good old setInterval
79
+ }
80
+ else {
81
+ animationStartTime = window.animationStartTime ||
82
+ window.webkitAnimationStartTime || window.mozAnimationStartTime ||
83
+ window.oAnimationStartTime || window.msAnimationStartTime || now();
84
+ requestAnimationFrame(timerCallBack);
85
+ }
86
+ }
87
+
88
+ var Y = 0; // Years
89
+ var O = 1; // Months
90
+ var W = 2; // Weeks
91
+ var D = 3; // Days
92
+ var H = 4; // Hours
93
+ var M = 5; // Minutes
94
+ var S = 6; // Seconds
95
+
96
+ $.extend(Countdown.prototype, {
97
+ /* Class name added to elements to indicate already configured with countdown. */
98
+ markerClassName: 'hasCountdown',
99
+ /* Name of the data property for instance settings. */
100
+ propertyName: 'countdown',
101
+
102
+ /* Class name for the right-to-left marker. */
103
+ _rtlClass: 'countdown_rtl',
104
+ /* Class name for the countdown section marker. */
105
+ _sectionClass: 'countdown_section',
106
+ /* Class name for the period amount marker. */
107
+ _amountClass: 'countdown_amount',
108
+ /* Class name for the countdown row marker. */
109
+ _rowClass: 'countdown_row',
110
+ /* Class name for the holding countdown marker. */
111
+ _holdingClass: 'countdown_holding',
112
+ /* Class name for the showing countdown marker. */
113
+ _showClass: 'countdown_show',
114
+ /* Class name for the description marker. */
115
+ _descrClass: 'countdown_descr',
116
+
117
+ /* List of currently active countdown targets. */
118
+ _timerTargets: [],
119
+
120
+ /* Override the default settings for all instances of the countdown widget.
121
+ @param options (object) the new settings to use as defaults */
122
+ setDefaults: function(options) {
123
+ this._resetExtraLabels(this._defaults, options);
124
+ $.extend(this._defaults, options || {});
125
+ },
126
+
127
+ /* Convert a date/time to UTC.
128
+ @param tz (number) the hour or minute offset from GMT, e.g. +9, -360
129
+ @param year (Date) the date/time in that timezone or
130
+ (number) the year in that timezone
131
+ @param month (number, optional) the month (0 - 11) (omit if year is a Date)
132
+ @param day (number, optional) the day (omit if year is a Date)
133
+ @param hours (number, optional) the hour (omit if year is a Date)
134
+ @param mins (number, optional) the minute (omit if year is a Date)
135
+ @param secs (number, optional) the second (omit if year is a Date)
136
+ @param ms (number, optional) the millisecond (omit if year is a Date)
137
+ @return (Date) the equivalent UTC date/time */
138
+ UTCDate: function(tz, year, month, day, hours, mins, secs, ms) {
139
+ if (typeof year == 'object' && year.constructor == Date) {
140
+ ms = year.getMilliseconds();
141
+ secs = year.getSeconds();
142
+ mins = year.getMinutes();
143
+ hours = year.getHours();
144
+ day = year.getDate();
145
+ month = year.getMonth();
146
+ year = year.getFullYear();
147
+ }
148
+ var d = new Date();
149
+ d.setUTCFullYear(year);
150
+ d.setUTCDate(1);
151
+ d.setUTCMonth(month || 0);
152
+ d.setUTCDate(day || 1);
153
+ d.setUTCHours(hours || 0);
154
+ d.setUTCMinutes((mins || 0) - (Math.abs(tz) < 30 ? tz * 60 : tz));
155
+ d.setUTCSeconds(secs || 0);
156
+ d.setUTCMilliseconds(ms || 0);
157
+ return d;
158
+ },
159
+
160
+ /* Convert a set of periods into seconds.
161
+ Averaged for months and years.
162
+ @param periods (number[7]) the periods per year/month/week/day/hour/minute/second
163
+ @return (number) the corresponding number of seconds */
164
+ periodsToSeconds: function(periods) {
165
+ return periods[0] * 31557600 + periods[1] * 2629800 + periods[2] * 604800 +
166
+ periods[3] * 86400 + periods[4] * 3600 + periods[5] * 60 + periods[6];
167
+ },
168
+
169
+ /* Attach the countdown widget to a div.
170
+ @param target (element) the containing division
171
+ @param options (object) the initial settings for the countdown */
172
+ _attachPlugin: function(target, options) {
173
+ target = $(target);
174
+ if (target.hasClass(this.markerClassName)) {
175
+ return;
176
+ }
177
+ var inst = {options: $.extend({}, this._defaults), _periods: [0, 0, 0, 0, 0, 0, 0]};
178
+ target.addClass(this.markerClassName).data(this.propertyName, inst);
179
+ this._optionPlugin(target, options);
180
+ },
181
+
182
+ /* Add a target to the list of active ones.
183
+ @param target (element) the countdown target */
184
+ _addTarget: function(target) {
185
+ if (!this._hasTarget(target)) {
186
+ this._timerTargets.push(target);
187
+ }
188
+ },
189
+
190
+ /* See if a target is in the list of active ones.
191
+ @param target (element) the countdown target
192
+ @return (boolean) true if present, false if not */
193
+ _hasTarget: function(target) {
194
+ return ($.inArray(target, this._timerTargets) > -1);
195
+ },
196
+
197
+ /* Remove a target from the list of active ones.
198
+ @param target (element) the countdown target */
199
+ _removeTarget: function(target) {
200
+ this._timerTargets = $.map(this._timerTargets,
201
+ function(value) { return (value == target ? null : value); }); // delete entry
202
+ },
203
+
204
+ /* Update each active timer target. */
205
+ _updateTargets: function() {
206
+ for (var i = this._timerTargets.length - 1; i >= 0; i--) {
207
+ this._updateCountdown(this._timerTargets[i]);
208
+ }
209
+ },
210
+
211
+ /* Reconfigure the settings for a countdown div.
212
+ @param target (element) the control to affect
213
+ @param options (object) the new options for this instance or
214
+ (string) an individual property name
215
+ @param value (any) the individual property value (omit if options
216
+ is an object or to retrieve the value of a setting)
217
+ @return (any) if retrieving a value */
218
+ _optionPlugin: function(target, options, value) {
219
+ target = $(target);
220
+ var inst = target.data(this.propertyName);
221
+ if (!options || (typeof options == 'string' && value == null)) { // Get option
222
+ var name = options;
223
+ options = (inst || {}).options;
224
+ return (options && name ? options[name] : options);
225
+ }
226
+
227
+ if (!target.hasClass(this.markerClassName)) {
228
+ return;
229
+ }
230
+ options = options || {};
231
+ if (typeof options == 'string') {
232
+ var name = options;
233
+ options = {};
234
+ options[name] = value;
235
+ }
236
+ if (options.layout) {
237
+ options.layout = options.layout.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
238
+ }
239
+ this._resetExtraLabels(inst.options, options);
240
+ var timezoneChanged = (inst.options.timezone != options.timezone);
241
+ $.extend(inst.options, options);
242
+ this._adjustSettings(target, inst,
243
+ options.until != null || options.since != null || timezoneChanged);
244
+ var now = new Date();
245
+ if ((inst._since && inst._since < now) || (inst._until && inst._until > now)) {
246
+ this._addTarget(target[0]);
247
+ }
248
+ this._updateCountdown(target, inst);
249
+ },
250
+
251
+ /* Redisplay the countdown with an updated display.
252
+ @param target (jQuery) the containing division
253
+ @param inst (object) the current settings for this instance */
254
+ _updateCountdown: function(target, inst) {
255
+ var $target = $(target);
256
+ inst = inst || $target.data(this.propertyName);
257
+ if (!inst) {
258
+ return;
259
+ }
260
+ $target.html(this._generateHTML(inst)).toggleClass(this._rtlClass, inst.options.isRTL);
261
+ if ($.isFunction(inst.options.onTick)) {
262
+ var periods = inst._hold != 'lap' ? inst._periods :
263
+ this._calculatePeriods(inst, inst._show, inst.options.significant, new Date());
264
+ if (inst.options.tickInterval == 1 ||
265
+ this.periodsToSeconds(periods) % inst.options.tickInterval == 0) {
266
+ inst.options.onTick.apply(target, [periods]);
267
+ }
268
+ }
269
+ var expired = inst._hold != 'pause' &&
270
+ (inst._since ? inst._now.getTime() < inst._since.getTime() :
271
+ inst._now.getTime() >= inst._until.getTime());
272
+ if (expired && !inst._expiring) {
273
+ inst._expiring = true;
274
+ if (this._hasTarget(target) || inst.options.alwaysExpire) {
275
+ this._removeTarget(target);
276
+ if ($.isFunction(inst.options.onExpiry)) {
277
+ inst.options.onExpiry.apply(target, []);
278
+ }
279
+ if (inst.options.expiryText) {
280
+ var layout = inst.options.layout;
281
+ inst.options.layout = inst.options.expiryText;
282
+ this._updateCountdown(target, inst);
283
+ inst.options.layout = layout;
284
+ }
285
+ if (inst.options.expiryUrl) {
286
+ window.location = inst.options.expiryUrl;
287
+ }
288
+ }
289
+ inst._expiring = false;
290
+ }
291
+ else if (inst._hold == 'pause') {
292
+ this._removeTarget(target);
293
+ }
294
+ $target.data(this.propertyName, inst);
295
+ },
296
+
297
+ /* Reset any extra labelsn and compactLabelsn entries if changing labels.
298
+ @param base (object) the options to be updated
299
+ @param options (object) the new option values */
300
+ _resetExtraLabels: function(base, options) {
301
+ var changingLabels = false;
302
+ for (var n in options) {
303
+ if (n != 'whichLabels' && n.match(/[Ll]abels/)) {
304
+ changingLabels = true;
305
+ break;
306
+ }
307
+ }
308
+ if (changingLabels) {
309
+ for (var n in base) { // Remove custom numbered labels
310
+ if (n.match(/[Ll]abels[02-9]|compactLabels1/)) {
311
+ base[n] = null;
312
+ }
313
+ }
314
+ }
315
+ },
316
+
317
+ /* Calculate interal settings for an instance.
318
+ @param target (element) the containing division
319
+ @param inst (object) the current settings for this instance
320
+ @param recalc (boolean) true if until or since are set */
321
+ _adjustSettings: function(target, inst, recalc) {
322
+ var now;
323
+ var serverOffset = 0;
324
+ var serverEntry = null;
325
+ for (var i = 0; i < this._serverSyncs.length; i++) {
326
+ if (this._serverSyncs[i][0] == inst.options.serverSync) {
327
+ serverEntry = this._serverSyncs[i][1];
328
+ break;
329
+ }
330
+ }
331
+ if (serverEntry != null) {
332
+ serverOffset = (inst.options.serverSync ? serverEntry : 0);
333
+ now = new Date();
334
+ }
335
+ else {
336
+ var serverResult = ($.isFunction(inst.options.serverSync) ?
337
+ inst.options.serverSync.apply(target, []) : null);
338
+ now = new Date();
339
+ serverOffset = (serverResult ? now.getTime() - serverResult.getTime() : 0);
340
+ this._serverSyncs.push([inst.options.serverSync, serverOffset]);
341
+ }
342
+ var timezone = inst.options.timezone;
343
+ timezone = (timezone == null ? -now.getTimezoneOffset() : timezone);
344
+ if (recalc || (!recalc && inst._until == null && inst._since == null)) {
345
+ inst._since = inst.options.since;
346
+ if (inst._since != null) {
347
+ inst._since = this.UTCDate(timezone, this._determineTime(inst._since, null));
348
+ if (inst._since && serverOffset) {
349
+ inst._since.setMilliseconds(inst._since.getMilliseconds() + serverOffset);
350
+ }
351
+ }
352
+ inst._until = this.UTCDate(timezone, this._determineTime(inst.options.until, now));
353
+ if (serverOffset) {
354
+ inst._until.setMilliseconds(inst._until.getMilliseconds() + serverOffset);
355
+ }
356
+ }
357
+ inst._show = this._determineShow(inst);
358
+ },
359
+
360
+ /* Remove the countdown widget from a div.
361
+ @param target (element) the containing division */
362
+ _destroyPlugin: function(target) {
363
+ target = $(target);
364
+ if (!target.hasClass(this.markerClassName)) {
365
+ return;
366
+ }
367
+ this._removeTarget(target[0]);
368
+ target.removeClass(this.markerClassName).empty().removeData(this.propertyName);
369
+ },
370
+
371
+ /* Pause a countdown widget at the current time.
372
+ Stop it running but remember and display the current time.
373
+ @param target (element) the containing division */
374
+ _pausePlugin: function(target) {
375
+ this._hold(target, 'pause');
376
+ },
377
+
378
+ /* Pause a countdown widget at the current time.
379
+ Stop the display but keep the countdown running.
380
+ @param target (element) the containing division */
381
+ _lapPlugin: function(target) {
382
+ this._hold(target, 'lap');
383
+ },
384
+
385
+ /* Resume a paused countdown widget.
386
+ @param target (element) the containing division */
387
+ _resumePlugin: function(target) {
388
+ this._hold(target, null);
389
+ },
390
+
391
+ /* Toggles a countdown widget between 'pause' and 'resume'.
392
+ @param target (element) the containing division */
393
+ _togglePlugin: function(target) {
394
+ var inst = $.data(target, this.propertyName);
395
+ if (inst) {
396
+ if (inst._hold == 'pause') {
397
+ this._resumePlugin(target);
398
+ }
399
+ else {
400
+ this._pausePlugin(target);
401
+ }
402
+ }
403
+ },
404
+
405
+ /* Toggles a countdown widget between 'lap' and 'resume'.
406
+ @param target (element) the containing division */
407
+ _lapTogglePlugin: function(target) {
408
+ var inst = $.data(target, this.propertyName);
409
+ if (inst) {
410
+ if (inst._hold == 'lap') {
411
+ this._resumePlugin(target);
412
+ }
413
+ else {
414
+ this._lapPlugin(target);
415
+ }
416
+ }
417
+ },
418
+
419
+ /* Pause or resume a countdown widget.
420
+ @param target (element) the containing division
421
+ @param hold (string) the new hold setting */
422
+ _hold: function(target, hold) {
423
+ var inst = $.data(target, this.propertyName);
424
+ if (inst) {
425
+ if (inst._hold == 'pause' && !hold) {
426
+ inst._periods = inst._savePeriods;
427
+ var sign = (inst._since ? '-' : '+');
428
+ inst[inst._since ? '_since' : '_until'] =
429
+ this._determineTime(sign + inst._periods[0] + 'y' +
430
+ sign + inst._periods[1] + 'o' + sign + inst._periods[2] + 'w' +
431
+ sign + inst._periods[3] + 'd' + sign + inst._periods[4] + 'h' +
432
+ sign + inst._periods[5] + 'm' + sign + inst._periods[6] + 's');
433
+ this._addTarget(target);
434
+ }
435
+ inst._hold = hold;
436
+ inst._savePeriods = (hold == 'pause' ? inst._periods : null);
437
+ $.data(target, this.propertyName, inst);
438
+ this._updateCountdown(target, inst);
439
+ }
440
+ },
441
+
442
+ /* Return the current time periods.
443
+ @param target (element) the containing division
444
+ @return (number[7]) the current periods for the countdown */
445
+ _getTimesPlugin: function(target) {
446
+ var inst = $.data(target, this.propertyName);
447
+ return (!inst ? null : (inst._hold == 'pause' ? inst._savePeriods : (!inst._hold ? inst._periods :
448
+ this._calculatePeriods(inst, inst._show, inst.options.significant, new Date()))));
449
+ },
450
+
451
+ /* A time may be specified as an exact value or a relative one.
452
+ @param setting (string or number or Date) - the date/time value
453
+ as a relative or absolute value
454
+ @param defaultTime (Date) the date/time to use if no other is supplied
455
+ @return (Date) the corresponding date/time */
456
+ _determineTime: function(setting, defaultTime) {
457
+ var offsetNumeric = function(offset) { // e.g. +300, -2
458
+ var time = new Date();
459
+ time.setTime(time.getTime() + offset * 1000);
460
+ return time;
461
+ };
462
+ var offsetString = function(offset) { // e.g. '+2d', '-4w', '+3h +30m'
463
+ offset = offset.toLowerCase();
464
+ var time = new Date();
465
+ var year = time.getFullYear();
466
+ var month = time.getMonth();
467
+ var day = time.getDate();
468
+ var hour = time.getHours();
469
+ var minute = time.getMinutes();
470
+ var second = time.getSeconds();
471
+ var pattern = /([+-]?[0-9]+)\s*(s|m|h|d|w|o|y)?/g;
472
+ var matches = pattern.exec(offset);
473
+ while (matches) {
474
+ switch (matches[2] || 's') {
475
+ case 's': second += parseInt(matches[1], 10); break;
476
+ case 'm': minute += parseInt(matches[1], 10); break;
477
+ case 'h': hour += parseInt(matches[1], 10); break;
478
+ case 'd': day += parseInt(matches[1], 10); break;
479
+ case 'w': day += parseInt(matches[1], 10) * 7; break;
480
+ case 'o':
481
+ month += parseInt(matches[1], 10);
482
+ day = Math.min(day, plugin._getDaysInMonth(year, month));
483
+ break;
484
+ case 'y':
485
+ year += parseInt(matches[1], 10);
486
+ day = Math.min(day, plugin._getDaysInMonth(year, month));
487
+ break;
488
+ }
489
+ matches = pattern.exec(offset);
490
+ }
491
+ return new Date(year, month, day, hour, minute, second, 0);
492
+ };
493
+ var time = (setting == null ? defaultTime :
494
+ (typeof setting == 'string' ? offsetString(setting) :
495
+ (typeof setting == 'number' ? offsetNumeric(setting) : setting)));
496
+ if (time) time.setMilliseconds(0);
497
+ return time;
498
+ },
499
+
500
+ /* Determine the number of days in a month.
501
+ @param year (number) the year
502
+ @param month (number) the month
503
+ @return (number) the days in that month */
504
+ _getDaysInMonth: function(year, month) {
505
+ return 32 - new Date(year, month, 32).getDate();
506
+ },
507
+
508
+ /* Determine which set of labels should be used for an amount.
509
+ @param num (number) the amount to be displayed
510
+ @return (number) the set of labels to be used for this amount */
511
+ _normalLabels: function(num) {
512
+ return num;
513
+ },
514
+
515
+ /* Generate the HTML to display the countdown widget.
516
+ @param inst (object) the current settings for this instance
517
+ @return (string) the new HTML for the countdown display */
518
+ _generateHTML: function(inst) {
519
+ var self = this;
520
+ // Determine what to show
521
+ inst._periods = (inst._hold ? inst._periods :
522
+ this._calculatePeriods(inst, inst._show, inst.options.significant, new Date()));
523
+ // Show all 'asNeeded' after first non-zero value
524
+ var shownNonZero = false;
525
+ var showCount = 0;
526
+ var sigCount = inst.options.significant;
527
+ var show = $.extend({}, inst._show);
528
+ for (var period = Y; period <= S; period++) {
529
+ shownNonZero |= (inst._show[period] == '?' && inst._periods[period] > 0);
530
+ show[period] = (inst._show[period] == '?' && !shownNonZero ? null : inst._show[period]);
531
+ showCount += (show[period] ? 1 : 0);
532
+ sigCount -= (inst._periods[period] > 0 ? 1 : 0);
533
+ }
534
+ var showSignificant = [false, false, false, false, false, false, false];
535
+ for (var period = S; period >= Y; period--) { // Determine significant periods
536
+ if (inst._show[period]) {
537
+ if (inst._periods[period]) {
538
+ showSignificant[period] = true;
539
+ }
540
+ else {
541
+ showSignificant[period] = sigCount > 0;
542
+ sigCount--;
543
+ }
544
+ }
545
+ }
546
+ var labels = (inst.options.compact ? inst.options.compactLabels : inst.options.labels);
547
+ var whichLabels = inst.options.whichLabels || this._normalLabels;
548
+ var showCompact = function(period) {
549
+ var labelsNum = inst.options['compactLabels' + whichLabels(inst._periods[period])];
550
+ return (show[period] ? self._translateDigits(inst, inst._periods[period]) +
551
+ (labelsNum ? labelsNum[period] : labels[period]) + ' ' : '');
552
+ };
553
+ var showFull = function(period) {
554
+ var labelsNum = inst.options['labels' + whichLabels(inst._periods[period])];
555
+ return ((!inst.options.significant && show[period]) ||
556
+ (inst.options.significant && showSignificant[period]) ?
557
+ '<span class="' + plugin._sectionClass + '">' +
558
+ '<span class="' + plugin._amountClass + '">' +
559
+ self._translateDigits(inst, inst._periods[period]) + '</span><br/>' +
560
+ (labelsNum ? labelsNum[period] : labels[period]) + '</span>' : '');
561
+ };
562
+ return (inst.options.layout ? this._buildLayout(inst, show, inst.options.layout,
563
+ inst.options.compact, inst.options.significant, showSignificant) :
564
+ ((inst.options.compact ? // Compact version
565
+ '<span class="' + this._rowClass + ' ' + this._amountClass +
566
+ (inst._hold ? ' ' + this._holdingClass : '') + '">' +
567
+ showCompact(Y) + showCompact(O) + showCompact(W) + showCompact(D) +
568
+ (show[H] ? this._minDigits(inst, inst._periods[H], 2) : '') +
569
+ (show[M] ? (show[H] ? inst.options.timeSeparator : '') +
570
+ this._minDigits(inst, inst._periods[M], 2) : '') +
571
+ (show[S] ? (show[H] || show[M] ? inst.options.timeSeparator : '') +
572
+ this._minDigits(inst, inst._periods[S], 2) : '') :
573
+ // Full version
574
+ '<span class="' + this._rowClass + ' ' + this._showClass + (inst.options.significant || showCount) +
575
+ (inst._hold ? ' ' + this._holdingClass : '') + '">' +
576
+ showFull(Y) + showFull(O) + showFull(W) + showFull(D) +
577
+ showFull(H) + showFull(M) + showFull(S)) + '</span>' +
578
+ (inst.options.description ? '<span class="' + this._rowClass + ' ' + this._descrClass + '">' +
579
+ inst.options.description + '</span>' : '')));
580
+ },
581
+
582
+ /* Construct a custom layout.
583
+ @param inst (object) the current settings for this instance
584
+ @param show (string[7]) flags indicating which periods are requested
585
+ @param layout (string) the customised layout
586
+ @param compact (boolean) true if using compact labels
587
+ @param significant (number) the number of periods with values to show, zero for all
588
+ @param showSignificant (boolean[7]) other periods to show for significance
589
+ @return (string) the custom HTML */
590
+ _buildLayout: function(inst, show, layout, compact, significant, showSignificant) {
591
+ var labels = inst.options[compact ? 'compactLabels' : 'labels'];
592
+ var whichLabels = inst.options.whichLabels || this._normalLabels;
593
+ var labelFor = function(index) {
594
+ return (inst.options[(compact ? 'compactLabels' : 'labels') +
595
+ whichLabels(inst._periods[index])] || labels)[index];
596
+ };
597
+ var digit = function(value, position) {
598
+ return inst.options.digits[Math.floor(value / position) % 10];
599
+ };
600
+ var subs = {desc: inst.options.description, sep: inst.options.timeSeparator,
601
+ yl: labelFor(Y), yn: this._minDigits(inst, inst._periods[Y], 1),
602
+ ynn: this._minDigits(inst, inst._periods[Y], 2),
603
+ ynnn: this._minDigits(inst, inst._periods[Y], 3), y1: digit(inst._periods[Y], 1),
604
+ y10: digit(inst._periods[Y], 10), y100: digit(inst._periods[Y], 100),
605
+ y1000: digit(inst._periods[Y], 1000),
606
+ ol: labelFor(O), on: this._minDigits(inst, inst._periods[O], 1),
607
+ onn: this._minDigits(inst, inst._periods[O], 2),
608
+ onnn: this._minDigits(inst, inst._periods[O], 3), o1: digit(inst._periods[O], 1),
609
+ o10: digit(inst._periods[O], 10), o100: digit(inst._periods[O], 100),
610
+ o1000: digit(inst._periods[O], 1000),
611
+ wl: labelFor(W), wn: this._minDigits(inst, inst._periods[W], 1),
612
+ wnn: this._minDigits(inst, inst._periods[W], 2),
613
+ wnnn: this._minDigits(inst, inst._periods[W], 3), w1: digit(inst._periods[W], 1),
614
+ w10: digit(inst._periods[W], 10), w100: digit(inst._periods[W], 100),
615
+ w1000: digit(inst._periods[W], 1000),
616
+ dl: labelFor(D), dn: this._minDigits(inst, inst._periods[D], 1),
617
+ dnn: this._minDigits(inst, inst._periods[D], 2),
618
+ dnnn: this._minDigits(inst, inst._periods[D], 3), d1: digit(inst._periods[D], 1),
619
+ d10: digit(inst._periods[D], 10), d100: digit(inst._periods[D], 100),
620
+ d1000: digit(inst._periods[D], 1000),
621
+ hl: labelFor(H), hn: this._minDigits(inst, inst._periods[H], 1),
622
+ hnn: this._minDigits(inst, inst._periods[H], 2),
623
+ hnnn: this._minDigits(inst, inst._periods[H], 3), h1: digit(inst._periods[H], 1),
624
+ h10: digit(inst._periods[H], 10), h100: digit(inst._periods[H], 100),
625
+ h1000: digit(inst._periods[H], 1000),
626
+ ml: labelFor(M), mn: this._minDigits(inst, inst._periods[M], 1),
627
+ mnn: this._minDigits(inst, inst._periods[M], 2),
628
+ mnnn: this._minDigits(inst, inst._periods[M], 3), m1: digit(inst._periods[M], 1),
629
+ m10: digit(inst._periods[M], 10), m100: digit(inst._periods[M], 100),
630
+ m1000: digit(inst._periods[M], 1000),
631
+ sl: labelFor(S), sn: this._minDigits(inst, inst._periods[S], 1),
632
+ snn: this._minDigits(inst, inst._periods[S], 2),
633
+ snnn: this._minDigits(inst, inst._periods[S], 3), s1: digit(inst._periods[S], 1),
634
+ s10: digit(inst._periods[S], 10), s100: digit(inst._periods[S], 100),
635
+ s1000: digit(inst._periods[S], 1000)};
636
+ var html = layout;
637
+ // Replace period containers: {p<}...{p>}
638
+ for (var i = Y; i <= S; i++) {
639
+ var period = 'yowdhms'.charAt(i);
640
+ var re = new RegExp('\\{' + period + '<\\}([\\s\\S]*)\\{' + period + '>\\}', 'g');
641
+ html = html.replace(re, ((!significant && show[i]) ||
642
+ (significant && showSignificant[i]) ? '$1' : ''));
643
+ }
644
+ // Replace period values: {pn}
645
+ $.each(subs, function(n, v) {
646
+ var re = new RegExp('\\{' + n + '\\}', 'g');
647
+ html = html.replace(re, v);
648
+ });
649
+ return html;
650
+ },
651
+
652
+ /* Ensure a numeric value has at least n digits for display.
653
+ @param inst (object) the current settings for this instance
654
+ @param value (number) the value to display
655
+ @param len (number) the minimum length
656
+ @return (string) the display text */
657
+ _minDigits: function(inst, value, len) {
658
+ value = '' + value;
659
+ if (value.length >= len) {
660
+ return this._translateDigits(inst, value);
661
+ }
662
+ value = '0000000000' + value;
663
+ return this._translateDigits(inst, value.substr(value.length - len));
664
+ },
665
+
666
+ /* Translate digits into other representations.
667
+ @param inst (object) the current settings for this instance
668
+ @param value (string) the text to translate
669
+ @return (string) the translated text */
670
+ _translateDigits: function(inst, value) {
671
+ return ('' + value).replace(/[0-9]/g, function(digit) {
672
+ return inst.options.digits[digit];
673
+ });
674
+ },
675
+
676
+ /* Translate the format into flags for each period.
677
+ @param inst (object) the current settings for this instance
678
+ @return (string[7]) flags indicating which periods are requested (?) or
679
+ required (!) by year, month, week, day, hour, minute, second */
680
+ _determineShow: function(inst) {
681
+ var format = inst.options.format;
682
+ var show = [];
683
+ show[Y] = (format.match('y') ? '?' : (format.match('Y') ? '!' : null));
684
+ show[O] = (format.match('o') ? '?' : (format.match('O') ? '!' : null));
685
+ show[W] = (format.match('w') ? '?' : (format.match('W') ? '!' : null));
686
+ show[D] = (format.match('d') ? '?' : (format.match('D') ? '!' : null));
687
+ show[H] = (format.match('h') ? '?' : (format.match('H') ? '!' : null));
688
+ show[M] = (format.match('m') ? '?' : (format.match('M') ? '!' : null));
689
+ show[S] = (format.match('s') ? '?' : (format.match('S') ? '!' : null));
690
+ return show;
691
+ },
692
+
693
+ /* Calculate the requested periods between now and the target time.
694
+ @param inst (object) the current settings for this instance
695
+ @param show (string[7]) flags indicating which periods are requested/required
696
+ @param significant (number) the number of periods with values to show, zero for all
697
+ @param now (Date) the current date and time
698
+ @return (number[7]) the current time periods (always positive)
699
+ by year, month, week, day, hour, minute, second */
700
+ _calculatePeriods: function(inst, show, significant, now) {
701
+ // Find endpoints
702
+ inst._now = now;
703
+ inst._now.setMilliseconds(0);
704
+ var until = new Date(inst._now.getTime());
705
+ if (inst._since) {
706
+ if (now.getTime() < inst._since.getTime()) {
707
+ inst._now = now = until;
708
+ }
709
+ else {
710
+ now = inst._since;
711
+ }
712
+ }
713
+ else {
714
+ until.setTime(inst._until.getTime());
715
+ if (now.getTime() > inst._until.getTime()) {
716
+ inst._now = now = until;
717
+ }
718
+ }
719
+ // Calculate differences by period
720
+ var periods = [0, 0, 0, 0, 0, 0, 0];
721
+ if (show[Y] || show[O]) {
722
+ // Treat end of months as the same
723
+ var lastNow = plugin._getDaysInMonth(now.getFullYear(), now.getMonth());
724
+ var lastUntil = plugin._getDaysInMonth(until.getFullYear(), until.getMonth());
725
+ var sameDay = (until.getDate() == now.getDate() ||
726
+ (until.getDate() >= Math.min(lastNow, lastUntil) &&
727
+ now.getDate() >= Math.min(lastNow, lastUntil)));
728
+ var getSecs = function(date) {
729
+ return (date.getHours() * 60 + date.getMinutes()) * 60 + date.getSeconds();
730
+ };
731
+ var months = Math.max(0,
732
+ (until.getFullYear() - now.getFullYear()) * 12 + until.getMonth() - now.getMonth() +
733
+ ((until.getDate() < now.getDate() && !sameDay) ||
734
+ (sameDay && getSecs(until) < getSecs(now)) ? -1 : 0));
735
+ periods[Y] = (show[Y] ? Math.floor(months / 12) : 0);
736
+ periods[O] = (show[O] ? months - periods[Y] * 12 : 0);
737
+ // Adjust for months difference and end of month if necessary
738
+ now = new Date(now.getTime());
739
+ var wasLastDay = (now.getDate() == lastNow);
740
+ var lastDay = plugin._getDaysInMonth(now.getFullYear() + periods[Y],
741
+ now.getMonth() + periods[O]);
742
+ if (now.getDate() > lastDay) {
743
+ now.setDate(lastDay);
744
+ }
745
+ now.setFullYear(now.getFullYear() + periods[Y]);
746
+ now.setMonth(now.getMonth() + periods[O]);
747
+ if (wasLastDay) {
748
+ now.setDate(lastDay);
749
+ }
750
+ }
751
+ var diff = Math.floor((until.getTime() - now.getTime()) / 1000);
752
+ var extractPeriod = function(period, numSecs) {
753
+ periods[period] = (show[period] ? Math.floor(diff / numSecs) : 0);
754
+ diff -= periods[period] * numSecs;
755
+ };
756
+ extractPeriod(W, 604800);
757
+ extractPeriod(D, 86400);
758
+ extractPeriod(H, 3600);
759
+ extractPeriod(M, 60);
760
+ extractPeriod(S, 1);
761
+ if (diff > 0 && !inst._since) { // Round up if left overs
762
+ var multiplier = [1, 12, 4.3482, 7, 24, 60, 60];
763
+ var lastShown = S;
764
+ var max = 1;
765
+ for (var period = S; period >= Y; period--) {
766
+ if (show[period]) {
767
+ if (periods[lastShown] >= max) {
768
+ periods[lastShown] = 0;
769
+ diff = 1;
770
+ }
771
+ if (diff > 0) {
772
+ periods[period]++;
773
+ diff = 0;
774
+ lastShown = period;
775
+ max = 1;
776
+ }
777
+ }
778
+ max *= multiplier[period];
779
+ }
780
+ }
781
+ if (significant) { // Zero out insignificant periods
782
+ for (var period = Y; period <= S; period++) {
783
+ if (significant && periods[period]) {
784
+ significant--;
785
+ }
786
+ else if (!significant) {
787
+ periods[period] = 0;
788
+ }
789
+ }
790
+ }
791
+ return periods;
792
+ }
793
+ });
794
+
795
+ // The list of commands that return values and don't permit chaining
796
+ var getters = ['getTimes'];
797
+
798
+ /* Determine whether a command is a getter and doesn't permit chaining.
799
+ @param command (string, optional) the command to run
800
+ @param otherArgs ([], optional) any other arguments for the command
801
+ @return true if the command is a getter, false if not */
802
+ function isNotChained(command, otherArgs) {
803
+ if (command == 'option' && (otherArgs.length == 0 ||
804
+ (otherArgs.length == 1 && typeof otherArgs[0] == 'string'))) {
805
+ return true;
806
+ }
807
+ return $.inArray(command, getters) > -1;
808
+ }
809
+
810
+ /* Process the countdown functionality for a jQuery selection.
811
+ @param options (object) the new settings to use for these instances (optional) or
812
+ (string) the command to run (optional)
813
+ @return (jQuery) for chaining further calls or
814
+ (any) getter value */
815
+ $.fn.countdown = function(options) {
816
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
817
+ if (isNotChained(options, otherArgs)) {
818
+ return plugin['_' + options + 'Plugin'].
819
+ apply(plugin, [this[0]].concat(otherArgs));
820
+ }
821
+ return this.each(function() {
822
+ if (typeof options == 'string') {
823
+ if (!plugin['_' + options + 'Plugin']) {
824
+ throw 'Unknown command: ' + options;
825
+ }
826
+ plugin['_' + options + 'Plugin'].
827
+ apply(plugin, [this].concat(otherArgs));
828
+ }
829
+ else {
830
+ plugin._attachPlugin(this, options || {});
831
+ }
832
+ });
833
+ };
834
+
835
+ /* Initialise the countdown functionality. */
836
+ var plugin = $.countdown = new Countdown(); // Singleton instance
837
+
838
+ })(jQuery);