visibilityjs 0.4.5 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,3 +1,8 @@
1
+ == 0.5 (SCORE, communication)
2
+ * Split library to core and timers modules.
3
+ * Allow to unbind change listener.
4
+ * Use common logic in change(), afterPrerendering() and onVisible().
5
+
1
6
  == 0.4.5 (Pioneer 3, closer)
2
7
  * Fix gem assets directory.
3
8
  * Update development dependencies.
data/README.md CHANGED
@@ -219,6 +219,18 @@ Visibility.change(function (e, state) {
219
219
 
220
220
  ## Installing
221
221
 
222
+ ### Packages
223
+
224
+ Visibility.js shipped with 4 files:
225
+
226
+ * `visibility.core` – core module.
227
+ * `visibility.timers` – `every` and `stop` methods to set `setTimeout` depend
228
+ on visibility state.
229
+ * `visibility` – `visibility.core` and `visibility.timers` together.
230
+ * `visibility.fallback` – fallback for browser without Page Visibility API.
231
+ It use document `focus`/`blur` events, so it say, that document in hidden,
232
+ when browser just lose focus.
233
+
222
234
  ### Ruby on Rails
223
235
 
224
236
  For Ruby on Rails you can use gem for Assets Pipeline.
@@ -241,6 +253,13 @@ For Ruby on Rails you can use gem for Assets Pipeline.
241
253
  #= require visibility
242
254
  ```
243
255
 
256
+ If you didn’t use `every` method, you can reduce library size, by including
257
+ only core module:
258
+
259
+ ```coffee
260
+ #= require visibility.core
261
+ ```
262
+
244
263
  ### Other
245
264
 
246
265
  If you don’t use any assets packaging manager (it’s very bad idea), you can use
@@ -0,0 +1,248 @@
1
+ /*
2
+ * Copyright 2011 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
3
+ * sponsored by Evil Martians.
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Lesser General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public License
16
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ ;(function () {
20
+ "use strict";
21
+
22
+ var defined = function (variable) {
23
+ return ('undefined' != typeof(variable));
24
+ };
25
+
26
+ // Visibility.js allow you to know, that your web page is in the background
27
+ // tab and thus not visible to the user. This library is wrap under
28
+ // Page Visibility API. It fix problems with different vendor prefixes and
29
+ // add high-level useful functions.
30
+ window.Visibility = {
31
+
32
+ // Call callback only when page become to visible for user or
33
+ // call it now if page is visible now or Page Visibility API
34
+ // doesn’t supported.
35
+ //
36
+ // Return true if callback if called now.
37
+ //
38
+ // Visibility.onVisible(function () {
39
+ // Notification.animateNotice("Hello");
40
+ // });
41
+ onVisible: function (callback) {
42
+ if ( !this.isSupported() || !this.hidden() ) {
43
+ callback();
44
+ return true;
45
+ }
46
+
47
+ var listener = this.change(function (e, state) {
48
+ if ( !Visibility.hidden() ) {
49
+ Visibility.unbind(listener);
50
+ callback();
51
+ }
52
+ });
53
+ },
54
+
55
+ // Call callback when visibility will be changed. First argument for
56
+ // callback will be original event object, second will be visibility
57
+ // state name.
58
+ //
59
+ // Return listener ID to unbind listener by `unbind` method.
60
+ //
61
+ // If Page Visibility API doesn’t supported method will be return false
62
+ // and callback never will be called.
63
+ //
64
+ // Visibility.change(function(e, state) {
65
+ // Statistics.visibilityChange(state);
66
+ // });
67
+ //
68
+ // It is just proxy to `visibilitychange` event, but use vendor prefix.
69
+ change: function (callback) {
70
+ if ( !this.isSupported() ) {
71
+ return false;
72
+ }
73
+ this._lastCallback += 1;
74
+ var number = this._lastCallback;
75
+ this._callbacks[number] = callback;
76
+ this._setListener();
77
+ return number;
78
+ },
79
+
80
+ // Remove `change` listener by it ID.
81
+ //
82
+ // var id = Visibility.change(function(e, state) {
83
+ // firstChangeCallback();
84
+ // Visibility.unbind(id);
85
+ // });
86
+ unbind: function (id) {
87
+ delete this._callbacks[id];
88
+ },
89
+
90
+ // Call `callback` in any state, expect “prerender”. If current state
91
+ // is “prerender” it will wait until state will be changed.
92
+ // If Page Visibility API doesn’t supported, it will call `callback`
93
+ // immediately.
94
+ //
95
+ // Visibility.afterPrerendering(function () {
96
+ // Statistics.countVisitor();
97
+ // });
98
+ afterPrerendering: function (callback) {
99
+ if ( !this.isSupported() || 'prerender' != this.state() ) {
100
+ callback();
101
+ return true;
102
+ }
103
+
104
+ var listener = this.change(function (e, state) {
105
+ if ( 'prerender' != state ) {
106
+ Visibility.unbind(listener);
107
+ callback();
108
+ }
109
+ });
110
+ },
111
+
112
+ // Return true if page now isn’t visible to user.
113
+ //
114
+ // if ( !Visibility.hidden() ) {
115
+ // VideoPlayer.play();
116
+ // }
117
+ //
118
+ // It is just proxy to `document.hidden`, but use vendor prefix.
119
+ hidden: function () {
120
+ return this._prop('hidden', false);
121
+ },
122
+
123
+ // Return visibility state: 'visible', 'hidden' or 'prerender'.
124
+ //
125
+ // if ( 'prerender' == Visibility.state() ) {
126
+ // Statistics.pageIsPrerendering();
127
+ // }
128
+ //
129
+ // Don’t use `Visibility.state()` to detect, is page visible, because
130
+ // visibility states can extend in next API versions.
131
+ // Use more simpler and general `Visibility.hidden()` for this cases.
132
+ //
133
+ // It is just proxy to `document.visibilityState`, but use
134
+ // vendor prefix.
135
+ state: function () {
136
+ return this._prop('visibilityState', 'visible');
137
+ },
138
+
139
+ // Return true if browser support Page Visibility API.
140
+ //
141
+ // if ( Visibility.isSupported() ) {
142
+ // Statistics.startTrackingVisibility();
143
+ // Visibility.change(function(e, state)) {
144
+ // Statistics.trackVisibility(state);
145
+ // });
146
+ // }
147
+ isSupported: function () {
148
+ return defined(this._prefix());
149
+ },
150
+
151
+ // Link to document object to change it in tests.
152
+ _doc: window.document,
153
+
154
+ // Vendor prefixes to create event and properties names.
155
+ _prefixes: ['webkit', 'moz', 'o', 'ms'],
156
+
157
+ // Vendor prefix cached by `_prefix` function.
158
+ _chechedPrefix: null,
159
+
160
+ // Is listener for `visibilitychange` event is already added
161
+ // by `_setListener` method.
162
+ _listening: false,
163
+
164
+ // Last timer number.
165
+ _lastCallback: -1,
166
+
167
+ // Callbacks from `change` method, that wait visibility changes.
168
+ _callbacks: { },
169
+
170
+ // Variable to check hidden-visible state changes.
171
+ _hiddenBefore: false,
172
+
173
+ // Initialize variables on page loading.
174
+ _init: function () {
175
+ this._hiddenBefore = this.hidden();
176
+ },
177
+
178
+ // Detect vendor prefix and return it.
179
+ _prefix: function () {
180
+ if ( null !== this._chechedPrefix ) {
181
+ return this._chechedPrefix;
182
+ }
183
+ if ( defined(this._doc.visibilityState) ) {
184
+ return this._chechedPrefix = '';
185
+ }
186
+ var name;
187
+ for ( var i = 0; i < this._prefixes.length; i++ ) {
188
+ name = this._prefixes[i] + 'VisibilityState';
189
+ if ( defined(this._doc[name]) ) {
190
+ return this._chechedPrefix = this._prefixes[i];
191
+ }
192
+ }
193
+ },
194
+
195
+ // Return property name with vendor prefix.
196
+ _name: function (name) {
197
+ var prefix = this._prefix();
198
+ if ( '' == prefix ) {
199
+ return name;
200
+ } else {
201
+ return prefix +
202
+ name.substr(0, 1).toUpperCase() + name.substr(1);
203
+ }
204
+ },
205
+
206
+ // Return document’s property value with name with vendor prefix.
207
+ // If API is not support, it will retun `unsupported` value.
208
+ _prop: function (name, unsupported) {
209
+ if ( !this.isSupported() ) {
210
+ return unsupported;
211
+ }
212
+ return this._doc[this._name(name)];
213
+ },
214
+
215
+ // Listener for `visibilitychange` event.
216
+ _onChange: function(event) {
217
+ var state = this.state();
218
+
219
+ for ( var i in this._callbacks ) {
220
+ this._callbacks[i].call(this._doc, event, state);
221
+ }
222
+
223
+ this._hiddenBefore = this.hidden();
224
+ },
225
+
226
+ // Set listener for `visibilitychange` event.
227
+ _setListener: function () {
228
+ if ( this._listening ) {
229
+ return;
230
+ }
231
+ var event = this._prefix() + 'visibilitychange';
232
+ var listener = function () {
233
+ Visibility._onChange.apply(Visibility, arguments);
234
+ };
235
+ if ( this._doc.addEventListener ) {
236
+ this._doc.addEventListener(event, listener, false);
237
+ } else {
238
+ this._doc.attachEvent(event, listener);
239
+ }
240
+ this._listening = true;
241
+ this._hiddenBefore = this.hidden();
242
+ }
243
+
244
+ };
245
+
246
+ Visibility._init();
247
+
248
+ })();
@@ -26,7 +26,7 @@
26
26
  //
27
27
  // For Firefox 5–9 it will be better to use MozVisibility hack without
28
28
  // this issue. See <https://github.com/private-face/mozvisibility>.
29
- ;(function() {
29
+ ;(function () {
30
30
  "use strict";
31
31
 
32
32
  if ( document.visibilityState || document.webkitVisibilityState ||
@@ -49,7 +49,7 @@
49
49
  document.dispatchEvent(event);
50
50
  } else {
51
51
  if ( typeof(Visibility) == 'object' ) {
52
- Visibility._onVisibilityChange.call(Visibility, { })
52
+ Visibility._onChange.call(Visibility, { })
53
53
  }
54
54
  }
55
55
  }
@@ -16,10 +16,10 @@
16
16
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
 
19
- ;(function() {
19
+ ;(function () {
20
20
  "use strict";
21
21
 
22
- var defined = function(variable) {
22
+ var defined = function (variable) {
23
23
  return ('undefined' != typeof(variable));
24
24
  };
25
25
 
@@ -35,7 +35,7 @@
35
35
  //
36
36
  // Return true if callback if called now.
37
37
  //
38
- // Visibility.onVisible(function() {
38
+ // Visibility.onVisible(function () {
39
39
  // Notification.animateNotice("Hello");
40
40
  // });
41
41
  onVisible: function (callback) {
@@ -43,84 +43,21 @@
43
43
  callback();
44
44
  return true;
45
45
  }
46
- this._onVisibleCallbacks.push(callback);
47
- this._setListener();
48
- },
49
46
 
50
- // Run callback every `interval` milliseconds if page is visible and
51
- // every `hiddenInterval` milliseconds if page is hidden.
52
- //
53
- // Visibility.every(60 * 1000, 5 * 60 * 1000, function() {
54
- // checkNewMails();
55
- // });
56
- //
57
- // You can skip `hiddenInterval` and callback will be called only if
58
- // page is visible.
59
- //
60
- // Visibility.every(1000, function() {
61
- // updateCountdown();
62
- // });
63
- //
64
- // It is analog of `setInterval(callback, interval)` but use visibility
65
- // state.
66
- //
67
- // It return timer ID, that you can use in `Visibility.stop(id)` to stop
68
- // timer (`clearInterval` analog).
69
- // Warning: timer ID is different from intervalID from `setInterval`,
70
- // so don’t use it in `clearInterval`.
71
- //
72
- // On change state from hidden to visible timers will be execute.
73
- //
74
- // If you include jQuery Chrono plugin before Visibility.js, you could
75
- // use Chrono’s syntax sugar in interval arguments:
76
- //
77
- // Visibility.every('second', function() {
78
- // updateCountdown();
79
- // });
80
- // Visibility.every('1 minute', '5 minutes', function() {
81
- // checkNewMails();
82
- // });
83
- every: function (interval, hiddenInterval, callback) {
84
- if ( !defined(callback) ) {
85
- callback = hiddenInterval;
86
- hiddenInterval = null;
87
- }
88
- this._lastTimer += 1;
89
- var number = this._lastTimer;
90
- this._timers[number] = ({
91
- interval: interval,
92
- hiddenInterval: hiddenInterval,
93
- callback: callback
47
+ var listener = this.change(function (e, state) {
48
+ if ( !Visibility.hidden() ) {
49
+ Visibility.unbind(listener);
50
+ callback();
51
+ }
94
52
  });
95
- this._runTimer(number, false);
96
- if ( this.isSupported() ) {
97
- this._setListener();
98
- }
99
- return number;
100
- },
101
-
102
- // Stop timer from `every` method by it ID (`every` method return it).
103
- //
104
- // slideshow = Visibility.every(5 * 1000, function() {
105
- // changeSlide();
106
- // });
107
- // $('.stopSlideshow').click(function() {
108
- // Visibility.stop(slideshow);
109
- // });
110
- stop: function(id) {
111
- var timer = this._timers[id]
112
- if ( !defined(timer) ) {
113
- return false;
114
- }
115
- this._stopTimer(id);
116
- delete this._timers[id];
117
- return timer;
118
53
  },
119
54
 
120
55
  // Call callback when visibility will be changed. First argument for
121
56
  // callback will be original event object, second will be visibility
122
57
  // state name.
123
58
  //
59
+ // Return listener ID to unbind listener by `unbind` method.
60
+ //
124
61
  // If Page Visibility API doesn’t supported method will be return false
125
62
  // and callback never will be called.
126
63
  //
@@ -133,9 +70,21 @@
133
70
  if ( !this.isSupported() ) {
134
71
  return false;
135
72
  }
136
- this._changeCallbacks.push(callback);
73
+ this._lastCallback += 1;
74
+ var number = this._lastCallback;
75
+ this._callbacks[number] = callback;
137
76
  this._setListener();
138
- return true;
77
+ return number;
78
+ },
79
+
80
+ // Remove `change` listener by it ID.
81
+ //
82
+ // var id = Visibility.change(function(e, state) {
83
+ // firstChangeCallback();
84
+ // Visibility.unbind(id);
85
+ // });
86
+ unbind: function (id) {
87
+ delete this._callbacks[id];
139
88
  },
140
89
 
141
90
  // Call `callback` in any state, expect “prerender”. If current state
@@ -151,8 +100,13 @@
151
100
  callback();
152
101
  return true;
153
102
  }
154
- this._afterPrerenderingCallbacks.push(callback);
155
- this._setListener();
103
+
104
+ var listener = this.change(function (e, state) {
105
+ if ( 'prerender' != state ) {
106
+ Visibility.unbind(listener);
107
+ callback();
108
+ }
109
+ });
156
110
  },
157
111
 
158
112
  // Return true if page now isn’t visible to user.
@@ -163,10 +117,7 @@
163
117
  //
164
118
  // It is just proxy to `document.hidden`, but use vendor prefix.
165
119
  hidden: function () {
166
- if ( !this.isSupported() ) {
167
- return false;
168
- }
169
- return this._prop('hidden');
120
+ return this._prop('hidden', false);
170
121
  },
171
122
 
172
123
  // Return visibility state: 'visible', 'hidden' or 'prerender'.
@@ -182,10 +133,7 @@
182
133
  // It is just proxy to `document.visibilityState`, but use
183
134
  // vendor prefix.
184
135
  state: function () {
185
- if ( !this.isSupported() ) {
186
- return 'visible';
187
- }
188
- return this._prop('visibilityState');
136
+ return this._prop('visibilityState', 'visible');
189
137
  },
190
138
 
191
139
  // Return true if browser support Page Visibility API.
@@ -213,22 +161,11 @@
213
161
  // by `_setListener` method.
214
162
  _listening: false,
215
163
 
216
- // Callbacks from `change` method, that wait visibility changes.
217
- _changeCallbacks: [],
218
-
219
- // Callbacks from `onVisible` method, that wait when page become to be
220
- // visible.
221
- _onVisibleCallbacks: [],
222
-
223
- // Callbacks from `afterPrerendering` method, that wait when visibility
224
- // state change from “prerender”.
225
- _afterPrerenderingCallbacks: [],
226
-
227
164
  // Last timer number.
228
- _lastTimer: 0,
165
+ _lastCallback: -1,
229
166
 
230
- // Callbacks and intervals added by `every` method.
231
- _timers: { },
167
+ // Callbacks from `change` method, that wait visibility changes.
168
+ _callbacks: { },
232
169
 
233
170
  // Variable to check hidden-visible state changes.
234
171
  _hiddenBefore: false,
@@ -236,12 +173,6 @@
236
173
  // Initialize variables on page loading.
237
174
  _init: function () {
238
175
  this._hiddenBefore = this.hidden();
239
-
240
- if ( defined(window.jQuery) && defined(jQuery.every) ) {
241
- this._setInterval = this._chronoSetInterval;
242
- } else {
243
- this._setInterval = this._originalSetInterval;
244
- }
245
176
  },
246
177
 
247
178
  // Detect vendor prefix and return it.
@@ -273,43 +204,23 @@
273
204
  },
274
205
 
275
206
  // Return document’s property value with name with vendor prefix.
276
- _prop: function (name) {
277
- return this._doc[this._name(name)]
207
+ // If API is not support, it will retun `unsupported` value.
208
+ _prop: function (name, unsupported) {
209
+ if ( !this.isSupported() ) {
210
+ return unsupported;
211
+ }
212
+ return this._doc[this._name(name)];
278
213
  },
279
214
 
280
215
  // Listener for `visibilitychange` event.
281
- _onVisibilityChange: function(event) {
282
- var isHidden = this.hidden(),
283
- state = this.state();
284
-
285
- for ( var i = 0; i < this._changeCallbacks.length; i++ ) {
286
- this._changeCallbacks[i].call(this._doc, event, state);
287
- }
288
-
289
- var hiddenBefore = this._hiddenBefore;
290
- if ( (isHidden && !hiddenBefore) || (!isHidden && hiddenBefore) ) {
291
- for ( i in this._timers ) {
292
- this._stopTimer(i);
293
- this._runTimer(i, !isHidden);
294
- }
295
- }
296
-
297
- if ( !isHidden ) {
298
- for ( var i = 0; i < this._onVisibleCallbacks.length; i++ ) {
299
- this._onVisibleCallbacks[i]();
300
- }
301
- this._onVisibleCallbacks = [];
302
- }
216
+ _onChange: function(event) {
217
+ var state = this.state();
303
218
 
304
- if ( 'prerender' != this.state() ) {
305
- var after = this._afterPrerenderingCallbacks.length
306
- for ( var i = 0; i < after; i++ ) {
307
- this._afterPrerenderingCallbacks[i]();
308
- }
309
- this._afterPrerenderingCallbacks = [];
219
+ for ( var i in this._callbacks ) {
220
+ this._callbacks[i].call(this._doc, event, state);
310
221
  }
311
222
 
312
- this._hiddenBefore = isHidden;
223
+ this._hiddenBefore = this.hidden();
313
224
  },
314
225
 
315
226
  // Set listener for `visibilitychange` event.
@@ -319,7 +230,7 @@
319
230
  }
320
231
  var event = this._prefix() + 'visibilitychange';
321
232
  var listener = function () {
322
- Visibility._onVisibilityChange.apply(Visibility, arguments);
233
+ Visibility._onChange.apply(Visibility, arguments);
323
234
  };
324
235
  if ( this._doc.addEventListener ) {
325
236
  this._doc.addEventListener(event, listener, false);
@@ -328,63 +239,184 @@
328
239
  }
329
240
  this._listening = true;
330
241
  this._hiddenBefore = this.hidden();
331
- },
242
+ }
332
243
 
333
- // Set interval directly by `setInterval` function without any syntax
334
- // sugar.
335
- _originalSetInterval: function (callback, interval) {
336
- return setInterval(callback, interval);
337
- },
244
+ };
338
245
 
339
- // Set interval by jQuery Chrono plugin. Add syntax sugar to `interval`
340
- // and `hiddenInterval` arguments, such as "1 second" and others.
341
- //
342
- // It will be automatically set to `_setInterval` on loading if
343
- // you include jQuery Chrono plugin before Visibility.js.
344
- _chronoSetInterval: function (callback, internal) {
345
- return jQuery.every(internal, callback);
346
- },
246
+ Visibility._init();
347
247
 
348
- // Set interval by `setInterval`. Allow to change function for tests or
349
- // syntax sugar in `interval` arguments.
350
- //
351
- // Function will be automatically set in `_init` method (which will be
352
- // call on script loading). So you must include jQuery Chrono plugin
353
- // before Visibility.js.
354
- _setInterval: null,
355
-
356
- // Try to run timer from every method by it’s ID. It will be use
357
- // `interval` or `hiddenInterval` depending on visibility state.
358
- // If page is hidden and `hiddenInterval` is null,
359
- // it will not run timer.
360
- //
361
- // Argument `now` say, that timers must be execute now too.
362
- _runTimer: function (id, now) {
363
- var interval,
364
- timer = this._timers[id];
365
- if ( this.hidden() ) {
366
- if ( null === timer.hiddenInterval ) {
367
- return;
368
- }
369
- interval = timer.hiddenInterval;
370
- } else {
371
- interval = timer.interval;
372
- }
373
- if ( now ) {
374
- timer.callback.call(window);
375
- }
376
- timer.intervalID = this._setInterval(timer.callback, interval);
377
- },
378
248
 
379
- // Stop timer from `every` method by it’s ID.
380
- _stopTimer: function (id) {
381
- var timer = this._timers[id];
382
- clearInterval(timer.intervalID);
383
- delete timer.intervalID;
384
- }
249
+ var timers = {
250
+
251
+ // Run callback every `interval` milliseconds if page is visible and
252
+ // every `hiddenInterval` milliseconds if page is hidden.
253
+ //
254
+ // Visibility.every(60 * 1000, 5 * 60 * 1000, function () {
255
+ // checkNewMails();
256
+ // });
257
+ //
258
+ // You can skip `hiddenInterval` and callback will be called only if
259
+ // page is visible.
260
+ //
261
+ // Visibility.every(1000, function () {
262
+ // updateCountdown();
263
+ // });
264
+ //
265
+ // It is analog of `setInterval(callback, interval)` but use visibility
266
+ // state.
267
+ //
268
+ // It return timer ID, that you can use in `Visibility.stop(id)` to stop
269
+ // timer (`clearInterval` analog).
270
+ // Warning: timer ID is different from interval ID from `setInterval`,
271
+ // so don’t use it in `clearInterval`.
272
+ //
273
+ // On change state from hidden to visible timers will be execute.
274
+ //
275
+ // If you include jQuery Chrono plugin before Visibility.js, you could
276
+ // use Chrono’s syntax sugar in interval arguments:
277
+ //
278
+ // Visibility.every('second', function () {
279
+ // updateCountdown();
280
+ // });
281
+ // Visibility.every('1 minute', '5 minutes', function () {
282
+ // checkNewMails();
283
+ // });
284
+ every: function (interval, hiddenInterval, callback) {
285
+ this._initTimers();
286
+
287
+ if ( !defined(callback) ) {
288
+ callback = hiddenInterval;
289
+ hiddenInterval = null;
290
+ }
291
+ this._lastTimer += 1;
292
+ var number = this._lastTimer;
293
+ this._timers[number] = ({
294
+ interval: interval,
295
+ hiddenInterval: hiddenInterval,
296
+ callback: callback
297
+ });
298
+ this._runTimer(number, false);
299
+
300
+ if ( this.isSupported() ) {
301
+ this._setListener();
302
+ }
303
+ return number;
304
+ },
305
+
306
+ // Stop timer from `every` method by it ID (`every` method return it).
307
+ //
308
+ // slideshow = Visibility.every(5 * 1000, function () {
309
+ // changeSlide();
310
+ // });
311
+ // $('.stopSlideshow').click(function () {
312
+ // Visibility.stop(slideshow);
313
+ // });
314
+ stop: function(id) {
315
+ var timer = this._timers[id]
316
+ if ( !defined(timer) ) {
317
+ return false;
318
+ }
319
+ this._stopTimer(id);
320
+ delete this._timers[id];
321
+ return timer;
322
+ },
323
+
324
+ // Last timer number.
325
+ _lastTimer: -1,
326
+
327
+ // Callbacks and intervals added by `every` method.
328
+ _timers: { },
329
+
330
+ // Is setInterval method detected and listener is binded.
331
+ _timersInitialized: false,
332
+
333
+ // Initialize variables on page loading.
334
+ _initTimers: function () {
335
+ if ( this._timersInitialized ) {
336
+ return;
337
+ }
338
+ this._timersInitialized = true;
339
+
340
+ if ( defined(window.jQuery) && defined(jQuery.every) ) {
341
+ this._setInterval = this._chronoInterval;
342
+ } else {
343
+ this._setInterval = this._originalInterval;
344
+ }
345
+ this.change(function () {
346
+ Visibility._timersStopRun()
347
+ });
348
+ },
349
+
350
+ // Set interval directly by `setInterval` function without any syntax
351
+ // sugar.
352
+ _originalInterval: function (callback, interval) {
353
+ return setInterval(callback, interval);
354
+ },
355
+
356
+ // Set interval by jQuery Chrono plugin. Add syntax sugar to `interval`
357
+ // and `hiddenInterval` arguments, such as "1 second" and others.
358
+ //
359
+ // It will be automatically set to `_setInterval` on loading if
360
+ // you include jQuery Chrono plugin before Visibility.js.
361
+ _chronoInterval: function (callback, internal) {
362
+ return jQuery.every(internal, callback);
363
+ },
364
+
365
+ // Set interval by `setInterval`. Allow to change function for tests or
366
+ // syntax sugar in `interval` arguments.
367
+ //
368
+ // Function will be automatically set in `_init` method (which will be
369
+ // call on script loading). So you must include jQuery Chrono plugin
370
+ // before Visibility.js.
371
+ _setInterval: null,
372
+
373
+ // Try to run timer from every method by it’s ID. It will be use
374
+ // `interval` or `hiddenInterval` depending on visibility state.
375
+ // If page is hidden and `hiddenInterval` is null,
376
+ // it will not run timer.
377
+ //
378
+ // Argument `now` say, that timers must be execute now too.
379
+ _runTimer: function (id, now) {
380
+ var interval,
381
+ timer = this._timers[id];
382
+ if ( this.hidden() ) {
383
+ if ( null === timer.hiddenInterval ) {
384
+ return;
385
+ }
386
+ interval = timer.hiddenInterval;
387
+ } else {
388
+ interval = timer.interval;
389
+ }
390
+ if ( now ) {
391
+ timer.callback.call(window);
392
+ }
393
+ timer.id = this._setInterval(timer.callback, interval);
394
+ },
395
+
396
+ // Stop timer from `every` method by it’s ID.
397
+ _stopTimer: function (id) {
398
+ var timer = this._timers[id];
399
+ clearInterval(timer.id);
400
+ delete timer.id;
401
+ },
402
+
403
+ // Listener for `visibilitychange` event.
404
+ _timersStopRun: function (event) {
405
+ var isHidden = this.hidden(),
406
+ hiddenBefore = this._hiddenBefore;
407
+
408
+ if ( (isHidden && !hiddenBefore) || (!isHidden && hiddenBefore) ) {
409
+ for ( var i in this._timers ) {
410
+ this._stopTimer(i);
411
+ this._runTimer(i, !isHidden);
412
+ }
413
+ }
414
+ }
385
415
 
386
416
  };
387
417
 
388
- Visibility._init();
418
+ for ( var prop in timers ) {
419
+ Visibility[prop] = timers[prop];
420
+ }
389
421
 
390
422
  })();
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Copyright 2012 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
3
+ * sponsored by Evil Martians.
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Lesser General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public License
16
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ ;(function () {
20
+ "use strict";
21
+
22
+ var defined = function(variable) {
23
+ return ('undefined' != typeof(variable));
24
+ };
25
+
26
+ var timers = {
27
+
28
+ // Run callback every `interval` milliseconds if page is visible and
29
+ // every `hiddenInterval` milliseconds if page is hidden.
30
+ //
31
+ // Visibility.every(60 * 1000, 5 * 60 * 1000, function () {
32
+ // checkNewMails();
33
+ // });
34
+ //
35
+ // You can skip `hiddenInterval` and callback will be called only if
36
+ // page is visible.
37
+ //
38
+ // Visibility.every(1000, function () {
39
+ // updateCountdown();
40
+ // });
41
+ //
42
+ // It is analog of `setInterval(callback, interval)` but use visibility
43
+ // state.
44
+ //
45
+ // It return timer ID, that you can use in `Visibility.stop(id)` to stop
46
+ // timer (`clearInterval` analog).
47
+ // Warning: timer ID is different from interval ID from `setInterval`,
48
+ // so don’t use it in `clearInterval`.
49
+ //
50
+ // On change state from hidden to visible timers will be execute.
51
+ //
52
+ // If you include jQuery Chrono plugin before Visibility.js, you could
53
+ // use Chrono’s syntax sugar in interval arguments:
54
+ //
55
+ // Visibility.every('second', function () {
56
+ // updateCountdown();
57
+ // });
58
+ // Visibility.every('1 minute', '5 minutes', function () {
59
+ // checkNewMails();
60
+ // });
61
+ every: function (interval, hiddenInterval, callback) {
62
+ this._initTimers();
63
+
64
+ if ( !defined(callback) ) {
65
+ callback = hiddenInterval;
66
+ hiddenInterval = null;
67
+ }
68
+ this._lastTimer += 1;
69
+ var number = this._lastTimer;
70
+ this._timers[number] = ({
71
+ interval: interval,
72
+ hiddenInterval: hiddenInterval,
73
+ callback: callback
74
+ });
75
+ this._runTimer(number, false);
76
+
77
+ if ( this.isSupported() ) {
78
+ this._setListener();
79
+ }
80
+ return number;
81
+ },
82
+
83
+ // Stop timer from `every` method by it ID (`every` method return it).
84
+ //
85
+ // slideshow = Visibility.every(5 * 1000, function () {
86
+ // changeSlide();
87
+ // });
88
+ // $('.stopSlideshow').click(function () {
89
+ // Visibility.stop(slideshow);
90
+ // });
91
+ stop: function(id) {
92
+ var timer = this._timers[id]
93
+ if ( !defined(timer) ) {
94
+ return false;
95
+ }
96
+ this._stopTimer(id);
97
+ delete this._timers[id];
98
+ return timer;
99
+ },
100
+
101
+ // Last timer number.
102
+ _lastTimer: -1,
103
+
104
+ // Callbacks and intervals added by `every` method.
105
+ _timers: { },
106
+
107
+ // Is setInterval method detected and listener is binded.
108
+ _timersInitialized: false,
109
+
110
+ // Initialize variables on page loading.
111
+ _initTimers: function () {
112
+ if ( this._timersInitialized ) {
113
+ return;
114
+ }
115
+ this._timersInitialized = true;
116
+
117
+ if ( defined(window.jQuery) && defined(jQuery.every) ) {
118
+ this._setInterval = this._chronoInterval;
119
+ } else {
120
+ this._setInterval = this._originalInterval;
121
+ }
122
+ this.change(function () {
123
+ Visibility._timersStopRun()
124
+ });
125
+ },
126
+
127
+ // Set interval directly by `setInterval` function without any syntax
128
+ // sugar.
129
+ _originalInterval: function (callback, interval) {
130
+ return setInterval(callback, interval);
131
+ },
132
+
133
+ // Set interval by jQuery Chrono plugin. Add syntax sugar to `interval`
134
+ // and `hiddenInterval` arguments, such as "1 second" and others.
135
+ //
136
+ // It will be automatically set to `_setInterval` on loading if
137
+ // you include jQuery Chrono plugin before Visibility.js.
138
+ _chronoInterval: function (callback, internal) {
139
+ return jQuery.every(internal, callback);
140
+ },
141
+
142
+ // Set interval by `setInterval`. Allow to change function for tests or
143
+ // syntax sugar in `interval` arguments.
144
+ //
145
+ // Function will be automatically set in `_init` method (which will be
146
+ // call on script loading). So you must include jQuery Chrono plugin
147
+ // before Visibility.js.
148
+ _setInterval: null,
149
+
150
+ // Try to run timer from every method by it’s ID. It will be use
151
+ // `interval` or `hiddenInterval` depending on visibility state.
152
+ // If page is hidden and `hiddenInterval` is null,
153
+ // it will not run timer.
154
+ //
155
+ // Argument `now` say, that timers must be execute now too.
156
+ _runTimer: function (id, now) {
157
+ var interval,
158
+ timer = this._timers[id];
159
+ if ( this.hidden() ) {
160
+ if ( null === timer.hiddenInterval ) {
161
+ return;
162
+ }
163
+ interval = timer.hiddenInterval;
164
+ } else {
165
+ interval = timer.interval;
166
+ }
167
+ if ( now ) {
168
+ timer.callback.call(window);
169
+ }
170
+ timer.id = this._setInterval(timer.callback, interval);
171
+ },
172
+
173
+ // Stop timer from `every` method by it’s ID.
174
+ _stopTimer: function (id) {
175
+ var timer = this._timers[id];
176
+ clearInterval(timer.id);
177
+ delete timer.id;
178
+ },
179
+
180
+ // Listener for `visibilitychange` event.
181
+ _timersStopRun: function (event) {
182
+ var isHidden = this.hidden(),
183
+ hiddenBefore = this._hiddenBefore;
184
+
185
+ if ( (isHidden && !hiddenBefore) || (!isHidden && hiddenBefore) ) {
186
+ for ( var i in this._timers ) {
187
+ this._stopTimer(i);
188
+ this._runTimer(i, !isHidden);
189
+ }
190
+ }
191
+ }
192
+
193
+ };
194
+
195
+ for ( var prop in timers ) {
196
+ Visibility[prop] = timers[prop];
197
+ }
198
+
199
+ })();
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: visibilityjs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: '0.5'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-22 00:00:00.000000000 Z
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sprockets
@@ -41,6 +41,8 @@ extra_rdoc_files:
41
41
  - ChangeLog
42
42
  files:
43
43
  - lib/assets/javascripts/visibility.js
44
+ - lib/assets/javascripts/visibility.core.js
45
+ - lib/assets/javascripts/visibility.timers.js
44
46
  - lib/assets/javascripts/visibility.fallback.js
45
47
  - lib/visibilityjs.rb
46
48
  - LICENSE