Continuing the evolution of this solution I've upped the ante by adding support for multiple root selections and deeper nested selections. This is a further development of JavierCane's solution (which in turn built on tarheel's).
/**_x000D_
* "on edit" event handler_x000D_
*_x000D_
* Based on JavierCane's answer in _x000D_
* _x000D_
* http://stackoverflow.com/questions/21744547/how-do-you-do-dynamic-dependent-drop-downs-in-google-sheets_x000D_
*_x000D_
* Each set of options has it own sheet named after the option. The _x000D_
* values in this sheet are used to populate the drop-down._x000D_
*_x000D_
* The top row is assumed to be a header._x000D_
*_x000D_
* The sub-category column is assumed to be the next column to the right._x000D_
*_x000D_
* If there are no sub-categories the next column along is cleared in _x000D_
* case the previous selection did have options._x000D_
*/_x000D_
_x000D_
function onEdit() {_x000D_
_x000D_
var NESTED_SELECTS_SHEET_NAME = "Sitemap"_x000D_
var NESTED_SELECTS_ROOT_COLUMN = 1_x000D_
var SUB_CATEGORY_COLUMN = NESTED_SELECTS_ROOT_COLUMN + 1_x000D_
var NUMBER_OF_ROOT_OPTION_CELLS = 3_x000D_
var OPTION_POSSIBLE_VALUES_SHEET_SUFFIX = ""_x000D_
_x000D_
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()_x000D_
var activeSheet = SpreadsheetApp.getActiveSheet()_x000D_
_x000D_
if (activeSheet.getName() !== NESTED_SELECTS_SHEET_NAME) {_x000D_
_x000D_
// Not in the sheet with nested selects, exit!_x000D_
return_x000D_
}_x000D_
_x000D_
var activeCell = SpreadsheetApp.getActiveRange()_x000D_
_x000D_
// Top row is the header_x000D_
if (activeCell.getColumn() > SUB_CATEGORY_COLUMN || _x000D_
activeCell.getRow() === 1 ||_x000D_
activeCell.getRow() > NUMBER_OF_ROOT_OPTION_CELLS + 1) {_x000D_
_x000D_
// Out of selection range, exit!_x000D_
return_x000D_
}_x000D_
_x000D_
var sheetWithActiveOptionPossibleValues = activeSpreadsheet_x000D_
.getSheetByName(activeCell.getValue() + OPTION_POSSIBLE_VALUES_SHEET_SUFFIX)_x000D_
_x000D_
if (sheetWithActiveOptionPossibleValues === null) {_x000D_
_x000D_
// There are no further options for this value, so clear out any old_x000D_
// values_x000D_
activeSheet_x000D_
.getRange(activeCell.getRow(), activeCell.getColumn() + 1)_x000D_
.clearDataValidations()_x000D_
.clearContent()_x000D_
_x000D_
return_x000D_
}_x000D_
_x000D_
// Get all possible values_x000D_
var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues_x000D_
.getSheetValues(1, 1, -1, 1)_x000D_
_x000D_
var possibleValuesValidation = SpreadsheetApp.newDataValidation()_x000D_
possibleValuesValidation.setAllowInvalid(false)_x000D_
possibleValuesValidation.requireValueInList(activeOptionPossibleValues, true)_x000D_
_x000D_
activeSheet_x000D_
.getRange(activeCell.getRow(), activeCell.getColumn() + 1)_x000D_
.setDataValidation(possibleValuesValidation.build())_x000D_
_x000D_
} // onEdit()
_x000D_
As Javier says:
And if you wanted to see it in action I've created a demo sheet and you can see the code if you take a copy.