it-swarm.dev

Como você faz suspensões dinâmicas/dependentes no Planilhas Google?

Como você obtém uma coluna de subcategoria para preencher uma lista suspensa com base no valor selecionado na lista suspensa da categoria principal no google sheets?

Eu pesquisei por aí e não encontrei nenhuma boa solução, portanto queria compartilhar a minha. Por favor, veja minha resposta abaixo.

37
tarheel

Você pode começar com uma planilha do google configurada com uma página principal e uma página de origem como mostrada abaixo.

Você pode configurar a primeira coluna suspensa através dos prompts normais do menu Dados> Validações.

Página principal

Main Page with the drop down for the first column already populated.

Página de origem suspensa

Source page for all of the sub-categories needed

Depois disso, você precisa configurar um script com o nome onEdit. (Se você não usar esse nome, o getActiveRange () não fará nada, mas retornará a célula A1)

E use o código fornecido aqui:

function onEdit() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = SpreadsheetApp.getActiveSheet();
  var myRange = SpreadsheetApp.getActiveRange();
  var dvSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Categories");
  var option = new Array();
  var startCol = 0;

  if(sheet.getName() == "Front Page" && myRange.getColumn() == 1 && myRange.getRow() > 1){
    if(myRange.getValue() == "Category 1"){
      startCol = 1;
    } else if(myRange.getValue() == "Category 2"){
      startCol = 2;
    } else if(myRange.getValue() == "Category 3"){
      startCol = 3;
    } else if(myRange.getValue() == "Category 4"){
      startCol = 4;
    } else {
      startCol = 10
    }

  if(startCol > 0 && startCol < 10){
    option = dvSheet.getSheetValues(3,startCol,10,1);
    var dv = SpreadsheetApp.newDataValidation();
    dv.setAllowInvalid(false);  
    //dv.setHelpText("Some help text here");
    dv.requireValueInList(option, true);
    sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).setDataValidation(dv.build());
   }

  if(startCol == 10){
    sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).clearDataValidations();
  } 
  }
}

Depois disso, configure um acionador na tela do editor de script acessando Editar> Acionadores do projeto atual. Isso abrirá uma janela para que você selecione várias listas suspensas para acabar com isso: 

Trigger set up

Você deve ser bom para ir atrás disso!

23
tarheel

Nota

Os scripts têm um limite: ele manipula até 500 valores em uma única lista suspensa.

Novo Script. 201801

O roteiro foi lançado em janeiro de 2018. Por favor, veja:

  1. A página principal com instruções e demonstração, onde você pode fazer uma pergunta.
  2. Página do GitHub com instruções e código fonte.

Melhorias:

  1. Acelerar
  2. Lida com várias regras em uma folha
  3. Vincule outras folhas como dados de origem.
  4. Ordem de coluna personalizada de listas suspensas

Roteiro Antigo. <201801

Versões do script

  1. v.1 .
  2. v.2. 2016-03 . Melhorado: funciona com duplicatas em qualquer categoria. Por exemplo, se tivermos list1 com modelos de carros e list2 com cores. A cor pode ser repetida em qualquer modelo.
  3. v3. 2017-01 . Melhorado: nenhum erro quando o único valor é inserido.
  4. Versão mais recente: 2018-02. Veja o artigo aqui .

Esta solução não é perfeita, mas dá alguns benefícios:

  1. Deixe você fazer várias listas suspensas
  2. Dá mais controle
  3. Os dados de origem são colocados na única folha, por isso é simples de editar

Primeiro de tudo, aqui está exemplo de trabalho , então você pode testá-lo antes de prosseguir.

When you choose one option, script makes new validation rule

Meu plano:

  1. Prepare dados
  2. Faça a primeira lista como de costume: Data > Validation
  3. Adicionar Script, defina algumas variáveis
  4. Feito!

Preparar dados

Os dados parecem com uma única tabela com todas as variantes possíveis dentro dela. Ele deve estar localizado em uma folha separada, para que possa ser usado pelo script. Veja este exemplo:

Sourse Data

Aqui temos quatro níveis, cada valor se repete. Observe que 2 colunas à direita dos dados são reservadas, portanto, não digite/cole nenhum dado.


