Skip to content

Instantly share code, notes, and snippets.

@coinsandsteeldev
Forked from arthurattwell/dialog.html
Last active September 8, 2024 11:18
Show Gist options
  • Save coinsandsteeldev/4c67dfa5411e8add913273fc5a30f5e7 to your computer and use it in GitHub Desktop.
Save coinsandsteeldev/4c67dfa5411e8add913273fc5a30f5e7 to your computer and use it in GitHub Desktop.
Google Sheets script to allow multi-select in cells with data-validation (adapted from https://www.youtube.com/watch?v=dm4z9l26O0I)
<!DOCTYPE html>
<html>
<head>
<script>
var data
var formId = 'form'
function drawForm() {
if (!data) return
var outputEl = document.getElementById(formId);
var output = ''
if (Object.prototype.toString.call(data) === '[object Array]') {
for (var i = 0; i < data.length; i++) {
if (data[i].length == 1 ) {
if (data[i][0] == '') continue;
output += '<input type=\'checkbox\' name=\''+data[i][0]+'\' value=\''+data[i][0]+'\'>'
output += data[i][0]
output += '<br>'
} else if (data[i].length > 1) {
if (data[i][0] == '') continue;
// left will be used as value
// the rest is title
output += '<input type=\'checkbox\' name=\''+data[i][0]+'\' value=\''+data[i][0]+'\'>'
output += data[i][0] + ' &ndash; '
for (var j = 1; j < data[i].length; j++) {
if (data[i][j] == '') continue
output += data[i][j] + '; '
}
output += '<br>'
}
}
} else {
output += '<p>This cell has no <a href="https://support.google.com/drive/answer/139705?hl=en">Data validation</a>.</p>';
}
outputEl.innerHTML = output
}
var onData = function(result) {
data = result
drawForm()
}
google.script.run.withSuccessHandler(onData).getValidationData();
function set() {
google.script.run.withSuccessHandler(x=>{
}).fillCell(document.getElementById(formId))
}
function update() {
google.script.run.withSuccessHandler(x=>{
}).updateCell(document.getElementById(formId))
}
function reset() {
drawForm()
}
</script>
</head>
<body>
<div style='position:fixed; padding-top: 10px; background-color: white; height: 30px; width: 100%; top: 0;'>
<input type="button" value="Set" onclick="set()" />
<input type="button" value="Update" onclick="update()" />
<input type="button" value="Reset" onclick="reset()" />
<input type="button" value="Refresh validation" onclick="google.script.run.showDialog()" />
</div>
<div style="font-family: sans-serif; padding-top: 30px;">
<form id="form" name="form">
</form>
</div>
</body>
<html>
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Scripts')
.addItem('Multi-select for this cell...', 'showDialog')
.addToUi();
}
function showDialog() {
var html = HtmlService.createHtmlOutputFromFile('dialog').setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi()
.showSidebar(html);
}
function getValidationData(){
try {
return SpreadsheetApp.getActiveRange().getDataValidation().getCriteriaValues()[0].getValues();
} catch(e) {
return null
}
}
function setValues_(e, update) {
var selectedValues = [];
for (var i in e) {
selectedValues.push(i);
}
var separator = ','
var total = selectedValues.length
if (total > 0) {
var range = SpreadsheetApp.getActiveRange()
var value = selectedValues.join(separator)
if (update) {
var values = range.getValues()
// check every cell in range
for (var row = 0; row < values.length; ++row) {
for (var column = 0; column < values[row].length; ++column) {
var currentValues = values[row][column].split(separator);//typeof values[row][column] === Array ? values[row][column].split(separator) : [values[row][column]+'']
// find same values and remove them
var newValues = []
for (var j = 0; j < currentValues.length; ++j) {
var uniqueValue = true
for(var i = 0; i < total; ++i) {
if (selectedValues[i] == currentValues[j]) {
uniqueValue = false
break
}
}
if (uniqueValue && currentValues[j].trim() != '') {
newValues.push(currentValues[j])
}
}
if (newValues.length > 0) {
range.getCell(row+1, column+1).setValue(newValues.join(separator)+separator+value)
} else {
range.getCell(row+1, column+1).setValue(value);
}
}
}
} else {
range.setValue(value);
}
}
}
function updateCell(e) {
return setValues_(e, true)
}
function fillCell(e) {
setValues_(e)
}
@tushar-anand
Copy link

