Skip to content

Instantly share code, notes, and snippets.

@metanomial
Last active February 5, 2020 07:16
Show Gist options
  • Save metanomial/3553320a49ad295d304d78ebbb062f89 to your computer and use it in GitHub Desktop.
Save metanomial/3553320a49ad295d304d78ebbb062f89 to your computer and use it in GitHub Desktop.
Google-Sheets-as-Database Search Interface
/**
@OnlyCurrentDoc
**/
function doGet(e) {
var template = HtmlService.createTemplateFromFile('Index');
const catalog = SpreadsheetApp.getActiveSheet().getRange('<set range>').getDisplayValues();
template.catalog = JSON.stringify(catalog);
const output = template.evaluate();
output.setTitle('<enter title here>');
output.addMetaTag('viewport', 'width=device-width, initial-scale=1');
return output;
}
<!DOCTYPE html>
<html lang=en>
<head>
<base target=_top>
<style>
body {
margin: 0;
padding: 1rem;
}
header, main, footer {
max-width: 720px; /* adjust as needed */
margin: 0 auto;
}
a { text-decoration: none; }
a:hover[href], a:target[href] { text-decoration: underline; }
#results {
width: 100%;
border-collapse: collapse;
border: 1px solid grey;
}
#results th, td { padding: 0.4rem; }
#results th {
text-align: left;
background-color: silver;
}
#results .result:nth-child(even) { background-color: #dfdfdf; }
#results .field .copy {
visibility: hidden;
cursor: pointer;
}
#results .field:hover .copy { visibility: visible; }
footer {
margin-top: 2rem;
color: grey;
}
footer a, footer a:hover, footer a:target, footer a:visited { color: cornflowerblue; }
</style>
<script type="text/javascript" src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
<script type="text/javascript">
const catalog = JSON.parse(<?= catalog ?>) // Catalog data is intserted here
.slice(1) // Remove header
.filter(record =>
record[0].length +
record[1].length /* +
... */ != 0 // Adjust according to record count
);
const app = new Vue({
data: {
search: '' // Search query
},
methods: {
clear() { this.search = ''; }, // Wipe search query
async copy(string) { await navigator.clipboard.writeText(string); } // Copy to clipboard
},
computed: {
results() {
return catalog.filter(record => {
// Search all fields for partial string match
const query = this.search.toLowerCase();
for(const field of record) if(field.toLowerCase().search(query) > -1) return true;
// "No results found"
return false;
});
}
}
});
document.addEventListener('DOMContentLoaded', () => app.$mount('#app')); // Mount Vue app on load
</script>
</head>
<body>
<header>
<h1><!-- Enter name here --></h1>
</header>
<main id=app>
<div id=search>
<p><label>
<b>Search</b>
<input type=text v-model="search">
<button @click="clear">Clear</button>
</label>
</div>
<table id=results>
<thead>
<tr>
<th><!-- Appropriate headers here -->
<th><!-- ... -->
</tr>
</thead>
<tbody>
<tr class=result v-for="record in results">
<td class="field header_class_name_here">{{ record[0] }} <a class=copy @click="copy(record[0])">&#128203;</a></td>
<!-- ... -->
</tr>
<tr v-if="!results.length">
<td colspan=1><!-- Adjust colspan as necessary-->No results found
</tr>
</tbody>
</table>
</main>
<footer>
<p>Designed by <a href="mailto:benjamin@metanomial.com" target=_blank>Benjamin Herman</a>
</footer>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment