• Stars
    star
    3,770
  • Rank 11,671 (Top 0.3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 13 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Tabletop.js gives spreadsheets legs

Tabletop.js (gives spreadsheets legs)

Tabletop.js takes a Google Spreadsheet and makes it easily accessible through JavaScript. With zero dependencies! If you've ever wanted to get JSON from a Google Spreadsheet without jumping through a thousand hoops, welcome home.

Tabletop.js easily integrates Google Spreadsheets with templating systems and anything else that is hip and cool. It will also help you make new friends and play jazz piano.

Build Status

But wait, don't use Tabletop!!!!

Tabletop was created in about 1995, back when we had to remember to disable call waiting when dialing into Compuserve. Now that it's 2020, Google is shutting down the infrastructure that Tabletop relies on.

Big surprise, right? But luckily, it's pretty easy to switch to a more modern way of doing things!

Instead of Tabletop, we're going to use Papa Parse. You still need to share your spreadsheet and all of that (see below), but the changes are actually pretty tiny! I've added a file where you can see a full example of doing it, but the quick version is:

Old Tabletop style

function init() {
  Tabletop.init( { key: 'https://docs.google.com/spreadsheets/d/0AmYzu_s7QHsmdDNZUzRlYldnWTZCLXdrMXlYQzVxSFE/pubhtml',
                    callback: function(data, tabletop) { 
                      console.log(data)
                    },
                    simpleSheet: true } )
}
window.addEventListener('DOMContentLoaded', init)

Updated version

function init() {
          Papa.parse('https://docs.google.com/spreadsheets/d/e/2PACX-1vRB4E_6RnpLP1wWMjqcwsUvotNATB8Np3OntlXb7066ULcAHI9oqqRhucltFifPTYNd7DRNRE56oTdt/pub?output=csv', {
          download: true,
          header: true,
          complete: function(results) {
            var data = results.data
            console.log(data)
          }
        })
window.addEventListener('DOMContentLoaded', init)

One important note is this won't work with Publish to Web if you have an organizational account. Like if I use my @columbia.edu account Google pretends I'm a terrible criminal and won't give me access via Papa Parse.

This also won't work if you're opening up an html file on your computer like a Normal Person, it requires you to either put it on the internet or run a local server. There are worse things, I guess.

But hey, as of September 2020 it's this way or the highway!

Google broke some MORE stuff, so you just plan might not be able to do this any more. If it's a PRIVATE project, you can get an API key and jump through a few hoops. But beware that this exposes your API key to anyone visiting the site, which allows people to do whatever they want with your data and run up bills on your end, so you probably don't want this on something facing the world.

But let's pretend Tabletop isn't leaving

Like how easy?

Step One: make a Google Spreadsheet and "Publish to Web." You'll need to pick CSV to be forwards-compatible for when Google shuts down the old way of doing things.

Step Two: Write a page that invokes Tabletop with the published URL Google gives you.

function init() {
  Tabletop.init( { key: 'https://docs.google.com/spreadsheets/d/0AmYzu_s7QHsmdDNZUzRlYldnWTZCLXdrMXlYQzVxSFE/pubhtml',
                    callback: function(data, tabletop) { 
                      console.log(data)
                    },
                    simpleSheet: true } )
}
window.addEventListener('DOMContentLoaded', init)

Step Two, modern-er version: We've moved to the future (aka like a decade ago) by supporting promises.

function init() {
  Tabletop.init( {
    key: 'https://docs.google.com/spreadsheets/d/0AmYzu_s7QHsmdDNZUzRlYldnWTZCLXdrMXlYQzVxSFE/pubhtml',
    simpleSheet: true }
  ).then(function(data, tabletop) { 
    console.log(data)
  })
}
window.addEventListener('DOMContentLoaded', init)

Step Three: Enjoy your data!

[ { name: "Carrot", category: "Vegetable", healthiness: "Adequate" }, 
  { name: "Pork Shoulder", category: "Meat", healthiness: "Questionable" }, 
  { name: "Bubblegum", category: "Candy", healthiness: "Super High"} ]

Yes, it's that easy.

NOTE: If your share URL has a /d/e in it, try refreshing the page to see if it goes away. If it doesn't, try this.

Getting Started

1) Publishing your Google Sheet

The first step is to get your data out into a form Tabletop can digest

Make a Google Spreadsheet. Give it some column headers, give it some content.

Name            Category   Healthiness
Carrot          Vegetable  Adequate
Pork Shoulder   Meat       Questionable
Bubblegum       Candy      Super High

Now go up to the File menu and pick Publish to the web. Fiddle with the options, then click Start publishing. A URL will appear, something like https://docs.google.com/spreadsheets/d/e/2PACX-1vQ2qq5UByYNkhsujdrWlDXtpSUhh7ovl0Ak6pyY3sWZqEaWS2lJ0iuqcag8iDLsoTuZ4XTiaEBtbbi0/pubhtml .

IGNORE THIS URL! You used to be able to use it, you can't anymore (you still need to do this step, though).

Now that you've published your sheet, you now need to share it, too.

  1. Click the Share link in the upper right-hand corner
  2. Click the very pale Advanced button
  3. Change... access to "On - Anyone with a link"
  4. Make sure Access: Anyone says Can view, since you don't want strangers editing your data
  5. Click Save

Copy the Link to Share. Your URL should look something like https://docs.google.com/spreadsheets/d/1Io6W5XitNvifEXER9ECTsbHhAjXsQLq6VEz7kSPDPiQ/edit?usp=sharing. It should not have a /d/e in it.

2) Setting up Tabletop

