PageSourceSearch

https://mobilizing-our-air.firebaseapp.com/js/tabletop1.3.4.js

js mobilizing-our-air.firebaseapp.com collected 2026-10-03 14:06:48 UTC 15,116 bytes, 474 lines download raw bytes

1(function(global) {
2  "use strict";
3
4  var inNodeJS = false;
5  if (typeof module !== 'undefined' && module.exports) {
6    inNodeJS = true;
7    var request = require('request');
8  }
9
10  var supportsCORS = false;
11  var inLegacyIE = false;
12  try {
13    var testXHR = new XMLHttpRequest();
14    if (typeof testXHR.withCredentials !== 'undefined') {
15      supportsCORS = true;
16    } else {
17      if ("XDomainRequest" in window) {
18        supportsCORS = true;
19        inLegacyIE = true;
20      }
21    }
22  } catch (e) { }
23
24  // Create a simple indexOf function for support
25  // of older browsers.  Uses native indexOf if
26  // available.  Code similar to underscores.
27  // By making a separate function, instead of adding
28  // to the prototype, we will not break bad for loops
29  // in older browsers
30  var indexOfProto = Array.prototype.indexOf;
31  var ttIndexOf = function(array, item) {
32    var i = 0, l = array.length;
33
34    if (indexOfProto && array.indexOf === indexOfProto) return array.indexOf(item);
35    for (; i < l; i++) if (array[i] === item) return i;
36    return -1;
37  };
38
39  /*
40    Initialize with Tabletop.init( { key: '0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc' } )
41      OR!
42    Initialize with Tabletop.init( { key: 'https://docs.google.com/spreadsheet/pub?hl=en_US&hl=en_US&key=0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc&output=html&widget=true' } )
43      OR!
44    Initialize with Tabletop.init('0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc')
45  */
46
47  var Tabletop = function(options) {
48    // Make sure Tabletop is being used as a constructor no matter what.
49    if(!this || !(this instanceof Tabletop)) {
50      return new Tabletop(options);
51    }
52
53    if(typeof(options) === 'string') {
54      options = { key : options };
55    }
56
57    this.callback = options.callback;
58    this.wanted = options.wanted || [];
59    this.key = options.key;
60    this.simpleSheet = !!options.simpleSheet;
61    this.parseNumbers = !!options.parseNumbers;
62    this.wait = !!options.wait;
63    this.reverse = !!options.reverse;
64    this.postProcess = options.postProcess;
65    this.debug = !!options.debug;
66    this.query = options.query || '';
67    this.orderby = options.orderby;
68    this.endpoint = options.endpoint || "https://spreadsheets.google.com";
69    this.singleton = !!options.singleton;
70    this.simple_url = !!options.simple_url;
71    this.callbackContext = options.callbackContext;
72
73    if(typeof(options.proxy) !== 'undefined') {
74      // Remove trailing slash, it will break the app
75      this.endpoint = options.proxy.replace(/\/$/,'');
76      this.simple_url = true;
77      this.singleton = true;
78      // Let's only use CORS (straight JSON request) when
79      // fetching straight from Google
80      supportsCORS = false
81    }
82
83    this.parameterize = options.parameterize || false;
84
85    if(this.singleton) {
86      if(typeof(Tabletop.singleton) !== 'undefined') {
87        this.log("WARNING! Tabletop singleton already defined");
88      }
89      Tabletop.singleton = this;
90    }
91
92    /* Be friendly about what you accept */
93    if(/key=/.test(this.key)) {
94      this.log("You passed an old Google Docs url as the key! Attempting to parse.");
95      this.key = this.key.match("key=(.*?)&")[1];
96    }
97
98    if(/pubhtml/.test(this.key)) {
99      this.log("You passed a new Google Spreadsheets url as the key! Attempting to parse.");
100      this.key = this.key.match("d\\/(.*?)\\/pubhtml")[1];
101    }
102
103    if(!this.key) {
104      this.log("You need to pass Tabletop a key!");
105      return;
106    }
107
108    this.log("Initializing with key " + this.key);
109
110    this.models = {};
111    this.model_names = [];
112
113    this.base_json_path = "/feeds/worksheets/" + this.key + "/public/basic?alt=";
114
115    if (inNodeJS || supportsCORS) {
116      this.base_json_path += 'json';
117    } else {
118      this.base_json_path += 'json-in-script';
119    }
120
121    if(!this.wait) {
122      this.fetch();
123    }
124  };
125
126  // A global storage for callbacks.
127  Tabletop.callbacks = {};
128
129  // Backwards compatibility.
130  Tabletop.init = function(options) {
131    return new Tabletop(options);
132  };
133
134  Tabletop.sheets = function() {
135    this.log("Times have changed! You'll want to use var tabletop = Tabletop.init(...); tabletop.sheets(...); instead of Tabletop.sheets(...)");
136  };
137
138  Tabletop.prototype = {
139
140    fetch: function(callback) {
141      if(typeof(callback) !== "undefined") {
142        this.callback = callback;
143      }
144      this.requestData(this.base_json_path, this.loadSheets);
145    },
146
147    /*
148      This will call the environment appropriate request method.
149
150      In browser it will use JSON-P, in node it will use request()
151    */
152    requestData: function(path, callback) {
153      if (inNodeJS) {
154        this.serverSideFetch(path, callback);
155      } else {
156        //CORS only works in IE8/9 across the same protocol
157        //You must have your server on HTTPS to talk to Google, or it'll fall back on injection
158        var protocol = this.endpoint.split("//").shift() || "http";
159        if (supportsCORS && (!inLegacyIE || protocol === location.protocol)) {
160          this.xhrFetch(path, callback);
161        } else {
162          this.injectScript(path, callback);
163        }
164      }
165    },
166
167    /*
168      Use Cross-Origin XMLHttpRequest to get the data in browsers that support it.
169    */
170    xhrFetch: function(path, callback) {
171      //support IE8's separate cross-domain object
172      var xhr = inLegacyIE ? new XDomainRequest() : new XMLHttpRequest();
173      xhr.open("GET", this.endpoint + path);
174      var self = this;
175      xhr.onload = function() {
176        try {
177          var json = JSON.parse(xhr.responseText);
178        } catch (e) {
179          console.error(e);
180        }
181        callback.call(self, json);
182      };
183      xhr.send();
184    },
185
186    /*
187      Insert the URL into the page as a script tag. Once it's loaded the spreadsheet data
188      it triggers the callback. This helps you avoid cross-domain errors
189      http://code.google.com/apis/gdata/samples/spreadsheet_sample.html
190
191      Let's be plain-Jane and not use jQuery or anything.
192    */
193    injectScript: function(path, callback) {
194      var script = document.createElement('script');
195      var callbackName;
196
197      if(this.singleton) {
198        if(callback === this.loadSheets) {
199          callbackName = 'Tabletop.singleton.loadSheets';
200        } else if (callback === this.loadSheet) {
201          callbackName = 'Tabletop.singleton.loadSheet';
202        }
203      } else {
204        var self = this;
205        callbackName = 'tt' + (+new Date()) + (Math.floor(Math.random()*100000));
206        // Create a temp callback which will get removed once it has executed,
207        // this allows multiple instances of Tabletop to coexist.
208        Tabletop.callbacks[ callbackName ] = function () {
209          var args = Array.prototype.slice.call( arguments, 0 );
210          callback.apply(self, args);
211          script.parentNode.removeChild(script);
212          delete Tabletop.callbacks[callbackName];
213        };
214        callbackName = 'Tabletop.callbacks.' + callbackName;
215      }
216
217      var url = path + "&callback=" + callbackName;
218
219      if(this.simple_url) {
220        // We've gone down a rabbit hole of passing injectScript the path, so let's
221        // just pull the sheet_id out of the path like the least efficient worker bees
222        if(path.indexOf("/list/") !== -1) {
223          script.src = this.endpoint + "/" + this.key + "-" + path.split("/")[4];
224        } else {
225          script.src = this.endpoint + "/" + this.key;
226        }
227      } else {
228        script.src = this.endpoint + url;
229      }
230
231      if (this.parameterize) {
232        script.src = this.parameterize + encodeURIComponent(script.src);
233      }
234
235      document.getElementsByTagName('script')[0].parentNode.appendChild(script);
236    },
237
238    /*
239      This will only run if tabletop is being run in node.js
240    */
241    serverSideFetch: function(path, callback) {
242      var self = this
243      request({url: this.endpoint + path, json: true}, function(err, resp, body) {
244        if (err) {
245          return console.error(err);
246        }
247        callback.call(self, body);
248      });
249    },
250
251    /*
252      Is this a sheet you want to pull?
253      If { wanted: ["Sheet1"] } has been specified, only Sheet1 is imported
254      Pulls all sheets if none are specified
255    */
256    isWanted: function(sheetName) {
257      if(this.wanted.length === 0) {
258        return true;
259      } else {
260        return (ttIndexOf(this.wanted, sheetName) !== -1);
261      }
262    },
263
264    /*
265      What gets send to the callback
266      if simpleSheet === true, then don't return an array of Tabletop.this.models,
267      only return the first one's elements
268    */
269    data: function() {
270      // If the instance is being queried before the data's been fetched
271      // then return undefined.
272      if(this.model_names.length === 0) {
273        return undefined;
274      }
275      if(this.simpleSheet) {
276        if(this.model_names.length > 1 && this.debug) {
277          this.log("WARNING You have more than one sheet but are using simple sheet mode! Don't blame me when something goes wrong.");
278        }
279        return this.models[ this.model_names[0] ].all();
280      } else {
281        return this.models;
282      }
283    },
284
285    /*
286      Add another sheet to the wanted list
287    */
288    addWanted: function(sheet) {
289      if(ttIndexOf(this.wanted, sheet) === -1) {
290        this.wanted.push(sheet);
291      }
292    },
293
294    /*
295      Load all worksheets of the spreadsheet, turning each into a Tabletop Model.
296      Need to use injectScript because the worksheet view that you're working from
297      doesn't actually include the data. The list-based feed (/feeds/list/key..) does, though.
298      Calls back to loadSheet in order to get the real work done.
299
300      Used as a callback for the worksheet-based JSON
301    */
302    loadSheets: function(data) {
303      var i, ilen;
304      var toLoad = [];
305      this.foundSheetNames = [];
306
307      for(i = 0, ilen = data.feed.entry.length; i < ilen ; i++) {
308        this.foundSheetNames.push(data.feed.entry[i].title.$t);
309        // Only pull in desired sheets to reduce loading
310        if( this.isWanted(data.feed.entry[i].content.$t) ) {
311          var linkIdx = data.feed.entry[i].link.length-1;
312          var sheet_id = data.feed.entry[i].link[linkIdx].href.split('/').pop();
313          var json_path = "/feeds/list/" + this.key + "/" + sheet_id + "/public/values?alt="
314          if (inNodeJS || supportsCORS) {
315            json_path += 'json';
316          } else {
317            json_path += 'json-in-script';
318          }
319          if(this.query) {
320            json_path += "&sq=" + this.query;
321          }
322          if(this.orderby) {
323            json_path += "&orderby=column:" + this.orderby.toLowerCase();
324          }
325          if(this.reverse) {
326            json_path += "&reverse=true";
327          }
328          toLoad.push(json_path);
329        }
330      }
331
332      this.sheetsToLoad = toLoad.length;
333      for(i = 0, ilen = toLoad.length; i < ilen; i++) {
334        this.requestData(toLoad[i], this.loadSheet);
335      }
336    },
337
338    /*
339      Access layer for the this.models
340      .sheets() gets you all of the sheets
341      .sheets('Sheet1') gets you the sheet named Sheet1
342    */
343    sheets: function(sheetName) {
344      if(typeof sheetName === "undefined") {
345        return this.models;
346      } else {
347        if(typeof(this.models[ sheetName ]) === "undefined") {
348          // alert( "Can't find " + sheetName );
349          return;
350        } else {
351          return this.models[ sheetName ];
352        }
353      }
354    },
355
356    /*
357      Parse a single list-based worksheet, turning it into a Tabletop Model
358
359      Used as a callback for the list-based JSON
360    */
361    loadSheet: function(data) {
362      var model = new Tabletop.Model( { data: data,
363                                    parseNumbers: this.parseNumbers,
364                                    postProcess: this.postProcess,
365                                    tabletop: this } );
366      this.models[ model.name ] = model;
367      if(ttIndexOf(this.model_names, model.name) === -1) {
368        this.model_names.push(model.name);
369      }
370      this.sheetsToLoad--;
371      if(this.sheetsToLoad === 0)
372        this.doCallback();
373    },
374
375    /*
376      Execute the callback upon loading! Rely on this.data() because you might
377        only request certain pieces of data (i.e. simpleSheet mode)
378      Tests this.sheetsToLoad just in case a race condition happens to show up
379    */
380    doCallback: function() {
381      if(this.sheetsToLoad === 0) {
382        this.callback.apply(this.callbackContext || this, [this.data(), this]);
383      }
384    },
385
386    log: function(msg) {
387      if(this.debug) {
388        if(typeof console !== "undefined" && typeof console.log !== "undefined") {
389          Function.prototype.apply.apply(console.log, [console, arguments]);
390        }
391      }
392    }
393
394  };
395
396  /*
397    Tabletop.Model stores the attribute names and parses the worksheet data
398      to turn it into something worthwhile
399
400    Options should be in the format { data: XXX }, with XXX being the list-based worksheet
401  */
402  Tabletop.Model = function(options) {
403    var i, j, ilen, jlen;
404    this.column_names = [];
405    this.name = options.data.feed.title.$t;
406    this.elements = [];
407    this.raw = options.data; // A copy of the sheet's raw data, for accessing minutiae
408
409    if(typeof(options.data.feed.entry) === 'undefined') {
410      options.tabletop.log("Missing data for " + this.name + ", make sure you didn't forget column headers");
411      this.elements = [];
412      return;
413    }
414
415    for(var key in options.data.feed.entry[0]){
416      if(/^gsx/.test(key))
417        this.column_names.push( key.replace("gsx$","") );
418    }
419
420    for(i = 0, ilen =  options.data.feed.entry.length ; i < ilen; i++) {
421      var source = options.data.feed.entry[i];
422      var element = {};
423      for(var j = 0, jlen = this.column_names.length; j < jlen ; j++) {
424        var cell = source[ "gsx$" + this.column_names[j] ];
425        if (typeof(cell) !== 'undefined') {
426          if(options.parseNumbers && cell.$t !== '' && !isNaN(cell.$t))
427            element[ this.column_names[j] ] = +cell.$t;
428          else
429            element[ this.column_names[j] ] = cell.$t;
430        } else {
431            element[ this.column_names[j] ] = '';
432        }
433      }
434      if(element.rowNumber === undefined)
435        element.rowNumber = i + 1;
436      if( options.postProcess )
437        options.postProcess(element);
438      this.elements.push(element);
439    }
440
441  };
442
443  Tabletop.Model.prototype = {
444    /*
445      Returns all of the elements (rows) of the worksheet as objects
446    */
447    all: function() {
448      return this.elements;
449    },
450
451    /*
452      Return the elements as an array of arrays, instead of an array of objects
453    */
454    toArray: function() {
455      var array = [],
456          i, j, ilen, jlen;
457      for(i = 0, ilen = this.elements.length; i < ilen; i++) {
458        var row = [];
459        for(j = 0, jlen = this.column_names.length; j < jlen ; j++) {
460          row.push( this.elements[i][ this.column_names[j] ] );
461        }
462        array.push(row);
463      }
464      return array;
465    }
466  };
467
468  if(inNodeJS) {
469    module.exports = Tabletop;
470  } else {
471    global.Tabletop = Tabletop;
472  }
473
474})(this);

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.