Les comparto esta pequeña librería que hice para hacer Selects Dependientes de N niveles con AJAX.
Su uso es bastante simple, solo es instanciar el select y pasar quien es el que depende y el URL para cargar los datos, posteriormente en ese URL van a recibir por GET dos datos, controlName (el nombre del control) y selectedId (el valor que selecciono).
La clase espera recibir un arreglo codificado en JSON, donde cada elemento es un objeto, con una propiedad text, y otra value, de esta forma:
Código:
  
La forma de uso es bastante simple, basta con tener un HTML:[
    { text: "Texto a mostrar", value: "valor del option" },
    { text: "Texto 2", value: "valor2" },
    ....
    { text: "Texto n", value: "ValorN" }
]
Código HTML:
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript" src="prototype.js"></script> <script type="text/javascript" src="dependant.js"></script> <title>Demo Selects Dependientes</title> </head> <body> <select name="sel1" id="sel1"> <option value="">- Selecciona -</option> <option>Mascotas</option> <option>Carros</option> </select> <select name="sel2" id="sel2"><option></option></select> <select name="sel3" id="sel3"><option></option></select> <script type="text/javascript"> Event.observe(window, 'load', function() { var sel3 = new HTMLSelect( 'sel3' ); var sel2 = new dependantSelectAJAX( 'sel2', sel3, 'loader.php' ); var sel1 = new dependantSelectAJAX( 'sel1', sel2, 'loader.php' ); }); </script> </body> </html>
Dejo un pequeño ejemplo de como cargar los datos via PHP, aunque es independiente del lenguaje, el único requisito es que los datos vengan en JSON y con la estructura mencionada mas arriba:
Código PHP:
   <?php
$mascotas = array( array( "text" => "Gato", "value" => "cat" ), array( "text" => "Perro", "value" => "dog" ), array( "text" => "Pajaro", "value" => "bird" ) );
$carros = array( array( "text" => "BMW", "value" => "bmw" ), array( "text" => "Lamborghini", "value" => "lamb" ), array( "text" => "Ferrari", "value" => "ferr" ) );
$mascotas_acc_cat = array( array( "text" => "Collar" ), array( "text" => "Estambre" ), array( "text" => "Arena" ) );
$mascotas_acc_dog = array( array( "text" => "Collar" ), array( "text" => "Correa" ), array( "text" => "Pelota" ) );
$mascotas_acc_bird = array( array( "text" => "Jaula" ), array( "text" => "Alpiste" ) );
$carros_color_bmw = array( array( "text" => "Azul" ), array( "text" => "Negro" ) );
$carros_color_lamb = array( array( "text" => "Azul" ), array( "text" => "Amarillo" ), array( "text" => "Rojo" ) );
$carros_color_ferr = array( array( "text" => "Rojo" ) );
switch( $_GET['controlName'] ) {
case 'sel1':
    switch( $_GET['selectedId'] ) {
    case 'Mascotas':
        $result = $mascotas;
        break;
    case 'Carros':
        $result = $carros;
        break;
    }
    break;
case 'sel2':
    switch( $_GET['selectedId'] ) {
    case 'cat':
        $result = $mascotas_acc_cat;
        break;
    case 'dog':
        $result = $mascotas_acc_dog;
        break;
    case 'bird':
        $result = $mascotas_acc_bird;
        break;
    case 'bmw':
        $result = $carros_color_bmw;
        break;
    case 'lamb':
        $result = $carros_color_lamb;
        break;
    case 'ferr':
        $result = $carros_color_ferr;
        break;
    }
} 
echo json_encode( $result );
exit();
?>    
Código:
  