Better if we add margin-top style in line number 70 in the html file for the form with id='form'.
style="margin-top: 30px;"

Adding this would display the form elements below the button.
As of now, the buttons are overlapping the form elements.

@Manda-FFA
Copy link

If I'm reading through this correctly, then the only way for the script to pick up the List of options from Data Validation is if it's reading from a designated cell range and can't be a List of Items, in the Criteria section of Data Validation?

DaTigerYT posted an image that that shows the Criteria being set as a List of items instead of List from range -- and that's why the script isn't working as intended, right? because the Criteria needs to be "List from range"

can the script also allow for multiple selections if pulling options from this List of items?

@luismartinezs
Copy link

It works handsomely! Thanks

@anglilian
Copy link

anglilian commented May 12, 2020

@DaTigerYT I believe I have setup that screen above as you have but I cannot get the Side Panel to show me a Multi-select (showing options and checkboxes) after Saving it. Is there another step to do that? If you know. Thanks

I also have a similar issue :( wondering if any of you manage to find a solution yet!

edit: typo

@anglilian
Copy link

If I'm reading through this correctly, then the only way for the script to pick up the List of options from Data Validation is if it's reading from a designated cell range and can't be a List of Items, in the Criteria section of Data Validation?

DaTigerYT posted an image that that shows the Criteria being set as a List of items instead of List from range -- and that's why the script isn't working as intended, right? because the Criteria needs to be "List from range"

can the script also allow for multiple selections if pulling options from this List of items?

Tried shifting to "list from range" still didn't work. Having the same problem with @DaTigerYT

@501commons-lucinda
Copy link

@lianlaughsalot and @coinsandsteeldev- this script had been working happily for me for a few months and only recently stopped working.

Turns out, the script above is not compatible with V8 runtime. https://developers.google.com/apps-script/guides/v8-runtime
Google updated the runtime for many scripts on the back end without much notification. I was able to get the script working for me again by using the legacy Rhino runtime.

  1. Edit the script
  2. Run -> Disable new Apps Script powered by Chrome V8
  3. Save

@VforVenique
Copy link

@coinsandsteeldev @lestroud
Any thoughts on how to update this script so that when you navigate back to a cell that already has multiple options selected, the options that have already been selected display as blue selected checkboxes in the sidebar? This would create a more intuitive way to update the contents of a cell.

Sorry, I'm a noob and don't even know where to start... :-\

@Lartsch
Copy link

Lartsch commented Nov 24, 2020

@VforVenique
Copy link

@Lartsch
Thanks so much, can't believe I missed that one.

One more question for you: is it possible to change the text that reads "Apps Script application" label at the top of the Scripts sidebar?

Screen Shot 2020-11-24 at 4 17 23 PM

@VforVenique
Copy link

@coinsandsteeldev
Thanks so much for creating this awesome script!

Follow-up question: I'd like to use this script at the nonprofit I work for and we have some pretty sensitive client data in our sheets. Does this script give you or anyone else outside of our Google Suite domain access to our Google Sheets/Drive (and therefore our client data)?

The Authorization prompts to run the script make me a little nervous about data privacy/security. But I'm hoping that it's just a standard Authorization prompt for third-party apps/add-ons and that it doesn't really apply to the multi-select script you've created.
Screen Shot 2020-11-30 at 3 49 36 PM

Thanks again for all of your help!

Many Thanks,
Steve

@msmichls
Copy link

msmichls commented Feb 3, 2021

I am having trouble seeing any of the validation values in the dialog box. How can I check the output values? Also, I am not seeing any activity when I click the buttons. I am working on Google Chrome. Is there something I am missing?

Dialog Box - No Values

@VforVenique
Copy link

VforVenique commented Feb 7, 2021 via email

@OnodOfTheNorth
Copy link

I've updated this script so that when you navigate back to a cell that already has multiple entries selected, the UI displays these options as checked so that its easier to work with large lists.

https://gist.github.com/GeorgeNavarre/fdaeb3c3b49e246b9fae8aefe9c30448

@OnodOfTheNorth
Copy link

@VforVenique
I've made a fork that does just this. If you, like me, couldn't get carlosleonam's script to function correctly you may have more luck with mine.
https://gist.github.com/GeorgeNavarre/fdaeb3c3b49e246b9fae8aefe9c30448

@noeldelgadom
Copy link

You may need to disable V8 and revert back to Rhino.

On Tue, Feb 2, 2021 at 7:54 PM msmichls @.> wrote: @.* commented on this gist. ------------------------------ I am having trouble seeing any of the validation values. Also, I am not seeing any activity when I click the buttons. I am working on Google Chrome. Is there something I am missing? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://gist.github.com/4c67dfa5411e8add913273fc5a30f5e7#gistcomment-3617773, or unsubscribe https://github.com/notifications/unsubscribe-auth/ARZZQ5A6QT4UJJJUO2ZICALS5CUEZANCNFSM4HXS23VQ .

Yes, @VforVenique. That worked. I had the same issue as @msmichls and was able to solve by disabling V8, which I did by

  1. Opening the Script Editor (Tools > Script Editor).
  2. Project Settings (left menu).
  3. Uncheck 'Enable Chrome V8 runtime'

@KierstenMc
Copy link

You may need to disable V8 and revert back to Rhino.

On Tue, Feb 2, 2021 at 7:54 PM msmichls @.> wrote: _@**.**_* commented on this gist. ------------------------------ I am having trouble seeing any of the validation values. Also, I am not seeing any activity when I click the buttons. I am working on Google Chrome. Is there something I am missing? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://gist.github.com/4c67dfa5411e8add913273fc5a30f5e7#gistcomment-3617773, or unsubscribe https://github.com/notifications/unsubscribe-auth/ARZZQ5A6QT4UJJJUO2ZICALS5CUEZANCNFSM4HXS23VQ .

Yes, @VforVenique. That worked. I had the same issue as @msmichls and was able to solve by disabling V8, which I did by

  1. Opening the Script Editor (Tools > Script Editor).
  2. Project Settings (left menu).
  3. Uncheck 'Enable Chrome V8 runtime'

Thank you so much! that fixed my issues.

@simon88pl
Copy link

I've made a fork with:

  • sorting values alphabetically
  • clickable options
  • a bit more pleasant look & feel

https://gist.github.com/simon88pl/609cf5ee665117fc8597a7dee4afb598

@vehgroshop
Copy link

Just tried this script and it does the trick nicely, thanks.
The reason I wanted something like this was because I need to be able to concatenate certain values, to be used as descriptions after further processing.

Now I get every selected value separated by a comma and a space. But what would I need to modify, to concatenate alle values like that except the last selected a value? The last selected value should be preceded by " and ". So that I get values like:
"red, white, bue and yellow".

And a second question...
What if I wanted the concatenated values to be translated?
My spreadsheet is designed to be gathering data in 3 languages. If a user selects multiple values from the dropdown, they should only be selecting them in one language. I want the data in the columns for the other two languages to be populated automatically based on the selection in the first...

When working just with single values this can easily be done using INDEX or Vertical Lookup from arrays, but the combinations with multiple select are arbitrary and I don't want to create a translation array with all possible combinations.

@vehgroshop
Copy link

vehgroshop commented Jan 17, 2022

I tried out the script a bit more and something doesn't quite work out yet.
If I select a number of items from my list in a specific order and press the "set" button in the Apps Script-app
I get the concatenated result allright, but the order is different from the order in which I selected them. (of course 25 kg should be last in line)
image

Furthermore.... if I press the "set" button once more, the order in the cell changes.
What should I do to make sure the set button will add individual values to the concatenation in the order of selection?

@OnodOfTheNorth
Copy link

OnodOfTheNorth commented Jan 17, 2022 via email

@brew-guy
Copy link

For those experiencing that the validation options don't render with Chrome V8 runtime, try and remove .getValues() from line 14 in the multi-select.gs file.

@aseiden7
Copy link

aseiden7 commented Sep 1, 2022

I still get a warning for cells with multiple values, is there a way to make the data validation accept cells containing a list?

@dolemite26
Copy link

This is awesome, thanks!
Apologies if this can be found elsewhere, but do you know if there Is there any way to change it so that the user would experience the validation choices in the menu as refreshing automatically upon moving to a cell selection with a different list range, rather than having to click the "refresh validation" to do it?

I've updated this script so that when you navigate back to a cell that already has multiple entries selected, the UI displays these options as checked so that its easier to work with large lists.

https://gist.github.com/GeorgeNavarre/fdaeb3c3b49e246b9fae8aefe9c30448

@devinhelgeson
Copy link

Reguarding the V8 runtime issue, I came up with this sollution. as @brew-guy suggested, getValues() is the troublemaker here. However, simply removing it does not format the list correctly. Here's some code to go inside the try in the getValidationData() function.
`

  // return SpreadsheetApp.getActiveRange().getDataValidation().getCriteriaValues()[0].getValues();

  const arr = SpreadsheetApp.getActiveRange().getDataValidation().getCriteriaValues()[0];

  const newArr = [];
  while(arr.length) newArr.push(arr.splice(0,1));

  return newArr;

`

The logic the html file implements requires an array of an array (one element only) of strings. This code above (and I assume getValues() formally as well) transforms the 1D array to a 2D array, without changing the 1D shape (basically a flat 2D array).

Hope this helps anyone still trying to use this script.

@l-yusya
Copy link

l-yusya commented Feb 28, 2023

@devinhelgeson even with this addition the items still don't render for me.
Could I have missed something? I've tried all the versions of the script mentioned here, but none of them worked.
image

@facine
Copy link

facine commented Sep 19, 2023

@devinhelgeson even with this addition the items still don't render for me. Could I have missed something? I've tried all the versions of the script mentioned here, but none of them worked. image

https://gist.github.com/arthurattwell/aa5afc178624bdd6f46c2d0d28d06136?permalink_comment_id=3805444#gistcomment-3805444

@Joshben88
Copy link

Joshben88 commented Jul 16, 2024

Trying to allow for multiple reoccurences.
For context - I'm using this to track products I send out to customers (sometimes multiple of the same product), so wondered if there was a way to delete the code that dissallows double entries without removing functionality. Any help would be appreciated! @coinsandsteeldev

@brew-guy
Copy link

Hi @Joshben88. Here's a take on adding multiple of the same values. There's a little plus button next to each value that allows you to add more of each:

multi-select-gs:

function onOpen(e) {
  SpreadsheetApp.getUi()
    .createMenu('⭐ Scripts')
    .addItem('Multi-select for this cell...', 'showDialog')
    .addToUi();
}

function showDialog() {
  var html = HtmlService.createHtmlOutputFromFile('dialog').setSandboxMode(HtmlService.SandboxMode.IFRAME);
  SpreadsheetApp.getUi()
    .showSidebar(html);
}

function getValidationData() {
  const cell = SpreadsheetApp.getActive().getActiveRange();
  const rule = cell.getDataValidation();
  if (rule != null) {
    var criteria = rule.getCriteriaType();
    var args = rule.getCriteriaValues();
    if (criteria == 'VALUE_IN_LIST') {
      return args[0].map(el => [el]);
    } else if (criteria == 'VALUE_IN_RANGE') {
      let values = args[0].getValues();
      if (values.length > 1) {
        return values;
      } else {
        return values[0].map(el => [el]);
      }
    }
  } else {
    return null
  }
}

function setValues_(selectedValues, update) {
  var separator = ','
  var total = selectedValues.length
  if (total > 0) {
    var range = SpreadsheetApp.getActiveRange()
    var value = selectedValues.join(separator)
    if (update) {
      var values = range.getValues()
      // check every cell in range
      for (var row = 0; row < values.length; ++row) {
        for (var column = 0; column < values[row].length; ++column) {
          var currentValues = values[row][column].split(separator);
          range.getCell(row + 1, column + 1).setValue(currentValues.concat(selectedValues).join(separator));
        }
      }
    } else {
      range.setValue(value);
    }
  }
}

function updateCell(selectedValues) {
  return setValues_(selectedValues, true)
}

function fillCell(selectedValues) {
  setValues_(selectedValues, false)
}

dialog.html:

<!DOCTYPE html>
<html>
<head>
  <script>
    var data
    var formId = 'form'

    function drawForm() {
      if (!data) return
      var outputEl = document.getElementById(formId);
      var output = ''
      if (Object.prototype.toString.call(data) === '[object Array]') {
        for (var i = 0; i < data.length; i++) {
          if (data[i].length == 1) {
            if (data[i][0] == '') continue;
            output += '<div>'
            output += '<input type="checkbox" name="' + data[i][0] + '" value="' + data[i][0] + '">'
            output += data[i][0]
            output += ' <button type="button" onclick="addValue(\'' + data[i][0] + '\')">+</button>'
            output += ' <span id="count_' + data[i][0] + '"></span>'
            output += '</div>'
          } else if (data[i].length > 1) {
            if (data[i][0] == '') continue;
            output += '<div>'
            output += '<input type="checkbox" name="' + data[i][0] + '" value="' + data[i][0] + '">'
            output += data[i][0] + ' &ndash; '
            for (var j = 1; j < data[i].length; j++) {
              if (data[i][j] == '') continue
              output += data[i][j] + '; '
            }
            output += ' <button type="button" onclick="addValue(\'' + data[i][0] + '\')">+</button>'
            output += ' <span id="count_' + data[i][0] + '"></span>'
            output += '</div>'
          }
        }
      } else {
        output += '<p>This cell has no <a href="https://support.google.com/drive/answer/139705?hl=en">Data validation</a>.</p>';
      }

      outputEl.innerHTML = output
    }

    var onData = function (result) {
      data = result
      drawForm()
    }
    google.script.run.withSuccessHandler(onData).getValidationData();

    function getSelectedValues() {
      var form = document.getElementById(formId);
      var checkboxes = form.querySelectorAll('input[type="checkbox"]:checked');
      var selectedValues = [];
      checkboxes.forEach(function (checkbox) {
        var count = parseInt(document.getElementById('count_' + checkbox.value).textContent) || 1;
        for (var i = 0; i < count; i++) {
          selectedValues.push(checkbox.value);
        }
      });
      return selectedValues;
    }

    function set() {
      google.script.run.withSuccessHandler(x => {
      }).fillCell(getSelectedValues())
    }

    function update() {
      google.script.run.withSuccessHandler(x => {
      }).updateCell(getSelectedValues())
    }

    function reset() {
      drawForm()
    }

    function addValue(value) {
      var countSpan = document.getElementById('count_' + value);
      var count = parseInt(countSpan.textContent) || 0;
      countSpan.textContent = count + 1;
    }

  </script>
</head>
<body>
  <div style='position:fixed; padding-top: 10px; background-color: white; height: 30px; width: 100%; top: 0;'>
    <input type="button" value="Set" onclick="set()" />
    <input type="button" value="Update" onclick="update()" />
    <input type="button" value="Reset" onclick="reset()" />
    <input type="button" value="Refresh validation" onclick="google.script.run.showDialog()" />
  </div>
  <div style="font-family: sans-serif; padding-top: 30px;">
    <form id="form" name="form">

    </form>
  </div>
</body>
</html>

@Joshben88
Copy link

Thanks @brew-guy, awesome work - exactly what I was hoping for!

@brew-guy
Copy link

brew-guy commented Jul 19, 2024

All credit due to Claude I showed it the existing code pieces and told it what we needed and its first shot is what you got 🤘

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment