├── .eleventy.js ├── .gitignore ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json └── src ├── site ├── _data │ ├── dev │ │ └── .keep │ └── prod │ │ └── sheet.js ├── _includes │ ├── css │ │ └── main.css │ ├── header.njk │ └── layouts │ │ └── base.njk └── index.md └── utils ├── minify-css.js ├── minify-html.js └── save-seed.js /.eleventy.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(config) { 3 | 4 | // A useful way to reference the context we are runing eleventy in 5 | let env = process.env.ELEVENTY_ENV; 6 | 7 | // Layout aliases can make templates more portable 8 | config.addLayoutAlias('default', 'layouts/base.njk'); 9 | 10 | // minify the html output 11 | config.addTransform("htmlmin", require("./src/utils/minify-html.js")); 12 | 13 | // use a filter for simple css minification 14 | config.addFilter("cssmin", require("./src/utils/minify-css.js")) 15 | 16 | 17 | // make the seed target act like prod 18 | env = (env=="seed") ? "prod" : env; 19 | return { 20 | dir: { 21 | input: "src/site", 22 | output: "dist", 23 | data: `_data/${env}` 24 | }, 25 | templateFormats : ["njk", "md"], 26 | htmlTemplateEngine : "njk", 27 | markdownTemplateEngine : "njk" 28 | }; 29 | 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies installed by npm or yarn 2 | /node_modules 3 | 4 | # build artefacts 5 | /dist 6 | /src/site/_includes/css 7 | /src/site/_data/dev/*.json 8 | 9 | # secrets and errors 10 | .env 11 | /yarn-error.log 12 | 13 | # macOS related files 14 | .DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Netlify Status](https://api.netlify.com/api/v1/badges/acfc2936-e6da-4242-88a5-ef27b3765059/deploy-status)](https://app.netlify.com/sites/read-from-google-sheets/deploys) 2 | 3 | # Read from Google Sheets API with Eleventy 4 | 5 | > Example site: https://read-from-google-sheets.netlify.com/ 6 | 7 | This site is an example of using [Eleventy's JavaScript Data Files](https://www.11ty.io/docs/data-js/) which simplify sourcing content from remote data sources and making them available as objects to use globally across an Eleventy site. 8 | 9 | The site is pulling content from a read-only Google Sheets feed at build time. 10 | 11 | 12 | ## Instructions 13 | 14 | To get your own instance of this 11ty example project cloned and deploying to Netlify very quickly, just click the button below and follow the instructions. 15 | 16 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/philhawksworth/example-read-from-sheets) 17 | 18 | 19 | ## Wait, what happens when I click that button? 20 | 21 | Good question. Here's what it will do... 22 | 23 | 1. Netlify will clone the git repository of this project into your Github account. It will be asking for permission to add the repo for you. 24 | 2. We'll create a new site for you in Netlify, and configure it to use your shiny new repo. Right away you'll be able to deploy changes simply by pushing changes to your repo. 25 | 3. That's it really. 26 | 27 | ## Changing the data source 28 | 29 | There are a couple of steps required to expose the content from your Google Sheet as a JSON API. 30 | 31 | 1. Create a google sheet and format it with column names in the first row. The JSON feed generated will use these column names. 32 | 2. You'll use the unique ID of the sheet from its URL to address it later. 33 | - For example: `https://docs.google.com/spreadsheets/d/1CfI6XGm9OjjNKGr3kXRSKVLui_gkHZdadoOPIiNgE9s/edit#gid=0` 34 | - Where: `https://docs.google.com/spreadsheets/d/{SHEET_ID}/edit#gid=0` 35 | 3. After creating you sheet, you need to publish it to the web. Do this from the **File menu in Google Sheets. Select the option to publish the sheet as a web page. 36 | 4. Check that you can now access the JSON feed of the sheet, using your Sheet ID in a URL using this convention: 37 | - `https://spreadsheets.google.com/feeds/list/{SHEET_ID}/od6/public/values?alt=json`; 38 | 5. Specify the Sheet ID to be used when constructing the request URL in [`/src/site/_data/prod/sheet.js`](/src/site/_data/prod/sheet.js#L8) 39 | 40 | ## Local development 41 | 42 | ### Prerequisites 43 | 44 | - [Node and NPM](https://nodejs.org/) 45 | 46 | 47 | ## Running locally 48 | 49 | ```bash 50 | # install the dependencies 51 | npm install 52 | 53 | # External data sources can be stashed locally for API-less dev 54 | npm run seed 55 | 56 | # It will then be available locally for building with: 57 | npm run start 58 | ``` 59 | 60 | 61 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "dist" 4 | NODE_ENV = "10.15.3" 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-example-google-sheets-data", 3 | "version": "0.0.1", 4 | "description": "An example of sourcing data from Google Sheets for use in Eleventy at build time", 5 | "author": "Phil Hawksworth", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "npm run dev", 9 | "dev": "cross-env ELEVENTY_ENV=dev eleventy --serve", 10 | "build": "cross-env ELEVENTY_ENV=prod eleventy", 11 | "seed": "cross-env ELEVENTY_ENV=seed eleventy" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/philhawksworth/example-read-from-sheets" 16 | }, 17 | "dependencies": { 18 | "@11ty/eleventy": "^0.8.3", 19 | "autoprefixer": "^9.6.0", 20 | "axios": "^0.19.0", 21 | "cssnano": "^4.1.10", 22 | "html-minifier": "^4.0.0" 23 | }, 24 | "devDependencies": { 25 | "cross-env": "^5.2.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/site/_data/dev/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philhawksworth/example-read-from-sheets/45e9ed2135fe009190035c4ac567e64785900fe4/src/site/_data/dev/.keep -------------------------------------------------------------------------------- /src/site/_data/prod/sheet.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const seed = require('../../../utils/save-seed.js'); 3 | 4 | 5 | // Once a googel sheet is "published to the web" we can access its JSON 6 | // via a URL of this form. We just need to pass in the ID of the sheet 7 | // which we can find in the URL of the document. 8 | const sheetID = "1CfI6XGm9OjjNKGr3kXRSKVLui_gkHZdadoOPIiNgE9s"; 9 | const googleSheetUrl = `https://spreadsheets.google.com/feeds/list/${sheetID}/od6/public/values?alt=json`; 10 | 11 | module.exports = () => { 12 | return new Promise((resolve, reject) => { 13 | 14 | console.log(`Requesting data from ${googleSheetUrl}`); 15 | 16 | axios.get(googleSheetUrl) 17 | .then(response => { 18 | 19 | // massage the data from the Google Sheets API into 20 | // a shape that will more convenient for us in our SSG. 21 | var data = { 22 | "East": [], 23 | "West": [] 24 | }; 25 | response.data.feed.entry.forEach(item => { 26 | data[item.gsx$conference.$t].push({ 27 | "name": item.gsx$name.$t, 28 | "team": item.gsx$team.$t 29 | }) 30 | }); 31 | 32 | // stash the data locally for developing without 33 | // needing to hit the API each time. 34 | seed(JSON.stringify(data), `${__dirname}/../dev/sheet.json`); 35 | 36 | // resolve the promise and return the data 37 | resolve(data); 38 | 39 | }) 40 | 41 | // uh-oh. Handle any errrors we might encounter 42 | .catch(error => { 43 | console.log('Error :', error); 44 | reject(error); 45 | }); 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/site/_includes/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | text-align: center; 8 | background-color: #fff; 9 | color: #333; 10 | font-family: sans-serif; 11 | font-weight: 300; 12 | font-size: 16px; 13 | line-height: 1.8; 14 | min-height: 100vh; 15 | } 16 | @media (min-width: 740px) { 17 | body { 18 | font-size: 18px; 19 | } 20 | } 21 | .container { 22 | margin-left: auto; 23 | margin-right: auto; 24 | width: 90%; 25 | text-align: left; 26 | } 27 | @media (min-width: 740px) { 28 | .container { 29 | width: 600px; 30 | } 31 | } 32 | @media (min-width: 1200px) { 33 | .container { 34 | width: 700px; 35 | } 36 | } 37 | 38 | header { 39 | margin-bottom: 3em; 40 | } 41 | 42 | h1, 43 | h2, 44 | h3 { 45 | color: #000; 46 | font-weight: 600; 47 | margin-top: 2em; 48 | margin-bottom: 1em; 49 | line-height: 1; 50 | } 51 | h1 { 52 | font-size: 3em; 53 | margin-top: 0.4em; 54 | margin-bottom: 0.2em; 55 | line-height: 1; 56 | } 57 | 58 | p { 59 | margin-top: 1em; 60 | margin-bottom: 1em; 61 | } 62 | .subtitle { 63 | font-size: 1.2em; 64 | margin-top: 1em; 65 | } 66 | 67 | ul, 68 | ol { 69 | padding-left: 1em; 70 | } 71 | 72 | a:link, 73 | a:visited { 74 | color: #ff8c00; 75 | text-decoration: none; 76 | border-bottom: solid 1px #ffdcb2; 77 | white-space: nowrap; 78 | } 79 | a:hover, 80 | a:focus { 81 | color: #ff8c00; 82 | border-bottom: solid 1px #ff8c00; 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/site/_includes/header.njk: -------------------------------------------------------------------------------- 1 |
2 | {%- if title %}

{{ title }}

{% endif %} 3 | {%- if subtitle %}

{{ subtitle | safe }}

{% endif %} 4 |
5 | -------------------------------------------------------------------------------- /src/site/_includes/layouts/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% set css %} 7 | {% include "css/main.css" %} 8 | {% endset %} 9 | 12 | {{ title }} 13 | 14 | 15 |
16 | {% include "header.njk" %} 17 | {{ content | safe }} 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/site/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Google Sheets data feed example. With Eleventy. 3 | layout: default 4 | --- 5 | 6 | 7 | ## Content from an external data source 8 | 9 | The lists below showing NBA AllStar starting fives for 2019 were sourced from [a Google Sheet](https://docs.google.com/spreadsheets/d/1CfI6XGm9OjjNKGr3kXRSKVLui_gkHZdadoOPIiNgE9s/edit#gid=0) as [JSON](https://spreadsheets.google.com/feeds/list/1CfI6XGm9OjjNKGr3kXRSKVLui_gkHZdadoOPIiNgE9s/od6/public/values?alt=json) at site build time. 10 | 11 | 12 | ### Eastern Conference All Stars 13 | 18 | 19 | ### Western Conference All Stars 20 | 25 | 26 | 27 | 28 | 29 | ## About this example 30 | 31 | This site is an example of using [Eleventy's JavaScript Data Files](https://www.11ty.io/docs/data-js/) which simplify pulling content from remote data sources and making them available as objects to be used globally across an Eleventy site. 32 | 33 | - This page is pulling content from a read-only [Google Sheets feed](https://spreadsheets.google.com/feeds/list/1CfI6XGm9OjjNKGr3kXRSKVLui_gkHZdadoOPIiNgE9s/od6/public/values?alt=json). 34 | - The [code is available to inspect on GitHub]({{ pkg.repository.url}}) and more information is available from the [ReadMe]({{ pkg.repository.url}}/blob/master/README.md). 35 | - You can also clone [the repo]({{ pkg.repository.url}}) and deploy your own version of the site to [Netlify](https://www.netlify.com) for free all in a couple of clicks by hitting the button below. That one down there. 👇 36 | 37 | 38 | ## Clone and deploy! 39 | 40 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/philhawksworth/example-read-from-sheets) 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/utils/minify-css.js: -------------------------------------------------------------------------------- 1 | const CleanCSS = require("clean-css"); 2 | module.exports = function(code) { 3 | return new CleanCSS({}).minify(code).styles; 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/minify-html.js: -------------------------------------------------------------------------------- 1 | const htmlmin = require("html-minifier"); 2 | 3 | module.exports = function(content, outputPath) { 4 | if( outputPath.endsWith(".html") ) { 5 | let minified = htmlmin.minify(content, { 6 | useShortDoctype: true, 7 | removeComments: true, 8 | collapseWhitespace: true 9 | }); 10 | return minified; 11 | } 12 | return content; 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/save-seed.js: -------------------------------------------------------------------------------- 1 | // Handy to save the results to a local file 2 | // to prime the dev data source 3 | 4 | const fs = require("fs"); 5 | 6 | module.exports = (data, path) => { 7 | if(process.env.ELEVENTY_ENV == 'seed') { 8 | fs.writeFile(path, data, err => { 9 | if(err) { 10 | console.log(err); 11 | } else { 12 | console.log(`Data saved for dev: ${path}`); 13 | } 14 | }); 15 | } 16 | } 17 | --------------------------------------------------------------------------------