Primeira Validação Simples de Dados (DV)

Prepare uma lista de valores exclusivos. Em nosso exemplo, é uma lista de Planets. Encontre espaço livre na planilha com dados e cole a fórmula: =unique(A:A) Em sua planilha principal, selecione a primeira coluna, onde DV será iniciado. Vá para Dados> Validação e selecione o intervalo com uma lista exclusiva.

4 columns right from data


Roteiro

Cole este código no editor de script:

function SmartDataValidation(event) 
{
  //--------------------------------------------------------------------------------------
  // The event handler, adds data validation for the input parameters
  //--------------------------------------------------------------------------------------
  
  
  // Declare some variables:
  //--------------------------------------------------------------------------------------
  var TargetSheet = 'Main' // name of the sheet where you want to verify the data
  var LogSheet = 'Data' // name of the sheet with information
  var NumOfLevels = 4 // number of associated drop-down list levels
  var lcol = 2; // number of the leftmost column, in which the changes are checked; A = 1, B = 2, etc.
  var lrow = 2; // line number from which the rule will be valid
  //--------------------------------------------------------------------------------------
  
  //	===================================   key variables	 =================================
  //
  //		ss			sheet we change (TargetSheet)
  //			br				range to change
  //			scol			number of column to edit
  //			srow			number of row to edit	
  //			CurrentLevel	level of drop-down, which we change
  //			HeadLevel		main level
  //			r				current cell, which was changed by user
  //			X         		number of levels could be checked on the right
  //
  //		ls			Data sheet (LogSheet)
  //
  //    ======================================================================================
  
  
  // [ 01 ].Track sheet on which an event occurs
  var ts = event.source.getActiveSheet();
  var sname = ts.getName();
  
  if (sname == TargetSheet) 
  {
    
    // ss -- is the current book
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    
    // [ 02 ]. If the sheet name is the same, you do business...
    var ls = ss.getSheetByName(LogSheet); // data sheet
    
    // [ 03 ]. Determine the level
    
    //-------------- The changing sheet --------------------------------
    var br = event.source.getActiveRange();
    var scol = br.getColumn(); // the column number in which the change is made
    var srow = br.getRow() // line number in which the change is made
    // Test if column fits
    if (scol >= lcol) 
    {
      // Test if row fits
      if (srow >= lrow) 
      {  
        var CurrentLevel = scol-lcol+2;
        // adjust the level to size of
        // range that was changed
        var ColNum = br.getLastColumn() - scol + 1;
        CurrentLevel = CurrentLevel + ColNum - 1; 
        
        // also need to adjust the range 'br'
        if (ColNum > 1) 
        {
          br = br.offset(0,ColNum-1);
        } // wide range
        
        var HeadLevel = CurrentLevel - 1; // main level
        
        // split rows
        var RowNum = br.getLastRow() - srow + 1;
        
        var X = NumOfLevels - CurrentLevel + 1;

        
        // the current level should not exceed the number of levels, or 
        // we go beyond the desired range
        if (CurrentLevel <= NumOfLevels )	
        {
          // determine columns on the sheet "Data"
          var KudaCol = NumOfLevels + 2
          var KudaNado = ls.getRange(1, KudaCol);
          var lastRow = ls.getLastRow(); // get the address of the last cell
          var ChtoNado = ls.getRange(1, KudaCol, lastRow, KudaCol);

          // ============================================================================= > loop >				
          for (var j = 1; j <= RowNum; j++)
          {		
            for (var k = 1; k <= X; k++) 
            {
               
              HeadLevel = HeadLevel + k - 1; // adjust parent level
              CurrentLevel = CurrentLevel + k - 1; // adjust current level
              
              var r = br.getCell(j,1).offset(0,k-1,1);
              var SearchText = r.getValue(); // searched text

              // if anything is choosen!
              if (SearchText != '') 
              {
                
                //-------------------------------------------------------------------
                
                // [ 04 ]. define variables to costumize data
                // for future data validation
                //--------------- Sheet with data --------------------------           
                // combine formula 
                // repetitive parts
                var IndCodePart = 'INDIRECT("R1C' + HeadLevel + ':R' + lastRow + 'C';
                IndCodePart = IndCodePart + HeadLevel + '",0)';
                // the formula
                var code = '=UNIQUE(INDIRECT("R" & MATCH("';
                code = code + SearchText + '",';
                code = code + IndCodePart;
                code = code + ',0) & "C" & "' + CurrentLevel
                code = code + '" & ":" & "R" & COUNTIF(';
                code = code + IndCodePart;   
                code = code + ',"' + SearchText + '") + MATCH("';
                code = code + SearchText + '";';
                code = code + IndCodePart;
                code = code + ',0) - 1'; 
                code = code + '& "C" & "' ;   
                code = code + CurrentLevel + '",0))';
                // Got it! Now we have to paste formula
                
                KudaNado.setFormulaR1C1(code);   
                // get required array
                var values = [];
                for (var i = 1; i <= lastRow; i++) 
                {
                  var currentValue = ChtoNado.getCell(i,1).getValue();
                  if (currentValue != '') 
                  { 
                    values.Push(currentValue);
                  } 
                  else 
                  {
                    var Variants = i-1; // number of possible values
                    i = lastRow; // exit loop
                  }       
                }
                //-------------------------------------------------------------------
                
                // [ 05 ]. Build daya validation rule
                var cell = r.offset(0,1);
                var rule = SpreadsheetApp
                .newDataValidation()
                .requireValueInList(values, true)
                .setAllowInvalid(false)
                .build();
                cell.setDataValidation(rule); 
                if (Variants == 1) 
                {
                  cell.setValue(KudaNado.getValue());		
                } // the only value
                else
                {
                  k = X+1;
                } // stop the loop through columns
                
                
              } // not blanc cell
              else
              {
                // kill extra data validation if there were 
                // columns on the right
                if (CurrentLevel <= NumOfLevels ) 
                {
                  for (var i = 1; i <= NumOfLevels; i++) 
                  {
                    var cell = r.offset(0,i);
                    // clean
                    cell.clear({contentsOnly: true});
                    // get rid of validation
                    cell.clear({validationsOnly: true});
                  }
                } // correct level
              } // empty row
            } // loop by cols
          } // loop by rows
          // ============================================================================= < loop <	
          
        } // wrong level
        
      } // rows
    } // columns... 
  } // main sheet
}

