Image Library
Tags: googleappsscript, googledrive, javascript, webdev
Published: 2021-02-17 00:50:02

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.

![Image Library Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yjlkvczn85fn3dsm3li.png)

## 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.

![Google Drive Folders](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xn9b3sh5g2ihpa6mqmdh.png)

## Project Setup

Visit [https://script.google.com/home](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:

![Project Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ienn0yg8yqkowcaqqzpy.png)

## Bootstrap Setup

Let’s use [Bootstrap](https://getbootstrap.com/) 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.

```html
  <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.

```html
  <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:

```html
<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.

```html
<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](https://developers.google.com/apps-script/reference/html/html-service) to return the html output of the index file. In your **Code.gs** file, add the following:

```javascript
// 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](https://developers.google.com/apps-script/reference/html/html-service) again to create the output from the file name we pass in. In your **Code.gs** file, add the following:

```javascript
// 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:

```javascript
  <?!= 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:

```javascript
  <?!= 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:

```javascript
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.

```javascript
// 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](https://developers.google.com/apps-script/reference/drive/drive-app#getFolderById(String)) function within the [DriveApp](https://developers.google.com/apps-script/reference/drive/drive-app) class.

```javascript
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](https://developers.google.com/apps-script/reference/drive/drive-app#getFolders()) function.

```javascript
var subfolders = mainFolder.getFolders();
```

Create a variable called **folders**, which will be an empty array to start.

```javascript
var folders = [];
```

Let’s loop through all the subfolders and grab their id and name and push that information into the folders array.

```javascript
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.

```javascript
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.

```javascript
// 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](https://developers.google.com/apps-script/reference/drive/drive-app#getFolderById(String)) function within the [DriveApp](https://developers.google.com/apps-script/reference/drive/drive-app) class.

```javascript
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](https://developers.google.com/apps-script/reference/drive/drive-app#getFolders()) function.

```javascript
var subfolders = mainFolder.getFolders();
```

Create a variable called **searchCriteria**. This will be used to search files based on the term the user provided.

```javascript
var searchCriteria = 'fullText contains "' + term + '"';
```

Create variables called **searchFoldersCriteria** and **results**, which will be empty arrays to start.

```javascript
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.

```javascript
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](https://developers.google.com/apps-script/reference/drive/drive-app#searchfilesparams) function, where we pass in the searchCriteria and searchFoldersCriteria as the search query.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
<script>
  $(document).ready(function() {
 
  });
</script>
```

Within jQuery’s ready function, add the [google.script.run](https://developers.google.com/apps-script/guides/html/reference/run) class and call the **getFolders** function in the Code.gs file. If it’s successful, we’ll call a function called **buildFolderOptions**.

```javascript
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.

```javascript
// 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:

```javascript
$('#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**.

```javascript
// 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 &amp;&amp; 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.

```javascript
// 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?&amp;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&amp;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.

```css
<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.

---
#### Visit our website at [https://nightwolf.dev](https://nightwolf.dev) and follow us on [Facebook](https://www.facebook.com/NightWolfDevelopment) and [Twitter](https://twitter.com/nightwolfdev)!
---