Now you're going to feed your spreadsheet into Tabletop

Include the Tabletop JavaScript file in your HTML, then try the following, substituting your URL for publicSpreadsheetUrl

<script src='https://cdnjs.cloudflare.com/ajax/libs/tabletop.js/1.5.1/tabletop.min.js'></script>
<script type='text/javascript'>    
  var publicSpreadsheetUrl = 'https://docs.google.com/spreadsheets/d/1sbyMINQHPsJctjAtMW0lCfLrcpMqoGMOJj6AN-sNQrc/pubhtml';

  function init() {
    Tabletop.init( { key: publicSpreadsheetUrl,
                     callback: showInfo,
                     simpleSheet: true } )
  }

  function showInfo(data, tabletop) {
    alert('Successfully processed!')
    console.log(data);
  }

  window.addEventListener('DOMContentLoaded', init)
</script>

After Tabletop reads your Sheet, it hops to the showInfo function with your data. Open up your console and check out the data it retrieved. All of those rows were turned right into objects! See how easy that was?

3) Honestly, that's it.

Check out the reference and the examples, but basically you're set. The only thing to think about right now is if you want to deal with multiple sheets you can get rid of simpleSheet: true (more on that later).

You might also be interested in the publishing/republishing/publish-as-it-changes aspects of Google Spreadsheets, but you'll need to google that for any specifics.

A note on node

To use this in a node environment:

npm install tabletop -save

Copy and paste this in your index.js file:

 const Tabletop = require('tabletop');

 var publicSpreadsheetUrl = 'URL OF SPREADSHEET AS YOU FIND IN THE BROWSER ADDRESS BAR';

function init() {
    Tabletop.init( { key: publicSpreadsheetUrl,
                     callback: showInfo,
                     simpleSheet: false } )
  }

 function showInfo(data, tabletop) {
  // do something with the data
  console.log(JSON.stringify(data, null, 2));
}

//initialise and kickstart the whole thing.
init()

Reference

Tabletop initialization

The simplest Tabletop initialization works like this:

var tabletop = Tabletop.init({ 
  key: '1sbyMINQHPsJctjAtMW0lCfLrcpMqoGMOJj6AN-sNQrc', 
  callback: showInfo 
})

You pass in either key as the actual spreadsheet key, or just the full published-spreadsheet URL.

showInfo is a function elsewhere in your code that gets called with your data.

Depending on how recently you've published your spreadsheet, your key comes from different places. Either the spreadsheet's URL in the address bar, the Publish URL, or the Share URL. Read this

Tabletop initialization options

key

key is the key of the published spreadsheet or the URL of the published spreadsheet.

callback

callback is the callback for when the data has been successfully pulled. It will be passed an object containing the models found in the spreadsheet (worksheets => models), and the tabletop instance. Each of these models contains the rows on that worksheet (see Tabletop.Model). If simpleSheet is turned on it simply receives an array of rows of the first worksheet.

