web-dev-qa-db-ja.com

Googleスプレッドシートで動的/依存ドロップダウンをどのように行いますか?

Googleシートのメインカテゴリドロップダウンで選択された値に基づいてドロップダウンを設定するサブカテゴリ列を取得するにはどうすればよいですか?

私はグーグルで検索し、良い解決策を見つけることができなかったので、自分のソリューションを共有したいと考えました。以下の私の答えをご覧ください。

39
tarheel

以下に示すように、メインページとドロップダウンソースページで設定されたGoogleシートから開始できます。

通常の[データ]> [検証]メニューのプロンプトを使用して、最初の列のドロップダウンを設定できます。

メインページ

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

ソースページのドロップダウン

Source page for all of the sub-categories needed

その後、スクリプトを設定する必要があります名前付きonEdit。 (その名前を使用しない場合、getActiveRange()はセルA1を返すだけです)

そして、ここで提供されるコードを使用します。

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

その後、[編集]> [現在のプロジェクトトリガー]に移動して、スクリプトエディター画面でトリガーを設定します。これにより、最終的にこれで終わるさまざまなドロップダウンを選択するためのウィンドウが表示されます。

Trigger set up

あなたはその後に行くのが良いはずです!

26
tarheel

スクリプトには制限があります。1つのドロップダウンリストで最大500個の値を処理します。

新しいスクリプト。 201801

このスクリプトは2018年1月にリリースされました。以下を参照してください。

  1. メインページ 指示とデモがあり、質問することができます。
  2. GitHubページ 指示とソースコード付き。

改善点:

  1. スピードアップ
  2. 1つのシートで複数のルールを処理
  3. 他のシートをソースデータとしてリンクします。
  4. ドロップダウンリストのカスタム列順序

古いスクリプト。 <201801

スクリプトのバージョン

  1. v.1
  2. v.2。2016- 。改善:任意のカテゴリの重複で動作します。たとえば、車のモデルを含むlist1と色のあるlist2があるとします。色はどのモデルでも繰り返すことができます。
  3. v3。2017-01 。改善:値のみを入力してもエラーは発生しません。
  4. 最新バージョン:2018-02。 こちらの記事 をご覧ください。

このソリューションは完璧ではありませんが、いくつかの利点があります。

  1. 複数のドロップダウンリストを作成できます
  2. より多くの制御を提供します
  3. ソースデータは唯一のシートに配置されるため、簡単に編集できます

まず、ここに 作業例 があるので、先に進む前にテストできます。

When you choose one option, script makes new validation rule

私の計画:

  1. データを準備する
  2. 通常どおり最初のリストを作成します:Data > Validation
  3. スクリプトを追加し、いくつかの変数を設定します
  4. できた!

データの準備

データは、考えられるすべてのバリアントが内部にある単一のテーブルのように見えます。スクリプトで使用できるように、別のシートに配置する必要があります。この例を見てください:

Sourse Data

ここには4つのレベルがあり、各値が繰り返されます。データの右側の2列は予約されているため、データを入力したり貼り付けたりしないでください。


最初の単純なデータ検証(DV)

一意の値のリストを準備します。この例では、Planetsのリストです。データを含むシート上の空きスペースを見つけて、式を貼り付けます:=unique(A:A)メインシートで、DVが開始する最初の列を選択します。 [データ]> [検証]に移動し、一意のリストで範囲を選択します。

4 columns right from data


スクリプト

このコードをスクリプトエディターに貼り付けます。

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

変更する変数のセットは次のとおりです。スクリプトで見つけることができます。

  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

スクリプトを熟知しているすべての人が、このコードに編集内容を送信することをお勧めします。検証リストを見つけて、スクリプトをより速く実行する簡単な方法があると思います。

9
Max Makhrov

ここには、@ 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() );
}

他のアプローチに比べていくつかの利点があります。

  • 「ルートオプション」を追加するたびにスクリプトを編集する必要はありません。このルートオプションのネストされたオプションで新しいシートを作成するだけです。
  • 変数のセマンティック名などを提供するスクリプトをリファクタリングしました。さらに、特定のケースに簡単に適応できるように、変数にいくつかのパラメーターを抽出しました。最初の3つの値のみを設定する必要があります。
  • ネストされたオプション値の制限はありません(-1の値でgetSheetValuesメソッドを使用しました)。

だから、それを使用する方法:

  1. ネストされたセレクターがあるシートを作成します
  2. [ツール]> [スクリプトエディター…]に移動し、[ブランクプロジェクト]オプションを選択します
  3. この回答に添付されたコードを貼り付けてください
  4. 値を設定するスクリプトの最初の3つの変数を変更して保存します
  5. 「ルートセレクタ」の可能な値ごとに、この同じドキュメント内に1つのシートを作成します。これらは、値+指定されたサフィックスとして名前を付ける必要があります。

楽しい!

2
JavierCane

編集:以下の答えは満足のいくものかもしれませんが、いくつかの欠点があります。

  1. スクリプトの実行には目立った一時停止があります。私は160ミリ秒のレイテンシーにありますが、それはうんざりするほどです。

  2. 特定の行を編集するたびに新しい範囲を作成することで機能します。これにより、以前のエントリに「無効なコンテンツ」が与えられます時々

他の人がこれをいくらかクリーンアップできることを願っています。

これを行う別の方法があります。これにより、多くの範囲の命名が節約されます。

ワークシートの3つのシート:Main、List、およびDRange(ダイナミックレンジ用)と呼びます。Mainシートの列1にはタイムスタンプが含まれています。このタイムスタンプは、編集時に変更されます。

リストでは、カテゴリとサブカテゴリは単純なリストとして配置されます。私はこれを自分のツリーファームの植物インベントリに使用しているので、リストは次のようになります。

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

どこ|列への分離を示します。
便宜上、ヘッダーを名前付き範囲の名前としても使用しました。

DRrange A1には次の式があります

=Max(Main!A2:A1000)

これは、最新のタイムスタンプを返します。

A2からA4には、次のバリエーションがあります。

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

右側のセルごとに2が増分されます。

A2からA4を実行すると、現在選択されているグループ、属、および種があります。

これらのそれぞれの下には、次のようなフィルターコマンドがあります。

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

これらのフィルターは、一番上のセルのコンテンツに一致するエントリを下のブロックに追加します。

フィルターは、ニーズに合わせて、リストの形式に合わせて変更できます。

メインに戻る:メインのデータ検証は、DRangeの範囲を使用して行われます。

私が使用するスクリプト:

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

OnEditタイムスタンプコンポーネントのほとんどを提供してくれたオリジナルのYoutubeプレゼンテーション: https://www.youtube.com/watch?v=RDK8rjdE85Y

2

このソリューションの進化を継続するために、複数のルート選択とより深いネストされた選択のサポートを追加することで、アンティを向上させました。これは、JavierCaneのソリューション(さらに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()

ハビエルが言うように:

  • ネストされたセレクターがあるシートを作成します
  • [ツール]> [スクリプトエディター…]に移動し、[空白プロジェクト]オプションを選択します
  • この回答に添付されたコードを貼り付けてください
  • 値を設定するスクリプトの上部にある定数を変更して保存します
  • 「ルートセレクタ」の可能な値ごとに、この同じドキュメント内に1つのシートを作成します。これらは、値+指定されたサフィックスとして名前を付ける必要があります。

そして、あなたが実際にそれを見たいなら、私は デモシート を作成しました。そして、コピーを取ると、コードを見ることができます。

1
Andrew Roberts