PageSourceSearch

https://codelamp.co.uk/js/tab-visibility-change.js?build=BUILDTOKEN

js codelamp.co.uk collected 2026-10-04 13:34:37 UTC 9,841 bytes, 329 lines download raw bytes

1/**
2 * Tab Visibility Handling
3 *
4 * Keeps track of when the current document is hidden from view.
5 * 
6 */
7tabVisibilityChange = {
8
9  /*
10  EXAMPLE USAGE:
11  
12  tabVisibilityChange.on('change', function(e, tab){
13    console.log(tab.hidden);
14  })
15  
16  OR:
17  
18  tabVisibilityChange.create(document, {
19    change: function(e, tab){
20      console.log(tab.hidden);
21    },
22    visible: function(){
23      console.log('visibubble!');
24    },
25    hidden: function(){
26      console.log('hiddenaways');
27    }
28  });
29  
30  */
31  
32  window: window,
33  document: document,
34  jQuery: jQuery,
35  
36  //global: null,
37  /// exchange the above line for the commented one below
38  /// if you want a global event, 'tabvisibilitychange' to
39  /// be broadcast. Global can be set to any element or object
40  ///  but window is obviously the most accessible.
41  global: window,
42  
43  ns: 'tvc',
44  nsSeed: 1,
45  shared: null,
46  disabled: false,
47  delegate: null,
48  
49  /**
50   * A shared listener is created for each unique document
51   * that tabVisibilityChange is asked to manage.
52   */
53  sharedListener: {
54    
55    eventTarget: null,
56    eventNames: [],
57    lastHidden: null,
58    disabled: false,
59    delegate: null,
60    
61    /**
62     * standard create function, expects an instance of tabVisibilityChange
63     * as first argument.
64     */
65    create: function( tvc, options ){
66      console.log(tvc);
67      return this.prep.apply(Object.create(this), arguments);
68    },
69    
70    /**
71     * standard prep function, applies unique references to an otherwise
72     * cloned sharedListener.
73     */
74    prep: function( tvc, options ){
75      this.document = tvc.document;
76      this.window   = tvc.window;
77      this.jQuery   = tvc.jQuery;
78      this.global   = tvc.global;
79      this.eventTarget = this.document;
80      this.delegate = this.jQuery( {} );
81      /// create a global object, if requested
82      this.global = (this.global
83        ? this.jQuery(this.global)
84        : null
85      );
86      /// check for which events to use
87      switch ( true ) {
88        case 'hidden' in this.document:       this.eventNames.push('visibilitychange');       break;
89        case 'msHidden' in this.document:     this.eventNames.push('msvisibilitychange');     break;
90        case 'mozHidden' in this.document:    this.eventNames.push('mozvisibilitychange');    break;
91        case 'webkitHidden' in this.document: this.eventNames.push('webkitvisibilitychange'); break;
92        case 'onfocusin' in this.document:
93          this.eventNames.push('focusin');
94          this.eventNames.push('focusout');
95        break;
96        default:
97          this.eventTarget = this.window;
98          this.eventNames.push('pageshow');
99          this.eventNames.push('pagehide');
100        break;
101      }
102      /// proxy so that 'this' makes sense in listener()
103      this.listener = this.jQuery.proxy(this.listener, this);
104      /// go go go!
105      this.start();
106      return this;
107    },
108    
109  
110    /**
111     * The key/global listening behaviour
112     */
113    listener: function( e ){
114      if ( this.disabled ) {
115        return;
116      }
117      var hidden = null;
118      switch ( e.type ) {
119        case 'visibilitychange':       hidden = this.document.hidden;       break;
120        case 'mozvisibilitychange':    hidden = this.document.mozHidden;    break;
121        case 'webkitvisibilitychange': hidden = this.document.webkitHidden; break;
122        case 'msvisibilitychange':     hidden = this.document.msHidden;     break;
123        case 'focusin':  hidden = false; break;
124        case 'focusout': hidden = true;  break;
125        case 'pageshow': hidden = false; break;
126        case 'pagehide': hidden = true;  break;
127      }
128      if ( hidden !== null && hidden !== this.lastHidden ) {
129        this.global && this.global.triggerHandler('tabvisibilitychange', { hidden: hidden });
130        this.delegate.triggerHandler( hidden ? 'hidden' : 'visible' );
131        this.delegate.triggerHandler('change', { hidden: hidden });
132        this.lastHidden = hidden;
133      }
134    },
135    
136    /**
137     * Shared Listener management API
138     */
139    start: function(){
140      /// start listening
141      this.jQuery(this.eventTarget).on(this.eventNames.join(' '), this.listener);
142    },
143    stop: function(){
144      /// stop listening
145      this.jQuery(this.eventTarget).off(this.eventNames.join(' '), this.listener);
146    },
147    
148    /**
149     * Shared Listener existence API
150     */
151    getFor: function( tvc ){
152      return tvc.document['listeningForTVC'];
153    },
154    existsFor: function( tvc ){
155      return tvc.document && 'listeningForTVC' in tvc.document;
156    },
157    createFor: function( tvc ){
158      return (tvc.document['listeningForTVC'] = this.create( tvc ));
159    },
160    
161    /**
162     * Shared Listener events API
163     */
164    on: function( name, callback ){
165      if ( !callback ) { callback=name; name=null; };
166      if ( !callback ) return;
167      if ( name && callback && callback.call ) {
168        this.delegate.on(name, callback);
169      }
170      else {
171        this.delegate.on(callback);
172      }
173    },
174    off: function( name, callback ){
175      if ( !callback ) { callback=name; name=null; };
176      if ( !callback ) return;
177      if ( name && callback && callback.call ) {
178        this.delegate.off(name, callback);
179      }
180      else {
181        this.delegate.off(callback);
182      }
183    }
184    
185  },
186  
187  /**
188   * Create an individual instance of tabVisibilityChange
189   */
190  create: function( element, options ){
191    return this.prep.apply(Object.create(this), arguments);
192  },
193  
194  /**
195   * Prep what makes this instance unique, in terms of references.
196   *
197   * element -- either a window, document or other element that 
198   *            references the "tab" you wish to watch over
199   *
200   * options -- futher config options
201   *
202   */
203  prep: function( element, options ){
204    /// force instance creation so this can't been called statically
205    if ( this === tabVisibilityChange ) {
206      return this.create.apply(this, arguments);
207    }
208    /// if we are a usual instance i.e. .create() has been called.
209    else {
210      this.ns = tabVisibilityChange.ns + (this.nsSeed++);
211      /// create a delegate for managing events
212      this.delegate = this.jQuery( {} );
213      /// configure this based on element
214      this.configure( element || document );
215      /// make sure our listener redirect knows that 'this' should be
216      this.sharedToLocalListener = this.jQuery.proxy(this.sharedToLocalListener, this);
217      /// make sure we have a shared listener based on document
218      this.shared = (this.sharedListener.existsFor( this )
219        ? this.sharedListener.getFor( this )
220        : this.sharedListener.createFor( this )
221      );
222      /// as long as we have a shared listener, we can continue
223      if ( this.shared ) {
224        /// we have a shared to local delegate step to allow for easy .off
225        /// behaviour, without the external user needing to know about
226        /// namespacing of events. Gives a slight overhead, but worth it
227        /// for a cleaner API.
228        this.delegateEar = {};
229        this.delegateEar['visible' + '.' + this.ns] = this.sharedToLocalListener;
230        this.delegateEar['hidden'  + '.' + this.ns] = this.sharedToLocalListener;
231        this.delegateEar['change'  + '.' + this.ns] = this.sharedToLocalListener;
232        this.shared.on( this.delegateEar );
233        /// check if we need our own local listener
234        if ( options ) {
235          /// only bother calling this if we have listeners as options
236          if ( options.change || options.visible || options.hidden ) {
237            /// pass our listener options to the shared instance.
238            this.delegate.on(options);
239          }
240        }
241      }
242      return this;
243    }
244  },
245  
246  /**
247   * Configure other elements based on a root
248   */
249  configure: function( element ){
250    if ( element === null ) {
251      element = options;
252    }
253    if ( element ) {
254      /// have we been passed a document object?
255      if ( element.doctype ) {
256        this.document = element;
257        this.window = this.document.defaultView;
258      }
259      /// have we been passed a window object?
260      else if ( element.open && element.location ) {
261        this.window = element;
262        this.document = this.window.document;
263      }
264      /// have we been passed a normal element?
265      else if ( element.nodeName ) {
266        this.document = element.document;
267        this.window = this.document && this.document.defaultView;
268      }
269      else {
270        if ( element.document && element.window ) {
271          this.window = element.window;
272          this.document = element.document;
273        }
274        else if ( element.document ) {
275          this.document = element.document;
276          this.window = this.document.defaultView;
277        }
278        else if ( element.window ) {
279          this.window = element.window;
280          this.document = this.window.document;
281        }
282      }
283    }
284  },
285  
286  /**
287   * Internal function for redirecting events from shared
288   * to the local delegate, and listening to 'disabled'.
289   */
290  sharedToLocalListener: function(e, tab){
291    if ( this.disabled ) {
292      return;
293    }
294    this.delegate.triggerHandler(e.type, tab);
295  },
296  
297  /**
298   * TVC Events API
299   */
300  on: function( name, callback ){
301    if ( this.delegate ) {
302      this.delegate.on.apply(this.delegate, arguments);
303    }
304    else {
305      return this.on.apply(this.create(document), arguments);
306    }
307    return this;
308  },
309  off: function( name, callback ){
310    if ( this.delegate ) {
311      this.delegate.off.apply(this.delegate, arguments);
312    }
313    else {
314      return this.off.apply(this.create(document), arguments);
315    }
316    return this;
317  },
318  
319  /**
320   * TVC Simple managment API, enable(), disable(), start() and stop()
321   */
322  enable: function(){
323    this.disabled = false;
324  },
325  disable: function(){
326    this.disabled = true;
327  }
328  
329};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.