function onEdit(event) 
{
  
  SmartDataValidation(event);
  
}

Aqui está um conjunto de variáveis ​​que serão alteradas, você as encontrará no script:

  var TargetSheet = 'Main' // name of the sheet where you want to verify the data
  var LogSheet = 'Data' // name of the sheet with information
  var NumOfLevels = 4 // number of associated drop-down list levels
  var lcol = 2; // leftmost column, in which the changes are checked; A = 1, B = 2, etc.
  var lrow = 2; // line number from which the rule will be valid

Eu sugiro que todos, que conhecem bem os scripts, enviem suas edições para esse código. Eu acho, há uma maneira mais simples de encontrar a lista de validação e fazer o script rodar mais rápido.

8
Max Makhrov

Aqui você tem outra solução baseada na fornecida por @tarheel

function onEdit() {
    var sheetWithNestedSelectsName = "Sitemap";
    var columnWithNestedSelectsRoot = 1;
    var sheetWithOptionPossibleValuesSuffix = "TabSections";

    var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    var activeSheet = SpreadsheetApp.getActiveSheet();

    // If we're not in the sheet with nested selects, exit!
    if ( activeSheet.getName() != sheetWithNestedSelectsName ) {
        return;
    }

    var activeCell = SpreadsheetApp.getActiveRange();

    // If we're not in the root column or a content row, exit!
    if ( activeCell.getColumn() != columnWithNestedSelectsRoot || activeCell.getRow() < 2 ) {
        return;
    }

    var sheetWithActiveOptionPossibleValues = activeSpreadsheet.getSheetByName( activeCell.getValue() + sheetWithOptionPossibleValuesSuffix );

    // Get all possible values
    var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues.getSheetValues( 1, 1, -1, 1 );

    var possibleValuesValidation = SpreadsheetApp.newDataValidation();
    possibleValuesValidation.setAllowInvalid( false );
    possibleValuesValidation.requireValueInList( activeOptionPossibleValues, true );

    activeSheet.getRange( activeCell.getRow(), activeCell.getColumn() + 1 ).setDataValidation( possibleValuesValidation.build() );
}

