skip to Main Content

I’m creating a dynamic sidebar in a Document with a Google Apps Scripts sidebar served by HTMLService.

In the client-side sidebar template script, I can find my container via a jQuery selector and dynamically append DOM buttons to it, but I cannot remove any button elements from the same container (which is best practice since each holds an event handler which, as I understand it, would not be deleted from memory).

The function within the sidebar.html responsible for these actions:

function fillContainerWithWords(arrayOfWords, container, wordClass) {
     while (container.first()) {               // empty word container
       container.remove(container.first());
     }

     arrayOfWords.forEach(word => {               // fill word container
       let wordButton = `<div><button class="${wordClass}" onclick=getNewWords("${word}")>${word}</button></div>`;
       container.append(wordButton);
     })
}

CAN append to the container correctly, but DOES NOT remove any children from the container (so it ends up stacking DOM elements onto the container)

I’ve tried deviations of the same intent, such as:

  • container.firstChild, (typical javascript way)
  • $(container).firstChild, (the jQuery way)
  • container.removeChild(container.first())

and nothing seems to work.
The "firstChild" approach produces an "undefined" (clearly not a property of the container.)
The code above is the closest I’ve managed, but produces an error deep in the jQuery implementation (or perhaps with Caja?):

    jquery.min.js:4 Uncaught TypeError: t.replace is not a function
    at st.matchesSelector (**jquery.min.js:4:10383**)
    at Function.filter (**jquery.min.js:4:23300**)
    at init.remove (**jquery.min.js:4:26762**)
    at **fillContainerWithWords** (userCodeAppPanel:73:17)
    at userCodeAppPanel:55:17
    at Of (2515706220-mae_html_user_bin_i18n_mae_html_user.js:94:266)
    at 2515706220-mae_html_user_bin_i18n_mae_html_user.js:25:132
    at Ug.U (2515706220-mae_html_user_bin_i18n_mae_html_user.js:123:380)
    at Bd (2515706220-mae_html_user_bin_i18n_mae_html_user.js:54:477)
    at a (2515706220-mae_html_user_bin_i18n_mae_html_user.js:52:52)

Any info on restrictions or possible options is greatly appreciated…

Stripped down minimum example:

Code.gs

/**
 * @OnlyCurrentDoc
*/

function onOpen(e) {
  DocumentApp.getUi().createAddonMenu()
      .addItem('Start', 'showSidebar')
      .addToUi();
}

function onInstall(e) {
  onOpen(e);
}

function showSidebar() {
  const ui = HtmlService.createHtmlOutputFromFile('basic').setTitle('stackoverflow').setSandboxMode(HtmlService.SandboxMode.EMULATED);;
  DocumentApp.getUi().showSidebar(ui);                  
}

basic.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
    <style>
    .col-contain {
      overflow: hidden;
    }
    .col-one {
      float: left;
      width: 50%;
    }
    .blue {
      color: blue;
    }
    .red {
      color: red;
    }
  </style>
  
  <body>
    <button class="blue" id="change-button">Change List</button>
    <div class="block col-contain">
      <div class="col-one" id="list"></div>
    </div>
  </body>
  
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  <script>
    $(function() {
      $('#change-button').click(() => fillContainerWithWords(['five', 'six', 'seven'], $("#list"), 'blue'));
  });

  function fillContainerWithWords(arrayOfWords, container, wordClass) {
    // while (container.first()) {               // empty word container
    //   container.remove(container.first());
    // }

    arrayOfWords.forEach(word => {               // fill word container
      let wordButton = `<div><button class="${wordClass}">${word}</button></div>`;
      container.append(wordButton);
    })
  }

  fillContainerWithWords(['one', 'two', 'three', 'four'], $("#list"), 'red');

  </script>  
</html>

2

Answers


  1. Chosen as BEST ANSWER

    Thanks Ruben - that put me on the right track.

    For anyone curious for the full set of modifications required to get the function to work successfully to both remove and replace the list:

      function fillContainerWithWords(arrayOfWords, container, wordClass) {
        while (container.firstChild) {               // empty word container
           container.removeChild(container.firstChild);
        }
    
        arrayOfWords.forEach(word => {               // fill word container
          let button = document.createElement('BUTTON');
          button.id = word;
          button.classList.add(wordClass);
          let text = document.createTextNode(word);
          button.appendChild(text);                 
          let div = document.createElement('DIV');
          div.appendChild(button);
          
          container.appendChild(div);
        })
      }
    

    1. Remove .setSandboxMode(HtmlService.SandboxMode.EMULATED);

    2. Remove

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    
    1. Replace
    $(function() {
          $('#change-button').click(() => fillContainerWithWords(['five', 'six', 'seven'], $("#list"), 'blue'));
      });
    

    by

    const list = document.querySelector('#list');
    document.querySelector('#change-button').addEventListener('click', () => {
      fillContainerWithWords(['five', 'six', 'seven'], list, 'blue'))
    });
    
    

    and replace

    fillContainerWithWords(['one', 'two', 'three', 'four'], $("#list"), 'red');
    

    by

    fillContainerWithWords(['one', 'two', 'three', 'four'], list, 'red');
    

    It looks that the references that you used to write your code are obsolete as it includes

    1. A very old jQuery version, 1.9.1. While this should still work, the client-side code doesn’t look to really need jQuery nowadays, i.e. Document.querySelector and Document.querySelectorAll could be used with CSS-selectors to select DOM elements in a similar way that jQuery used them.
    2. A deprecated method sandox mode, HtmlService.SandboxMode.EMULATED.
      From https://developers.google.com/apps-script/reference/html/sandbox-mode

      The NATIVE and EMULATED modes were deprecated on October 13, 2015 and both are now sunset. Only IFRAME mode is now supported.

    jQuery is not bad per se but, IMHO, nowadays it’s better to avoid it’s use in very small web apps like usually are Google editors custom dialogs and sidebars as the tecnhologies that support them have evolved a lot making jQuery not as "indispensable" as it was several years ago.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search