simpleSheet

simpleSheet can be true or false (default false). It assumes you have one table and you don't care what it's called, so it sends the callback an array of rows instead of a list of models. Peek at the examples for more info.

parseNumbers

parseNumbers can be true or false (default false). If true, Tabletop will automatically parse any numbers for you so they don't run around as strings.

error

error is the callback for when something goes wrong. I'm uncertain how well it works in the browser in all situations, but in our Modern World I'm pretty sure it can be relied on.

orderby

orderby asks Google to sort the results by a column. You'll need to strip spaces and lowercase your column names, i.e. {order: 'firstname'} for a column called First Name. You'll want to use this when you only have a single sheet, though, otherwise it will try to sort by the same column on every single sheet.

reverse

reverse reverses the order if set to true.

postProcess

postProcess is a function that processes each row after it has been created. Use this to rename columns, compute attributes, etc.

For example:

 postProcess: function(element) {
   // Combine first and last name into a new column
   element["full_name"] = element["first_name"] + " " + element["last_name"];
   
   // Convert string date into Date date
   element["timestamp"] = Date.parse(element["displaydate"]);
 } 

wanted

wanted is an array of sheets you'd like to pull. If you have 20 sheets in a public spreadsheet you might as well only pull what you need to access. See the example in simple/multiple.html. Defaults to all.

endpoint

endpoint is the protocol and domain you'd like to query for your spreadsheet. Defaults to https://spreadsheets.google.com.

singleton

singleton assigned the instantiated Tabletop object to Tabletop.singleton, implemented to simplify caching and proxying of requests. Defaults to false.

simple_url

simpleUrl, if true, changes all requests to KEY and KEY-SHEET_ID. Defaults to false.

proxy

proxy allows you to easily use spreadsheets not located on Google Spreadsheet servers. Setting proxy: "http://www.proxy.com" is equivalent to setting { simple_url: true, singleton: true, endpoint: "http://www.proxy.com" }. Flatware might provide better documentation.

wait

wait prevents tabletop from pulling the Google spreadsheet until you're ready. Used in the backbone.js example.

query

query sends a structured query along with the spreadsheet request, so you can ask for rows with age > 55 and the like. Right now it's passed with every request, though, so if you're using multiple tables you'll end up in Problem City. It should work great with simpleSheet situations, though. Doesn't want to work at the moment.

debug

debug returns on debug mode, which gives you plenty of messaging about what's going on under the hood.

authkey

authkey is the authorization key for private sheet support.

parameterize

parameterize changes the src of all injected scripts. Instead of src, src is URI encoded and appended to parameterize, e.g. set it to http://example.herokuapp.com/?url=. Mostly for gs-proxy.

callbackContext

callbackContext sets the this for your callback. It's the tabletop object by default.

prettyColumnNames

prettyColumnNames can be true or false (default to true, unless proxy is enabledโ€ ). Since Google doesn't pass us exactly the same column names as in the header ('$ Processed' becomes 'processed'), it takes an extra request to correct them. If you don't want the extra request, you'll want to set it to false

โ€  prettyColumnNames doesn't work with Flatware, is why we disable it with a proxy by default

Tabletop object attributes and methods

Once you're in the callback, you get the data and a tabletop object. That object is capable of all sorts of fun things.

.sheets()

.sheets() are the Tabletop.Models that were populated, one per worksheet. You access a sheet by its name.

.sheets(name) is how you access a specific sheet. Say I have a worksheet called Cats I Know, I'll access it via tabletop.sheets("Cats I Know")

.modelNames

.modelNames are the names of the models [read: sheets] that Tabletop knows about. The sheet names do not reflect their ordering in the original spreadsheet.

.foundSheetNames

.foundSheetNames are the names of the sheets [read: models] that Tabletop knows about. Their order reflects the sheets' order in the original spreadsheet.

.data()

.data() returns the rows of the first model if you're in simpleSheet mode. It's the same as .sheets() otherwise. This is just a little sugar.

.fetch()

.fetch() manually initializes a data request to the Google Sheet.

.addWanted(name)

