'Google Sheets Index Sidebar of all tabs - add search functionality

After some searching I found this code to get a sidebar of all tabs (workbooks) in Google Sheets as a hyperlink. I am looking to add two functionalities to this, if its possible

  1. Search function - a search box on sidebar to look up tabs more easily
  2. Same tab - open the clicked hyperlinked tab in the same browser tab instead of opening in a new window.

Here's the code so far:

    function onOpen() {
      SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
          .createMenu('Sidebar Menu')
          .addItem('Show sidebar', 'showSidebar')
          .addToUi();
    }
    
    function showSidebar() {
      var ui = HtmlService.createTemplateFromFile('sidebar.html')
          .evaluate()
          .setSandboxMode(HtmlService.SandboxMode.IFRAME)
          .setTitle('Index Sidebar');
      
      SpreadsheetApp.getUi().showSidebar(ui);
    }
        
    function getSheetNames() {
      
      // Get all the different sheet IDs
      var ss = SpreadsheetApp.getActiveSpreadsheet();
      var sheets = ss.getSheets();
      
      return sheetNamesIds(sheets);
    }
        
    // function to create array of sheet names and sheet ids
    function sheetNamesIds(sheets) {
      
      var indexOfSheets = [];
      
      // create array of sheet names and sheet gids
      sheets.forEach(function(sheet){
        indexOfSheets.push([sheet.getSheetName(),sheet.getSheetId()]);
       
      });
      
      //Logger.log(indexOfSheets);
      return indexOfSheets; 
    }

HTML

<!DOCTYPE html>
<h1>Index of all sheets in this workbook:</h1>
<input type="button" value="Close" onclick="google.script.host.close()" />

<ol>
<?!= getSheetNames().map(function(d) {
    return "<li><a href='https://docs.google.com/spreadsheets/d/1234/edit#gid=" + d[1] + "' target='_blank'>" + d[0] + "</a></li>";
    }).join(''); ?>
</ol>


Solution 1:[1]

Proposed Solution

You should be able to replace the code you posted with what is below and have it work. First copy and paste it into the two files and then run it from the Sheet, not from the Script Editor.

enter image description here

HTML

<!DOCTYPE html>
<h1>Index of all sheets in this workbook:</h1>
<script>

function removeElement(elementId) {
    var element = document.getElementById(elementId);
    element.parentNode.removeChild(element);
}

function buildList(text) {
    google.script.run.withSuccessHandler(onSuccess).returnListItems(text)
}

function onSuccess(result) {
    var element = document.createElement("ol")
    element.innerHTML = result
    var sidebar = document.getElementById("sidebar")
    sidebar.appendChild(element)
}

function getTextAndSearch() {
    var text = document.getElementById("text-search").value
    removeElement("ol")
    buildList(text)
}


</script>
<sidebar id="sidebar">
    <input type="button" value="Close" onclick="google.script.host.close()" />
    <br>
    <input type="text" id="text-search" />
    <input type="button" value="Search" onclick="getTextAndSearch()" />

    <ol id="ol">
       <?!=
        returnListItems()
       ?>
    </ol>
</sidebar>

JS

function onOpen() {
  SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
  .createMenu('Sidebar Menu')
  .addItem('Show sidebar', 'showSidebar')
  .addToUi();
}

function showSidebar() {
  var ui = HtmlService.createTemplateFromFile('sidebar.html')
  .evaluate()
  .setSandboxMode(HtmlService.SandboxMode.IFRAME)
  .setTitle('Index Sidebar');
  
  SpreadsheetApp.getUi().showSidebar(ui);
}

function getSheetNames() {
  
  // Get all the different sheet IDs
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = ss.getSheets();
  
  return sheetNamesIds(sheets);
}

// function to create array of sheet names and sheet ids
function sheetNamesIds(sheets) {
  
  var indexOfSheets = [];
  
  // create array of sheet names and sheet gids
  sheets.forEach(function(sheet){
    indexOfSheets.push([sheet.getSheetName(),sheet.getSheetId()]);
    
  });
  
  //Logger.log(indexOfSheets);
  return indexOfSheets; 
}

// function to return a button with onclick attribute for each sheet that matches

