it-swarm.com.ru

Как вы делаете динамические/зависимые выпадающие в Google Sheets?

Как получить столбец подкатегории для заполнения раскрывающегося списка на основе значения, выбранного в раскрывающемся списке основной категории на листах Google?

Я погуглил и не смог найти хороших решений, поэтому хотел поделиться своим. Пожалуйста, смотрите мой ответ ниже.

37
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

Вы должны быть хороши, чтобы пойти после этого!

23
tarheel

Заметка

Сценарии имеют ограничение: он обрабатывает до 500 значений в одном раскрывающемся списке.

Новый скрипт 201801

Сценарий был выпущен в январе 2018 года. Пожалуйста, смотрите:

  1. Главная страница с инструкциями и демоверсией, где вы можете задать вопрос.
  2. страница GitHub с инструкциями и исходным кодом.

Улучшения:

  1. Ускорить
  2. Обрабатывает несколько правил на 1 листе
  3. Связать другие листы как исходные данные.
  4. Пользовательский порядок столбцов раскрывающихся списков

Старый сценарий <201801

Версии скрипта

  1. т.1 .
  2. т.2. 2016-03 . Улучшено: работает с дубликатами в любой категории. Например, если у нас есть 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

Здесь у нас есть четыре уровня, каждое значение повторяется. Обратите внимание, что 2 столбца справа от данных зарезервированы, поэтому не вводите и не вставляйте туда никаких данных.


Первая простая проверка данных (DV)

Подготовьте список уникальных значений. В нашем примере это список Planets. Найдите свободное место на листе с данными и вставьте формулу: =unique(A:A) В главном списке выберите первый столбец, с которого начнется DV. Перейдите в Данные> Проверка и выберите диапазон с уникальным списком.

 4 columns right from data


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

Вот набор переменных, которые нужно изменить, вы найдете их в скрипте:

  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

Я предлагаю всем, кто хорошо знает сценарии, присылайте свои изменения в этот код. Я думаю, есть более простой способ найти список проверки и заставить скрипт работать быстрее.

8
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 значения.
  • Нет ограничений на вложенные значения параметров (я использовал метод getSheetValues ​​со значением -1).

Итак, как его использовать:

  1. Создайте лист, где вы будете иметь вложенные селекторы
  2. Перейдите в «Инструменты»> «Редактор скриптов…» и выберите опцию «Пустой проект»
  3. Вставьте код, прикрепленный к этому ответу
  4. Измените первые 3 переменные скрипта, установив ваши значения и сохраните их
  5. Создайте один лист в этом же документе для каждого возможного значения «корневого селектора». Они должны быть названы как значение + указанный суффикс.

Наслаждайтесь!

2
JavierCane

Правка: ответ ниже может быть удовлетворительным, но он имеет некоторые недостатки:

  1. Есть заметная пауза для запуска скрипта. У меня задержка 160 мс, и этого достаточно, чтобы раздражать.

  2. Он работает, создавая новый диапазон каждый раз, когда вы редактируете данную строку. Это дает «недопустимое содержимое» для предыдущих записей иногда 

Я надеюсь, что другие могут почистить это несколько.

Вот еще один способ сделать это, который сэкономит вам массу именования диапазона:

Три листа на листе: назовите их Main, List и DRange (для динамического диапазона.) На главном листе столбец 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)

Это возвращает самую последнюю временную метку.

А2-А4 имеют варианты:

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

с увеличением 2 на каждую ячейку справа.

При запуске от А2 до А4 будут выбраны текущие группы, роды и виды.

Ниже каждого из них приведена команда фильтра, примерно такая:

= Уникальный (фильтр (Bot_Name, REGEXMATCH (Bot_Name, С1)))

Эти фильтры заполнят блок ниже соответствующими записями содержимого верхней ячейки.

Фильтры могут быть изменены в соответствии с вашими потребностями и форматом вашего списка.

Back to Main: проверка данных в Main выполняется с использованием диапазонов из 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
      }
    }
  }
}

Оригинальная презентация на YouTube, которая дала мне большую часть компонента метки времени onEdit: https://www.youtube.com/watch?v=RDK8rjdE85Y

2
Sherwood Botsford

Продолжая развитие этого решения, я повысил ставку, добавив поддержку нескольких корневых выделений и более глубоких вложенных выделений. Это дальнейшее развитие решения 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
Andrew Roberts