Tem alguns benefícios sobre a outra abordagem:

  • Você não precisa editar o script sempre que adicionar uma "opção raiz". Você só precisa criar uma nova planilha com as opções aninhadas dessa opção raiz.
  • Eu refatorei o script fornecendo mais nomes semânticos para as variáveis ​​e assim por diante. Além disso, eu extraí alguns parâmetros para variáveis, a fim de facilitar a adaptação ao seu caso específico. Você só precisa definir os 3 primeiros valores.
  • Não há limite de valores de opções aninhadas (usei o método getSheetValues ​​com o valor -1).

Então, como usá-lo:

  1. Crie a planilha onde você terá os seletores aninhados
  2. Vá até "Ferramentas"> "Editor de scripts…" e selecione a opção "Projeto em branco"
  3. Cole o código anexado a esta resposta
  4. Modifique as 3 primeiras variáveis ​​do script configurando seus valores e salve-o
  5. Crie uma folha dentro deste mesmo documento para cada valor possível do "seletor de raiz". Eles devem ser nomeados como o valor + o sufixo especificado.

Apreciar!

2
JavierCane

Edit: A resposta abaixo pode ser satisfatória, mas tem algumas desvantagens:

  1. Há uma pausa perceptível para a execução do script. Eu estou em uma latência de 160 ms, e é o suficiente para ser chato.

  2. Ele funciona criando um novo intervalo cada vez que você edita uma determinada linha. Isto dá um 'conteúdo inválido' para entradas anteriores algum tempo 

Espero que outros possam limpar isso um pouco.

Aqui está outra maneira de fazer isso, isso poupa uma tonelada de nomes de intervalo:

Três folhas na planilha: chamá-las de Principal, Lista e DRange (para faixa dinâmica.) Na planilha principal, a coluna 1 contém um registro de data e hora. Este carimbo de hora é modificado emEdit.

Na lista suas categorias e subcategorias são organizadas como uma lista simples. Eu estou usando isso para o inventário da planta na minha fazenda, então minha lista é assim:

Group   | Genus | Bot_Name
Conifer | Abies | Abies balsamea
Conifer | Abies | Abies concolor
Conifer | Abies | Abies lasiocarpa var bifolia
Conifer | Pinus | Pinus ponderosa
Conifer | Pinus | Pinus sylvestris
Conifer | Pinus | Pinus banksiana
Conifer | Pinus | Pinus cembra
Conifer | Picea | Picea pungens
Conifer | Picea | Picea glauca
Deciduous | Acer | Acer ginnala
Deciduous | Acer | Acer negundo
Deciduous | Salix | Salix discolor
Deciduous | Salix | Salix fragilis
...

Onde | indica separação em colunas.
Por conveniência eu também usei os cabeçalhos como nomes para intervalos nomeados.

DRrange A1 tem a fórmula 

=Max(Main!A2:A1000)

Isso retorna o timestamp mais recente.

A2 a A4 tem variações sobre:

=vlookup($A$1,Inventory!$A$1:$E$1000,2,False) 

com o 2 sendo incrementado para cada célula à direita.

Na execução de A2 a A4 terá o Grupo, Gênero e Espécie atualmente selecionados.

Abaixo de cada um destes, é um comando de filtro algo como isto:

= unique (filter (Bot_Name, REGEXMATCH (Bot_Name, C1)))

Esses filtros preencherão um bloco abaixo com entradas correspondentes ao conteúdo da célula superior.

Os filtros podem ser modificados para atender às suas necessidades e ao formato da sua lista.

Voltar ao Main: A validação de dados no Main é feita usando intervalos do DRange.

O script que eu uso: 