.addWanted(name) adds a sheet to the list that are updated with .fetch

Tabletop.Model attributes and methods

Tabletop refers to sheets as Models, which have a few extra abilities compared to the sheets-as-plain-objects.

.name

.name is the name of the worksheet it came from (the tab at the bottom of the spreadsheet)

.columnNames

.columnNames gives you the names of the columns in that table

.originalColumns

.originalColumns gives you the names of the columns that Google sends on the first pass (numbers stripped, lowercase, etc)

.prettyColumns

.prettyColumns gives you the mapping between the column headers in the spreadsheet and the and the columnNames. Disabled by passing prettyColumnNames: false when initializing Tabletop.

.all()

.all() returns an array of the rows of the table, in the style of [ { name: "Tom", age: 5}, { name: "Liz", age: 12 } ]

.toArray()

.toArray() returns the rows of the table with numbered indices instead of named ones [ [ "Tom", 5] , [ "Liz", 12 ] ]

So what the hell do I do with this?

Imagine it's a read-only, JavaScript CMS that you can edit through Google Docs. It's like Christmas up in here.

You can see examples of working with different systems in, yes, /examples/.

Tabletop and any templating system (Handlebars etc)

Super easy. Just feed the models to your template and you're all set.

Tabletop and Backbone.js

I've put together a Backbone.tabletopSync driver for Backbone collections and models. It's read-only, but you can't really complain if you're powering your Backbone app through Google Spreadsheets.

Source is, of course, in /src, and you can check it out in action in examples/backbone/

Tabletop and AngularJS

ร†ndrew Rininsland (@aendrew) at The Times and Sunday Times has created a module that makes using Tabletop with AngularJS extremely easy. It also includes a loader for angular-translate that gives Tabletop the ability to provide i18n translation strings.

Please see times/angular-tabletop for more details.

Caching/Proxying Google Spreadsheets

Yeah, Google Spreadsheets can sometimes be slow or sometimes be overwhelmed or maybe one day Google will just up and disappear on us. So Tabletop.js now supports fetching your data from elsewhere, using options like endpoint and proxy.

proxy is the fun one, in that it rewrites your requests to be simpler-looking and plays nicely with the app & example I put together.

Using Flatware

If you don't mind running around with Heroku and AWS, Flatware is an app I built that uploads the spreadsheet JSON response to S3.

Using table-service

table-service hosts it on your own server using a python script, and auto-updates thanks to a tiny script you add to your spreadsheet.

Using gs-proxy

gs-proxy is another option that also uses Heroku. You'll set parameterize to something like http://example.herokuapp.com/?url= and off you go!

Using other caching

You can point proxy at anything you'd like as long as it has KEY and KEY-SHEET_ID files sitting in a directory. Feel free to host it on your own server! You can use /caching/local.rb if you want a pretty easy solution for generating the flat files.

Notes

Strange behavior

Empty tables are trouble. We can't get column names from them (c'mon, Google!), so don't be too confused when a table with 0 rows is coming back with an empty .column_names or your code starts throwing weird errors when processing the results.

Empty rows are trouble. If you have a row that's completely empty, Google doesn't return any rows after the empty row. As a result, you need to make sure every line in your spreadsheet has data in it.

Weird-named columns are trouble. A column named % might cause your spreadsheet to stop processing.

Unnamed columns are trouble. A column without a name will get in the way of your data successfully coming through.

If you are having trouble

Turn on debugging by passing debug: true when you initialize Tabletop. Check out the console, I try to keep my error messages chatty and informative. Or just email me at [email protected], I'm happy to help!

Tabletop.js in the wild

The more examples the better, right? Feel free to fork or contact me if you have a good example of something you've done.

A contextual video player with popcorn.js by @maboa

The WNYC mayoral tracker uses Tabletop along with Backbone.js

A Facebook-esque timeline from Balance Media (with a git repo)

Mapsheet creates super easy, customizable maps.

Other Options

If you aren't really feeling Tabletop, you should give Dataset a shot. It's "a JavaScript client-side data transformation and management library," which means it does a hell of a lot more than our dear Tabletop.

Credits

Jonathan Soma, who would rather be cooking than coding. Inspired by the relentless demands of John Keefe of WNYC.