Requiere de la libreria Prototype versión 1.6 minimo debido a la nueva forma de declarar clases.var DataStore = Class.create(Enumerable, {
	initialize: function( store ) {
		if( store == null ) {
			store = [];
		}
		
		this._store = store;
	},
	
	addItem: function( text, value ) {
		if( value == null ) {
			value = text;
		}
		
		this._store.push( {text: text, value: value } );
	},
	
	removeItem: function( idx ) {
		this._store.splice( idx, 1 );
	},
	
	clear: function() {
		this._store = [];
	},
	
	size: function() {
		return this._store.length;
	},
	
	inspect: function() {
		alert( this._store.inspect() );
	},
	
	_each: function(iterator) {
		for (var i = 0; i < this._store.length; i++) {
			var value = this._store[i];
			
			iterator(value);
		}
	}
});
var HTMLSelect = Class.create({
	initialize: function( element ) {
		this.element = $(element);
		this.element.onchange = this.onChange.bindAsEventListener(this);
		this.element.onclick = this.onClick.bindAsEventListener(this);
		this.element.onfocus = this.onFocus.bindAsEventListener(this);
		
		var store = new DataStore();
		var opts = this.element.options;
		for(var i = 0; i < opts.length; i++ ) {
			var el = opts[i];
			store.addItem( el.text, el.value );
		}
		
		this.store = store;
	},
	
	setStore: function( ds ) {
		this.store = ds;
	},
	
	reload: function() {
		this.empty();
		
		var num = 0;
		this.store.each(function(item) {
			this.addOption(item.text, item.value);
			num++;
		}.bind(this));
	},
	
	onChange: function(e) {},
	onClick: function(e) {},
	onFocus: function(e) {},
	onEmpty: function() { return true; },
	
	selectIndex: function( index ) {
		this.element.selectedIndex = index;
	},
	
	selectOption: function( option ) {
		var size = this.element.length;
		var found = false;
		for(i = 0; i < size; i++) {
			var el = this.element.options[i].text;
			if( el == option ) {
				found = true;
				break;
			}
		}
		
		if( found ) {
			this.selectIndex(i);
			this.onChange();
		}
	},
	
	countOptions: function() {
		return this.element.length;
	},
	
	getSelectedOption: function() {
		var op = this.element.options[this.element.selectedIndex];
		return { value: op.value, text: op.text };
	},
	
	getValue: function() {
		var op = this.element.options[this.element.selectedIndex];
		var ret = "";
		
		ret = op.value;
		if( ret == "" ) {
			ret = op.text;
		}
		
		return ret;
	},
	
	empty: function() {
		if( this.onEmpty() ) {
			this._empty();
		}
	},
	
	addOption: function( text, value ) {
		if( value == null ) {
			value = text;
		}
		
		var op = new Option( text, value );
		var idx = this.element.length;
		this.element.options[idx] = op;
		
		return idx;
	},
	
	deleteOption: function( index ) {
		if( this.element.length > 0 && index > 0 ) {
			this.element.options[index] = null;
		}
	},
	
	selectAllOptions: function() {
		var size = this.element.length - 1;
		for(i = size; i>=0; i--) {
			this.element.options[i].selected = true;
		}
	},
	
	getSelectedOptions: function() {
		var texts = [];
		var size = this.element.length - 1;
		for(i = size; i>=0; i--) {
			if( this.element.options[i].selected === true ) {
				texts.push(this.element.options[i].text);
			}
		}
		
		return texts;
	},
	
	_empty: function() {
		this.element.options.length = 0;
	}
});
var dependantSelectAJAX = Class.create(HTMLSelect, {
	initialize: function( $super, select, child, url ) {
		$super( select );
		if( typeof( select ) == "string" ) {
			this.name = select;
		} else {
			this.name = select.name;
		}
		
		this.child = child;
		this.url = url;
	},
	
	onChange: function(e) {
		this.child.empty();
		var value = this.getValue();
		
		if( value != "" ) {
			var request = new Ajax.Request( this.url, {
				method: 'get',
				parameters: {controlName: this.name, selectedId: value},
				onSuccess: function( transport ) {
					var store = transport.responseText.evalJSON(true);
					if( typeof store.error != "undefined" ) {
						alert( store.error );
					} else {
						this.child.setStore(new DataStore(store));
						this.child.reload();
						var size = this.child.countOptions();
						if( size == 1 ) {
							this.child.onChange(); 
						}
					}
				}.bind(this),
				onFalure: function(t) {
					alert( "Error in request" );
				}
			});
		}
	},
	
	onEmpty: function() {
		this.child.empty();
		
		return true;
	}
});
Saludos y espero les sea de utilidad.
 
, pero de cualquier forma, sirve correctamente.
 
Este tema le ha gustado a 2 personas