With Google Drive, Apps Script, Bootstrap, and a little jQuery, you can develop an Image Library web application with search and download functionality! Let’s get started!
Image Library Example
Here’s an example of what we’re going to build. The header will have a search form. Use the input field to search for something. Use the select field to search across all folders or within a specific folder. Search results will display the images in a grid with a Download link.
Google Drive Setup
Let’s start by creating a folder called Image Library in your Google Drive. Next, create subfolders for each category. For example, I created a folder called Illustrations. Maybe you have another one for Icons, Wallpapers, etc. Add a few images in the folder(s) as examples.
Project Setup
Visit https://script.google.com/home and select the New project button. Give the project a title like Image Library. You can remove the default function provided in Code.gs.
Let’s add a few files to the project. This will help us organize the code a bit better. We’ll be creating files to store our html, javascript, and css. Select the plus icon next to Files and choose HTML. This will append .html to whatever name you give the file. Create the following 3 file names:
- index
- app.js
- style.css
I know, it looks weird seeing files for javascript and css using .html, but don’t worry! You can remove the default HTML provided in app.js.html and style.css.html. Your project should look like the following so far:
Bootstrap Setup
Let’s use Bootstrap to help build the layout of the web application. In your index.html file, let’s add the Bootstrap stylesheet just before the closing head tag.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
</head>
Again, in your index.html file, let’s add jQuery and Bootstrap’s javascript file just before the closing body tag.
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script>
</body>
Let’s create the sticky header with the search form. In your index.html, add the following just after the starting body tag:
<body>
<div class="navbar navbar-dark bg-dark sticky-top">
<a class="navbar-brand">Image Library</a>
<form id="search" class="form-inline">
<input id="term" class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<select id="folders" class="form-control">
<option value="">Loading...</option>
</select>
</form>
</div>
Now let’s create a div where the search results will appear. Again, in your index.html file, add the following just after the code above.
<div id="searchResults" class="container mt-4"></div>
App Setup
In order for the web application to appear, we need to include a doGet function that returns our html output. Let’s use the HtmlService to return the html output of the index file. In your Code.gs file, add the following:
// Creates the html page for the web application.
function doGet(e) {
var page = HtmlService.createTemplateFromFile("index").evaluate();
page.setTitle("Image Library");
page.addMetaTag('viewport', 'width=device-width, initial-scale=1, shrink-to-fit=no');
return page;
}
We’ve created separate files for our css and javascript, but have no way of including them in our index.html file. Let’s create a function to help us do that.
Let’s use the HtmlService again to create the output from the file name we pass in. In your Code.gs file, add the following:
// Get other files within project to be used in web app such as CSS/Javascript.
function include(file) {
return HtmlService.createHtmlOutputFromFile(file).getContent();
}
To include your style.css file, go to your index.html file and add the following just before the closing head tag:
<?!= include("style.css"); ?>
</head>
To include your app.js file, go to your index.html file and add the following just before the closing body tag:
<?!= include("app.js"); ?>
</body>
Initial Deployment
In order to see your application, you need to deploy it first. Don’t worry, you can keep it hidden to yourself to start. The deployment also provides a url you can use to test your changes.
Under the Deploy button, choose New Deployment. At the Description field, enter something like Initial Deployment. Under the Web App section, at the Execute As field, choose to execute the app as your user. At the Who has access field, choose Only myself. This will restrict the app so only you can use it. This can be changed later if and when you want to allow anyone access to the app.
Now that the app has been deployed, it will have a test deployment url you can use. To find it, go to the Deploy button again and this time choose Test Deployments. Copy the Web App Url and visit it. It won’t do much right now, but you can now use this url to test your changes!
Get Google Drive Folders
When the app loads, the select field in our search form only shows a single Loading option. We want it to show options for each subfolder in our library.
First, we need the folder id of the Image Library folder. Navigate to the Image Library folder in Google Drive. The url will look something like drive.google.com/drive/folders/YOUR_FOLDER_ID. Copy that folder id.
At the top of your Code.gs file, create a variable called mainFolderId and replace YOUR_FOLDER_ID with the actual folder id you copied:
var mainFolderId = 'YOUR_FOLDER_ID';
Now let’s create a function called getFolders. This will be called by our app to list the folder names in the select field of the search form.
// Get subfolder information for use in search select field.
function getFolders() {
}
Let’s continue to add to the getFolders function. First, create a variable called mainFolder, which will be the Image Library folder using the getFolderById function within the DriveApp class.
var mainFolder = DriveApp.getFolderById(mainFolderId);
Next, create a variable called subfolders, which will be all the subfolders within the Image Library folder using the getFolders function.
var subfolders = mainFolder.getFolders();
Create a variable called folders, which will be an empty array to start.
var folders = [];
Let’s loop through all the subfolders and grab their id and name and push that information into the folders array.
while (subfolders.hasNext()) {
var folder = subfolders.next();
var folderId = folder.getId();
var folderName = folder.getName();
folders.push({
id: folderId,
name: folderName
});
}
Finally, let’s return the array of folders in alphabetical order.
return folders.sort(function(a, b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
We now have our list the folders ready to hook up to the search form.
Search Google Drive Folders
We need a way to search our subfolders based on what the user provided in the search form of the app. Let’s create a function called search which will receive the folder id and term as arguments.
// Search based on folder id and term provided.
function search(id, term) {
}
Let’s continue to add to the search function. First, create a variable called mainFolder, which will be the Image Library folder using the getFolderById function within the DriveApp class.
var mainFolder = DriveApp.getFolderById(mainFolderId);
Next, create a variable called subfolders, which will be all the subfolders within the Image Library folder using the getFolders function.
var subfolders = mainFolder.getFolders();
Create a variable called searchCriteria. This will be used to search files based on the term the user provided.
var searchCriteria = 'fullText contains "' + term + '"';
Create variables called searchFoldersCriteria and results, which will be empty arrays to start.
var searchFoldersCriteria = [];
var results = [];
If the user decides to search across all folders, we need to build the search query to search within each folder. We have to loop through all the subfolders and grab their id and push the information into the searchFoldersCriteria array. We convert that array into a string, separating each entry with an or.
If the user selects a specific folder, we only need to search within that folder. The folder id was already provided by the user.
if (id === 'ALL') {
while (subfolders.hasNext()) {
var folder = subfolders.next();
var folderId = folder.getId();
searchFoldersCriteria.push('"' + folderId + '" in parents');
}
searchFoldersCriteria = searchFoldersCriteria.join(' or ');
} else {
searchFoldersCriteria = '"' + id + '" in parents';
}
We want the search query to look something like the following:
'fullText contains "puppy" and ("id1" in parents or "id2" in parents or "id3" in parents')
Create a variable called files. Its value will be the result of using the searchFiles function, where we pass in the searchCriteria and searchFoldersCriteria as the search query.
var files = DriveApp.searchFiles(searchCriteria + ' and ' + '(' + searchFoldersCriteria + ')');
Let’s loop through the matching files found, grab their id and name, and push that information into the results array.
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();
var fileName = file.getName();
results.push({
id: fileId,
name: fileName
});
}
All that’s left to do is return the results array.
return results;
We now have our search functionality ready to hook up to the search form.
Select Field Folders List
When the app loads, let’s replace the Loading… option that currently appears in the select field of the search form with the actual list of folders.
Head on over to the app.js.html file and start by adding an opening and closing script tag along with jQuery’s ready function.
<script>
$(document).ready(function() {
});
</script>
Within jQuery’s ready function, add the google.script.run class and call the getFolders function in the Code.gs file. If it’s successful, we’ll call a function called buildFolderOptions.
google.script.run.withSuccessHandler(buildFolderOptions).getFolders();
Let’s create the buildFolderOptions function now in app.js.html. It will start by creating an option for searching all folders. It will then add in options for each subfolder where the value will be the folder id and the text will be the folder name. We replace the select field with the html we built.
// Run when getting folders was successful.
function buildFolderOptions(folders) {
var html = '<option value="ALL">All</option>';
for (var i = 0; i < folders.length; i++) {
var folder = folders[i];
html += '<option value="' + folder.id + '">' + folder.name + '</option>';
}
$('#folders').html(html);
}
Search Form Submission
Let’s create the function called search that gets called when the search form is submitted. In the app.js.html file, add the following within the jQuery ready function:
$('#search').on('submit', search);
Now let’s create the search function in app.js.html. Grab the search term and selected folder. Prevent the default form submission from happening. Display text to the user that a search is happening. Using the google.script.run class again, call the search function in the Code.gs file passing in the folder id and term. If it’s successful, we’ll call a function called displaySearchResults.
// Run when the search form is submitted.
function search(e) {
var term = $('#term').val().trim();
var folderId = $('#folders').val();
var searchResults = $('#searchResults');
// Prevent default form submission from happening.
e.preventDefault();
if (term && folderId) {
// Display Searching text.
searchResults.html('<p class="alert alert-info" role="alert">Searching...</p>');
// Perform the search.
google.script.run.withSuccessHandler(displaySearchResults).search(folderId, term);
}
}
Let’s create the displaySearchResults function now in app.js.html. It will create the html output for the search results, building a grid of cards with the image and download link. If there are no results found, it will display a message.
// Run when search was successful.
function displaySearchResults(results) {
var searchResults = $('#searchResults');
var html = '';
if (results.length === 0) {
html = '<p class="text-center">Sorry, no results found.</p>';
} else {
html += '<div class="row row-cols-1 row-cols-md-3">';
for (var i = 0; i < results.length; i++) {
var result = results[i];
html += '<div class="col mb-4">';
html += ' <div class="card h-100">';
html += ' <div class="card-header text-center">' + result.name + '</div>';
html += ' <div class="card-body"><img src ="https://drive.google.com/uc?&id=' + result.id + '" class="card-img-top" /></div>';
html += ' <div class="card-footer text-center"><a class="card-link" href="https://drive.google.com/uc?export=download&id=' + result.id + '">Download</a></div>';
html += ' </div>';
html += '</div>';
}
html += '</div>';
}
searchResults.html(html);
}
Finally, in your style.css.html file, let’s add a style to the images so they don’t break out of the grid columns.
<style>
img { width: 100%; height: auto; }
</style>
Refresh your app url if you’re currently on it and give the app a try!
Partial Searches
You may have noticed that some of your searches aren’t returning any results. For example, I have a file named spongebob.png. If I search for sponge, nothing is found. I have to search for spongebob.
Providing a description that includes additional words or phrases can help. This can be done in Google Drive by right clicking on the file, choosing View Details, and editing the file’s description.