Thanks to Scott Seaward for implementing multi-instance Tabletop.

Alan Palazzolo hooked the world up with gs-proxy and added support for it into Tabletop via parameterize

Max Ogden was kind enough to lend Tabletop nodejs support as part of an Open News code sprint

More Repositories

1

mapsheet

Easily create interactive maps from data in Google Spreadsheets. Supports Google Maps, Leaflet, Mapbox, MapQuest, etc.
JavaScript
224
star
2

selenium-github-actions

Python
94
star
3

flatware

S3 caching of Google Spreadsheets for Tabletop.js
Ruby
91
star
4

persine

Persine is an automated tool to study and reverse-engineer algorithmic recommendation systems.
Python
89
star
5

fuzzy_pandas

Fuzzy matches and merging of datasets in pandas using csvmatch
Python
71
star
6

kull

A tool to interactively select text regions of PDFs and images. Mostly for use with PDFQuery or tesseract (UZN/OCR zone files)
Vue
51
star
7

spotify-langchain-gpt

Building Spotify playlists based on vibes using LangChain and GPT
Jupyter Notebook
50
star
8

gs-spreadsheet-manager

A slightly more object-oriented, ORM-y, and altogether pleasant way of dealing with Google Spreadsheets when using Google Apps Script.
JavaScript
45
star
9

hexagon-grids-for-adobe-illustrator

A simple script to automatically build hexagon grids in Illustrator, for things like D3 hex cartogram visualizations
JavaScript
18
star
10

tesseract-uzn

Simplify using uzn files with tesseract for OCR
Jupyter Notebook
17
star
11

ai2html-walkthroughs

Code samples for ai2html walkthroughs
HTML
16
star
12

designingviz

A step-by-step design guide for data visualizations
JavaScript
15
star
13

storytelling-2015

HTML
15
star
14

NICAR20-geocoding-apis

Geocoding APIs repo for NICAR20 session
Jupyter Notebook
14
star
15

simplified-scrollama-scrollytelling

Non-d3, barebones version of scrollama.js scrollytelling examples
HTML
13
star
16

nicar23-huggingface

Jupyter Notebook
13
star
17

python-disease-modeling

A walkthrough of how SIR infectious disease modeling works, along with a do-it-yourself Python COVID model that you can use to simulate a lockdown.
Jupyter Notebook
13
star
18

data-studio-projects

12
star
19

autoscraper-changes

Tiny template that uses GitHub Actions and Python to automatically scrape the BBC's homepage every four hours.
Python
11
star
20

clipped-and-georeferenced-images-from-google-earth-in-qgis-3

Georeference images from Google Earth in QGIS 3, then clip them so they aren't shaped all weird.
11
star
21

d3-topojson-data-guide

How to wrangle your data into a useable TopoJSON format for making D3 visualizations
10
star
22

ire24-scraping

Tutorials for IRE 2024 about using Playwright to scrape state-level license and violations data
Jupyter Notebook
10
star
23

NICAR20-intro-d3

Base for an introductory walkthrough of D3's features for NICAR 2020
HTML
8
star
24

interactives-class-content

8
star
25

abraji24-ai-audio-video

HTML
8
star
26

nicar23-building-ai-editors

Conceptual framework for building AI editors to help with journalism workflows
Jupyter Notebook
8
star
27

hardboiled

A node.js detective kit for investigating how web sites are built.
JavaScript
8
star
28

mediaparty-folktales

Notes and code for learning Hungarian folktales through semantic search and GPT: Media Party 2023 lightning talk
Jupyter Notebook
8
star
29

nicar24-beyond-chatgpt

Jupyter Notebook
7
star
30

2023-abraji-ai-workshop

Journalism in AI workshop for Abraji 2023
6
star
31

lede-notes

Notes from Summer 2015's Lede Python class
6
star
32

playfair-projects

Common repository of projects for Playfair
Jupyter Notebook
6
star
33

chatgpt-for-educators

Slides for a talk about the use and abuse of ChatGPT in the classroom
HTML
6
star
34

data-studio-projects-2022

Projects for Columbia J School's Data Studio 2022
Python
6
star
35

nb2jekyll

