web-dev-qa-db-fra.com

Comment effectuez-vous des menus déroulants dynamiques/dépendants dans Google Sheets?

Comment obtenez-vous une colonne de sous-catégorie pour remplir un menu déroulant en fonction de la valeur sélectionnée dans le menu déroulant de catégorie principale dans les feuilles Google?

J'ai cherché sur Google et je ne trouvais pas de bonne solution. Je voulais donc partager la mienne. S'il vous plaît voir ma réponse ci-dessous.

37
tarheel

Vous pouvez commencer avec une feuille Google configurée avec une page principale et une liste déroulante comme indiqué ci-dessous.

Vous pouvez configurer la liste déroulante de la première colonne via les invites normales du menu Données> Validations.

Page d'accueil

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

Page source déroulante

Source page for all of the sub-categories needed

Après cela, vous devez configurer un script avec le nom onEdit. (Si vous n'utilisez pas ce nom, la méthode getActiveRange () ne retournera que la cellule A1)

Et utilisez le code fourni ici:

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();
  } 
  }
}

Après cela, configurez un déclencheur dans l'écran de l'éditeur de script en sélectionnant Edition> Déclencheurs de projet actuels. Cela fera apparaître une fenêtre vous permettant de sélectionner différents menus déroulants pour finir par ceci: 

Trigger set up

Vous devriez être bon pour aller après cela!

23
tarheel

Remarque

Les scripts ont une limite: il gère jusqu'à 500 valeurs dans une seule liste déroulante.

Nouveau script. 201801

Le script a été publié en janvier 2018. Voir:

  1. La page principale avec les instructions et la démo, où vous pouvez poser une question.
  2. Page GitHub avec les instructions et le code source.

Améliorations:

  1. Accélérer
  2. Gère plusieurs règles dans 1 feuille
  3. Liez d'autres feuilles en tant que données source.
  4. Ordre personnalisé des colonnes des listes déroulantes

Vieux script. <201801

Versions du script

  1. v.1 .
  2. v.2. 2016-03 . Amélioré: fonctionne avec les doublons de toutes les catégories. Par exemple, si nous avons list1 avec des modèles de voiture et list2 avec des couleurs. La couleur peut être répétée dans n'importe quel modèle.
  3. v3. 2017-01 . Amélioré: pas d'erreur lorsque la seule valeur est entrée.
  4. Nouvelle version: 2018-02. Voir l'article ici .

Cette solution n’est pas parfaite, mais elle offre certains avantages:

  1. Vous permet de créer plusieurs listes déroulantes
  2. Donne plus de contrôle
  3. Les données source sont placées sur la seule feuille, il est donc facile de les modifier.

Tout d’abord, voici exemple de travail , afin que vous puissiez le tester avant d’aller plus loin.

 When you choose one option, script makes new validation rule

Mon plan:

  1. Préparer les données
  2. Faites la première liste comme d'habitude: Data > Validation
  3. Ajouter un script, définir des variables
  4. Terminé!

Préparer les données

Les données ressemblent à une seule table avec toutes les variantes possibles. Il doit être placé sur une feuille séparée pour pouvoir être utilisé par le script. Regardez cet exemple:

 Sourse Data

Nous avons ici quatre niveaux, chaque valeur se répète. Notez que 2 colonnes à droite des données sont réservées, donc ne tapez/collez pas de données.


Première validation de données simple (DV)

Préparez une liste de valeurs uniques. Dans notre exemple, il s’agit d’une liste de Planètes. Recherchez un espace libre sur la feuille avec les données et collez la formule suivante: =unique(A:A) Sur votre feuille d’aide, sélectionnez la première colonne, où commence DV. Allez dans Données> Validation et sélectionnez une plage avec une liste unique.

 4 columns right from data


Script

Collez ce code dans l'éditeur 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);
  
}

Voici un ensemble de variables à modifier, vous les trouverez dans le 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

Je suggère à tous ceux qui connaissent bien les scripts d'envoyer vos modifications à ce code. J'imagine qu'il existe un moyen plus simple de trouver une liste de validation et d'accélérer l'exécution du script.

8
Max Makhrov

Ici vous avez une autre solution basée sur celle fournie par @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() );
}