function onEdit(event) {

  //SETTINGS
  var dynamicSheet='DRange'; //sheet where the dynamic range lives
  var tsheet = 'Main'; //the sheet you are monitoring for edits
  var lcol = 2; //left-most column number you are monitoring; A=1, B=2 etc
  var rcol = 5; //right-most column number you are monitoring
  var tcol = 1; //column number in which you wish to populate the timestamp
  //

  var s = event.source.getActiveSheet();
  var sname = s.getName();
  if (sname == tsheet) {
    var r = event.source.getActiveRange();
    var scol = r.getColumn();  //scol is the column number of the edited cell
    if (scol >= lcol && scol <= rcol) {
      s.getRange(r.getRow(), tcol).setValue(new Date());
      for(var looper=scol+1; looper<=rcol; looper++) {
         s.getRange(r.getRow(),looper).setValue(""); //After edit clear the entries to the right
      }
    }
  }
}

Apresentação original no Youtube que me deu a maior parte do componente de registro de data/hora onEdit: https://www.youtube.com/watch?v=RDK8rjdE85Y

2
Sherwood Botsford

Continuando a evolução desta solução, eu elevei a aposta adicionando suporte a várias seleções de raiz e seleções aninhadas mais profundas. Este é um desenvolvimento adicional da solução do JavierCane (que por sua vez foi construída sobre o tarheel). 

/**
 * "on edit" event handler
 *
 * Based on JavierCane's answer in 
 * 
 *   http://stackoverflow.com/questions/21744547/how-do-you-do-dynamic-dependent-drop-downs-in-google-sheets
 *
 * Each set of options has it own sheet named after the option. The 
 * values in this sheet are used to populate the drop-down.
 *
 * The top row is assumed to be a header.
 *
 * The sub-category column is assumed to be the next column to the right.
 *
 * If there are no sub-categories the next column along is cleared in 
 * case the previous selection did have options.
 */

function onEdit() {

  var NESTED_SELECTS_SHEET_NAME = "Sitemap"
  var NESTED_SELECTS_ROOT_COLUMN = 1
  var SUB_CATEGORY_COLUMN = NESTED_SELECTS_ROOT_COLUMN + 1
  var NUMBER_OF_ROOT_OPTION_CELLS = 3
  var OPTION_POSSIBLE_VALUES_SHEET_SUFFIX = ""
  
  var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  var activeSheet = SpreadsheetApp.getActiveSheet()
  
  if (activeSheet.getName() !== NESTED_SELECTS_SHEET_NAME) {
  
    // Not in the sheet with nested selects, exit!
    return
  }
  
  var activeCell = SpreadsheetApp.getActiveRange()
  
  // Top row is the header
  if (activeCell.getColumn() > SUB_CATEGORY_COLUMN || 
      activeCell.getRow() === 1 ||
      activeCell.getRow() > NUMBER_OF_ROOT_OPTION_CELLS + 1) {

    // Out of selection range, exit!
    return
  }
  
  var sheetWithActiveOptionPossibleValues = activeSpreadsheet
    .getSheetByName(activeCell.getValue() + OPTION_POSSIBLE_VALUES_SHEET_SUFFIX)
  
  if (sheetWithActiveOptionPossibleValues === null) {
  
    // There are no further options for this value, so clear out any old
    // values
    activeSheet
      .getRange(activeCell.getRow(), activeCell.getColumn() + 1)
      .clearDataValidations()
      .clearContent()
      
    return
  }
  
  // Get all possible values
  var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues
    .getSheetValues(1, 1, -1, 1)
  
  var possibleValuesValidation = SpreadsheetApp.newDataValidation()
  possibleValuesValidation.setAllowInvalid(false)
  possibleValuesValidation.requireValueInList(activeOptionPossibleValues, true)
  
  activeSheet
    .getRange(activeCell.getRow(), activeCell.getColumn() + 1)
    .setDataValidation(possibleValuesValidation.build())
    
} // onEdit()

Como Javier diz: 

  • Crie a planilha onde você terá os seletores aninhados
  • Vá até "Ferramentas"> "Editor de scripts…" e selecione a opção "Projeto em branco"
  • Cole o código anexado a esta resposta
  • Modifique as constantes na parte superior do script, definindo seus valores E salve-os
  • Crie uma folha dentro deste mesmo documento para cada valor possível de O "seletor de raiz". Eles devem ser nomeados como o valor + o sufixo Especificado.

E se você quisesse ver isso em ação eu criei uma folha de demonstração e você pode ver o código se você pegar uma cópia.

1
Andrew Roberts