Use nbconvert to convert IPython/Jupyter Notebooks into Jekyll-compatible Markdown + Front Matter
Python
5
star
36

NICAR20-apis

Jupyter Notebook
5
star
37

page-templates

HTML
5
star
38

final_final_v2_truefinal

JavaScript
4
star
39

quarto-tutorial

Quarto framework tutorial for C+J/European Data Journalism Conf 2023
HTML
4
star
40

how-to-host-on-github-pages

4
star
41

lede-projects-2023

Projects for Lede Program 2023
Python
3
star
42

data-studio-projects-2024

Python
3
star
43

excel-tutorials

3
star
44

lede-2023-auto-scraper

Jupyter Notebook
3
star
45

data-studio-projects-2023

Projects for Columbia Journalism School's Data Studio course, Spring 2023
Python
3
star
46

2023-ona-before-beyond-chatgpt

Slides and links for AI Tools from Before (and Beyond!) ChatGPT (ONA 2023)
3
star
47

lede-projects-2022

Projects for Lede Program 2022
Python
3
star
48

walkthrough-github

this is a walkthrough about how to use git and github desktop
Jupyter Notebook
3
star
49

bad-air-cities

An auto-updating website of major cities' air quality index
Jupyter Notebook
2
star
50

dotenv-example

Python
2
star
51

autoscraper-history

Python
2
star
52

auto-scraper

an automatic scraper?????
Python
2
star
53

automatic-scraper

Python
2
star
54

sample-d3-project

HTML
2
star
55

2024-abraji-ai-docs

Jupyter Notebook
2
star
56

2023-journalismai

Jupyter Notebook
2
star
57

example-svelte-viz-page

JavaScript
2
star
58

lede-websites

Thousands of websites for various classes I've taught over the years, primarily for Columbia's Lede Program
Jupyter Notebook
2
star
59

jupyter-notebook-site-autobuild

Lil' baby static site builder that creates a website from Jupyter notebooks and markdown files
CSS
2
star
60

data-studio

HTML
2
star
61

japanese-dialect-quiz

Japanese dialect quiz built using Claude artifacts
HTML
1
star
62

flask-energy

Jupyter Notebook
1
star
63

2024-ds-dojo

Jupyter Notebook
1
star
64

words

Jupyter Notebook
1
star
65

pandas-question-factory

a question factory. for pandas.
Python
1
star
66

foundations

Code for Lede Program 2017 Foundations class I guess????
1
star
67

slack-files

List your large slack files so you can search for and delete them
Ruby
1
star
68

normal-ai

HTML
1
star
69

bottlejack

JavaScript
1
star
70

automatic-scraper-bbc

Python
1
star
71

fanciframe

Let's embed iframes with grace and beauty
JavaScript
1
star
72

data-buddies

It's little datas!!!
1
star
73

track_url

A Sublime Text 2 plugin for following along with the ever-changing contents of a URL. I use it so students can follow along.
Python
1
star
74

typing-of-the-dead

Code to extract phrases from the impossilby incredible Dreamcast game The Typing of the Dead
Python
1
star
75

sample-story

HTML
1
star
76

charset-downloader

7-bit ASCII ought to be enough for ะฐะฟัƒะฑะพะดัƒ
HTML
1
star
77

advanced-scraping-with-playwright

Notes for the Advanced Scraping with Playwright session in February 2024
HTML
1
star
78

demodemo

JavaScript
1
star
79

fancy-github-guide

A guide to a fancy github experience https://jonathansoma.com/fancy-github/
1
star
80

miscellaneous-data

Miscellaneous and most probably useless data sets that I've used for this or that
1
star
81

autoscraper-mailer

Scrapes on a schedule, emailing out whatever it finds.
Python
1
star
82

tiny-spider

Convert spreadsheets into network-friendly formats, a.k.a. from ๐Ÿ“ˆ to ๐Ÿ•ธ, using ๐Ÿ•ท
JavaScript
1
star
83

altair-reference

HTML
1
star
84

transliteration_project

GLSL
1
star
85

2023-ona-trusting-ai

Slides and links for Trusting AI in the newsroom: Hallucinations, bias, security and labor (ONA 2023)
1
star
86

nyc-schools

Jupyter Notebook
1
star