Il présente certains avantages par rapport à l’autre approche:

  • Vous n'avez pas besoin de modifier le script chaque fois que vous ajoutez une "option racine". Il vous suffit de créer une nouvelle feuille avec les options imbriquées de cette option racine.
  • J'ai refactoré le script en fournissant plus de noms sémantiques pour les variables, etc. De plus, j'ai extrait certains paramètres de variables afin de faciliter l'adaptation à votre cas particulier. Il suffit de définir les 3 premières valeurs.
  • Il n'y a pas de limite aux valeurs des options imbriquées (j'ai utilisé la méthode getSheetValues ​​avec la valeur -1).

Alors, comment l'utiliser:

  1. Créez la feuille où vous aurez les sélecteurs imbriqués
  2. Allez dans "Outils"> "Editeur de script…" et sélectionnez l'option "Projet vide"
  3. Collez le code attaché à cette réponse
  4. Modifiez les 3 premières variables du script définissant vos valeurs et enregistrez-le.
  5. Créez une feuille dans ce même document pour chaque valeur possible du "sélecteur racine". Ils doivent être nommés comme la valeur + le suffixe spécifié.

Prendre plaisir!

2
JavierCane

Edit: La réponse ci-dessous peut être satisfaisante, mais elle présente quelques inconvénients:

  1. Il y a une pause notable pour l'exécution du script. Je suis sur une latence de 160 ms, et c'est assez pour être ennuyeux.

  2. Cela fonctionne en créant une nouvelle plage chaque fois que vous éditez une ligne donnée. Cela donne un 'contenu invalide' aux entrées précédentes parfois 

J'espère que les autres peuvent nettoyer cela un peu.

Voici une autre façon de le faire, qui vous évite des tonnes de noms de plages:

Trois feuilles de la feuille de calcul: appelez-les principal, Liste et DRange (pour la plage dynamique.) Sur la feuille principale, la colonne 1 contient un horodatage. Cet horodatage est modifié onEdit.

Sur Liste, vos catégories et sous-catégories sont organisées comme une simple liste. J'utilise ceci pour l'inventaire des plantes de ma ferme forestière, donc ma liste est la suivante:

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

Où | indique la séparation en colonnes.
Pour plus de commodité, j’ai également utilisé les en-têtes comme noms de plages nommées.

DRrange A1 a la formule 

=Max(Main!A2:A1000)

Ceci retourne l'horodatage le plus récent.

A2 à A4 ont des variations sur:

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

les 2 étant incrémentés pour chaque cellule à droite.

Si vous utilisez A2 à A4, le groupe, le genre et l’espèce actuellement sélectionnés seront sélectionnés.

Au-dessous de chacune d’elles, se trouve une commande de filtrage qui ressemble à ceci:

= unique (filtre (nom_botte, REGEXMATCH (nom_botte, C1)))

Ces filtres rempliront un bloc ci-dessous avec des entrées correspondantes au contenu de la cellule supérieure.

Les filtres peuvent être modifiés pour répondre à vos besoins et au format de votre liste.

Retour au menu principal: la validation des données dans le logiciel principal s'effectue à l'aide de plages de DRange.

Le script que j'utilise: 

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
      }
    }
  }
}

Présentation originale sur Youtube qui m'a fourni l'essentiel du composant d'horodatage onEdit: https://www.youtube.com/watch?v=RDK8rjdE85Y

2
Sherwood Botsford

Poursuivant l'évolution de cette solution, j'ai relevé le défi en ajoutant la prise en charge de plusieurs sélections de racines et de sélections imbriquées plus profondes. Il s'agit d'un développement ultérieur de la solution de JavierCane (qui à son tour est basée sur celle de 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()

Comme dit Javier: 

  • Créez la feuille où vous aurez les sélecteurs imbriqués
  • Allez dans "Outils"> "Editeur de script…" et sélectionnez l'option "Projet vide"
  • Collez le code attaché à cette réponse
  • Modifiez les constantes en haut du script en configurant vos valeurs Et enregistrez-les
  • Créez une feuille dans ce même document pour chaque valeur possible de Le "sélecteur de racine". Ils doivent être nommés comme la valeur + le suffixe spécifié

Et si vous vouliez le voir en action, j'ai créé une fiche de démonstration et vous pouvez voir le code si vous en prenez une copie.

1
Andrew Roberts