function returnListItems(text) {
  
    var sheetNames = getSheetNames()
    
    // Checking if there is a search term
    if (text) {
      sheetNames = sheetNames.filter(n => n[0].includes(text))
    }
    
    var htmlString = sheetNames.map(function(d) {
        var string = `
        <li> 
          <input
           type="button"
           value="${d[0]}"
           onclick=\"google.script.run.setActiveByName('${d[0]}')\"/>
        </li>
        `
        return string }).join(' ')
    
    return htmlString
}

// Utility function to set Active sheet by name.
function setActiveByName(name) {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name)
  SpreadsheetApp.setActiveSheet(ss)
}

You may need to create a dummy function in the script editor: function init(){} and run it just to grant the permissions the script needs, though this should happen when you run the script from the menu.

Explanation

Bringing sheets into focus - setActiveSheet

This is relatively simple so I have included it in the answer, though technically is a second question. In future ensure to only ask one question per post, it keeps things tidier on the site and easier for future users to search for answers. Thank you

This involves using setActiveSheet for which I made the utility function setActiveByName.

To insert this function into each link, I wrote the function returnListItems, that generates and returns the HTML for each button to call the setActiveByName when clicked. Writing it as a second function was not strictly necessary, but it made things clearer, especially since it would make the main question a lot easier.

Search function

What seemed relatively simple when I started quickly got quite complicated. It involved breaking up some of the code into its component functionality, to make it easier to work with. Yet the biggest challenge is properly setting up the server side code (Apps Script editor) and the client side code (contained in the HTML).

Once the Sidebar is loaded, I found that any further manipulation of the sidebar HTML needed to be done client side, hence the functions in the HTML. The solution lay in the "Client-to-Server Communication" article in the docs.

The search button is pressed, then the client side script gets the text in the Search input box, passes it to the buildList function, also client side, which in turn calls:

google.script.run.withSuccessHandler(onSuccess).returnListItems([SEARCH_TERM])

This is a asynchronous function that goes server side. The "Success Handler" is the call back function that runs when the list items are returned. onSuccess simply updates the HTML in the sidebar. I modified the returnListItems function to be able to accept a search term, so when it runs the first time, it runs without any arguments so all sheets are returned. When called by the client side function, it runs with a text. If the sheet name contains the search term, it is listed.

References

Solution 2:[2]

Thank you Ian for your solution above. Works well! After some trial and error, I was able to tweak it to ensure hidden sheets aren't included. Below is the updated code for anyone else interested:

function onOpen() {
  SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
  .createMenu('Sidebar Menu')
  .addItem('Show sidebar', 'showSidebar')
  .addToUi();
}

function showSidebar() {
  var ui = HtmlService.createTemplateFromFile('Sidebar.html')
  .evaluate()
  .setSandboxMode(HtmlService.SandboxMode.IFRAME)
  .setTitle('Index Sidebar');
  
  SpreadsheetApp.getUi().showSidebar(ui);
}


function getSheetNames() {
  
  // Get all the different sheet IDs
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = ss.getSheets().filter(s => !s.isSheetHidden());
  
  return sheetNamesIds(sheets);
}


// function to create array of sheet names and sheet ids
function sheetNamesIds(sheets) {
  
  var indexOfSheets = [];

  // create array of sheet names and sheet gids
  sheets.forEach(function(sheet){
  
    indexOfSheets.push([sheet.getSheetName(),sheet.getSheetId()]);
        
  });

  
  //Logger.log(indexOfSheets);
  return indexOfSheets; 
}


// function to return a button with onclick attribute for each sheet that matches
function returnListItems(text) {
  
    var sheetNames = getSheetNames()
    
    // Checking if there is a search term
    if (text) {
      sheetNames = sheetNames.filter(n => n[0].includes(text))
    }
    
    var htmlString = sheetNames.map(function(d) {
        var string = `
        <li> 
          <input
           type="button"
           value="${d[0]}"
           onclick=\"google.script.run.setActiveByName('${d[0]}')\"/>
        </li>
        `
        return string }).join(' ')
    
    return htmlString
}



// Utility function to set Active sheet by name.
function setActiveByName(name) {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name)
  SpreadsheetApp.setActiveSheet(ss)
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mia O