/**
 * Define un objeto (lvm_slcts) que permite crear campos selects con unas
 * prestaciones especiales. Concretamente:
 * 
 * - Los campos SELECT creados con un mismo nombre, se considera que son el mismo,
 *   y cambian juntos de modo que siempre tienen seleccionada la misma opción.
 *   Es útil cuando se desea poner el campo en varias partes de la página, para
 *   facilitar la usabilidad.
 * - Los campos SELECT con diferentes nombres pueden ser independientes entre
 *   sí, y también pueden ser maestros y/o esclavos. Cuando un SELECT es maestro
 *   su selección determina un subconjunto de opciones que deben mostrar sus
 *   esclavos.
 * - Los campos SELECT con diferentes nombres pueden compartir la misma lista
 *   de opciones, que se debe guardar en un array javascript lo cual ahorra
 *   ancho de banda en algunas circunstancias.
 * - Además de la lista de opciones común, se puede especificar una opción
 *   extra en cada SELECT. Esto es útil cuando se muestra una opción no
 *   disponible para nuevas selecciones, pero que se debe mostrar si ya está
 *   seleccionada (por ejemplo, cuando el SELECT muestra el contenido de una
 *   fila borrada lógicamente en una base de datos).
 * 
 * ---------------------------
 * Dependencias: ninguna.
 * ---------------------------
 * 
 * ---------------------------
 * Uso (mas sencillo):
 * ---------------------------
 * 
 * - Definir un array con las opciones del select. Cada item es un array que
 *   define, a su vez, una opción. El primer item es el ID de opcion (el valor
 *   que enviará el select si se selecciona), el segundo es el texto mostrado
 *   de la misma, y el tercero (opcional) es el conjunto al que pertenece (otro
 *   ID) para su uso en selects esclavos.
 * 
 *   Ambos ID deben ser enteros mayores que cero. La opción en blanco no debe
 *   estar en el array, ya que se coloca de forma automática, con ID = 0.
 * 
 * - Llamar a lvm_slcts.dibujar_nuevo con estos parámetros:
 *   - Un nombre para el select.
 *   - El array definido anteriormente.
 *   - Opcionalmente, el ID de la opción seleccionada, es cero por defecto.
 *   - Opcionalmente un array con una opción extra que no tiene porque estar en
 *     el array de opciones.
 * 
 * - En cualquier momento, si es preciso, llamar a lvm_slcts.set_maestro_esclavo
 *   con el nombre de un select maestro, y el nombre de otro esclavo.
 * 
 * ---------------------------
 * Clases:
 * ---------------------------
 * 
 * Aunque está previsto implementar una forma de definir las clases de los
 * diferentes elementos creados, ahora son (más o menos) fijas, así:
 * 
 * - El campo SELECT, tiene la clase 'SLCT'.
 * - Las opciones OPTION normales, tienen la clase 'OPTN'.
 * - La opción actual (no necesariamente seleccionada) tiene la clase: 'OPTN_ACTUAL'.
 * 
 * Existe, sin embargo, una forma poco ortodoxa y poco flexible de cambiarlas,
 * consiste en lo siguiente:
 * 
 * - En lugar de llamar a lvm_slcts.dibujar_nuevo, llamar primero a lvm_slcts.nuevo
 *   y tomar el valor devuelto, que es un objeto que representa al select aún no
 *   dibujado.
 * - Siendo obj el objeto devuelto, tenemos que:
 *   - obj.clases.campo es la clase para el campo SELECT.
 *   - obj.clases.opcion es la clase para las opciones normales.
 *   - obj.clases.actual es la clase para la opción actual.
 *   - obj.clases.extra es la clase para la opción opcional no incluida en el
 *     array de opciones, siempre que no sea la opción actual. Si es false (lo
 *     es por defecto) toma el valor de obj.clases.opcion.
 *              Ojo: en realidad, esta posibilidad no se da nunca, ya que la opción
 *                   extra es siempre la actual, porque la opción extra se define
 *                   precisamente cuando se especifica una opción actual que no está
 *                   en la lista de opciones.
 *   - obj.clases.extra_actual es la clase para la opción opcional no incluida
 *     en el array de opciones CUANDO ES TAMBIÉN LA ACTUAL. Si es false (lo es
 *     por defecto) toma el valor de obj.clases.actual.
 *   Todos estos valores pueden ser modificados, siempre que se haga antes de
 *   la llamada a obj.dibujar.
 * - Llamar a obj.dibujar (no recibe parámetros).
 * 
 * ---------------------------
 * Nombres en el scope global:
 * ---------------------------
 * 
 * El código de este archivo define los siguientes elementos globales (sólo el
 * primero de ellos es público).
 * 
 * - El objeto lvm_slcts, encargado de crear objetos <SELECT> con unas prestaciones
 *   especiales. Es el objeto que el código cliente debe usar.
 * - La clase _tp_lvm_selects, lvm_slcts es de este tipo y el código cliente no debe
 *   crear más objetos con esta clase (de ahí el '_' inicial).
 * - La clase _tp_lvm_select, usada internamente por la anterior para gestionar cada
 *   select individual. Es para uso interno también, aunque es posible, a través
 *   de lvm_slcts acceder a los objetos de esta clase creados y usar sus métodos.
 * - La función _tp_lvm_select_aux, es llamada por el navegador cuando se produce
 *   un evento en el campo select. Se encarga de llamar al manejador real que es
 *   el método _set_seleccion de _tp_lvm_select.
 * 
 * ---------------------------
 * @author: a_vera.
 */

window.lvm_slcts = new _tp_lvm_selects();

/**
 * Auxiliar para _tp_lvm_select. Es llamada en los eventos. Se limita a obtener el
 * nombre del campo select, 
 * 
 * @author: a_vera.
 */
function _tp_lvm_select_aux(th) {
    var i,nm,v;
    i = th.selectedIndex;
    nm = th.name;
    v = parseInt(th.options[i].value);
    if(!v) {
        v = 0;
    }
    lvm_slcts._set_seleccion(nm, v);
}

/**
 * Crea un objeto _tp_lvm_selects, que es una factoria de objetos _tp_lvm_select.
 * @author: a_vera.
 */
function _tp_lvm_selects() {
    var t;
    t = this;
    t.objetos = {};
    t._auto_id = 1;
    t.get_id = _get_id;
    t.nuevo = _nuevo;
    t.maestros = {};
    t.esclavos = {};
    t.callbacks = {};
    t._set_seleccion = _set_seleccion;
    t.set_maestro_esclavo = _set_maestro_esclavo;
    t.dibujar_nuevo = _dibujar_nuevo;
    t.set_callback = _set_callback;

    function _set_callback(nm, cb) {
        this.callbacks[nm] = cb;
    }
    
    /**
     * Crea un objeto _tp_lvm_select.
     * 
     * @param: string. Nombre del objeto que se va a crear.
     * @param: array. Lista de opciones a mostrar.
     * @param: integer. ID de la opción seleccionada.
     * @param: array, opcional. Opción adicional (mismo formato que los items
     *         del array del segundo parámetro).
     * @param: mixed, opcional (false). Si es evaluable a verdadero entonces
     *         el campo se crea deshabilitado.
     * @param: integer, opcional (-1). (Consultar comentarios en el método _nuevo)
     * @param: integer, tipo (0). (Consultar comentarios en el método _nuevo)
     */
    function _dibujar_nuevo(nm, os, seleccionado, actual, dis, c_global, tipo, xtra) {
        var o;
        o = t.nuevo(nm, os, seleccionado, actual, c_global, tipo);
        if(xtra!=undefined) {
            o.xtra_params = xtra;
        }
        if(dis) {
            o.clases.campo = "RO_" + o.clases.campo;
            o.disabled = 1;
        }
        o.dibujar();
        return o;
    }

    /**
     * Crea un objeto _tp_lvm_select, con un nombre, una lista de opciones
     * un valor actual y un valor seleccionado.
     * El objeto no se dibuja, el método crea únicamente el objeto lógico.
     * 
     * @param: string. Nombre del objeto que se va a crear.
     * @param: array. Lista de opciones a mostrar.
     * @param: integer. ID de la opción seleccionada. Si el campo es de
     *         multiselección, es un array de ID's.
     * @param: array, opcional. Opción adicional (mismo formato que los items
     *         del array del segundo parámetro).
     * @param: integer, opcional. ID del conjunto global. Sólo tiene sentido
     *         en selects esclavos, indica el ID del conjunto de opciones que
     *         se deben mostrar para las opciones esclavas cuando en el 
     *         maestro no se selecciona ninguna. Los valores interesantes
     *         actualmente son:
     * 
     *         -1 : No seleccionar ninguna opción.
     *          0 : Seleccionar todas las opciones.
     *         
     * @param: integer, tipo (0). Tipo de select:
     *         0: Normal. Una lista desplegable normal. Sólo se puede seleccionar
     *                    un elemento de la misma a la vez.
     *         1: Normal. Un 
     *         
     * @author: a_vera.
     */
    function _nuevo(nm, os, seleccionado, actual, c_global, tipo) {
        var t,ss,o,m,i,ii,jj,j,ac,x1,x2;
        t = this;
        if(t.objetos[nm]) {
            return t.objetos[nm];
        }
        if(!tipo) {
            tipo = 0;
        } else {
            tipo = parseInt(tipo);
        }
        //#-------------------------------------
        //# El código siguiente corrige <seleccionado> para que sea un entero
        //# en campos de selección simple o un array de enteros en campos
        //# de selección múltiple.
        //#-------------------------------------
        if(_sv.is_array(seleccionado)) {
            ii=seleccionado.length;
            if(tipo&1) {
                for(i=0; i<ii; i++) {
                    if(!seleccionado[i]) {
                        seleccionado[i] = 0;
                    } else {
                        seleccionado[i] = parseInt(seleccionado[i]);
                    }
                }
            } else {
                switch(ii) {
                case 1:
                    seleccionado = seleccionado[0];
                    break;
                default:
                    seleccionado = 0;
                    break;
                }
            }
        } else {
            if(!seleccionado) {
                seleccionado = 0;
            } else {
                seleccionado = parseInt(seleccionado);
            }
            if(tipo&1) {
                seleccionado = [seleccionado];
            }
        }
        if(_sv.is_array(seleccionado)) {        //# Si es un campo de selección multiple.
            ac = [];
            ii = seleccionado.length;
            jj = os.length;
            for(j=0;j<jj;j++) {
                x1 = os[j][0];
                if(!x1) {
                    x1=0;
                } else {
                    x1=parseInt(x1);
                }
                for(i=0;i<ii;i++) {
                    x2 = seleccionado[i];
                    if(x1==x2) {
                        break;
                    }
                }
                if(i<ii) {
                    ac[j] = 1;
                } else {
                    ac[j] = 0;
                }
            }
        } else {
            if(!seleccionado) {
                seleccionado = 0;
            } else {
                seleccionado = parseInt(seleccionado);
            }
            ac=seleccionado;
        }
        o = new _tp_lvm_select(nm,os);
        o.actual = ac;
        if(c_global==undefined) {
            c_global = -1;
        }
        o.c_global = parseInt(c_global);
        o.tipo = tipo;
        t.objetos[nm] = o;
        if(actual!=undefined) {
            o.opcion_xtra = actual;
            if(_sv.is_array(actual[0])) {
                o.v_actual = actual[0];
            } else {
                o.v_actual = parseInt(actual[0]);
            }
            o.t_actual = actual[1];
            if(_sv.is_array(actual[2])) {
                o.c_actual = actual[2];
            } if(actual[2]) {
                o.c_actual = parseInt(actual[2]);
            } else {
                o.c_actual = 0;
            }
        } else {
            o.opcion_xtra = [0,'',0];
            o.v_actual = 0;
            o.t_actual = '';
            o.c_actual = 0;
        }
        //--------------------------------
        // Se comprueba si este select es un esclavo, en cuyo caso se establece
        // su conjunto actual.
        //--------------------------------
        m = t.esclavos[nm];             // m = Nombre del maestro o undefined.
        if(m) {                         // Si está definido.
            o.es_esclavo = 1;           // | Se trata de un esclavo.
            m = t.objetos[m];           // | m = Objeto maestro.
            if(m) {                     // | Si está definido.
                o._set_conjunto         // | | Hacer el conjunto del objeto igual al
                    (m.actual);         // | | actual seleccionado de su maestro.
            }                           // | 
        }                               // 
        o._set_conjuntos(o.actual);     // Establecer el conjunto para los esclavos.
        return o;                       // Devolver una referencia al objeto.
    }

    /**
     * Obtiene un nuevo ID automático.
     */
    function _get_id() {
        var r;
        r = "LVM_SLS_ID" + String(this._auto_id);
        this._auto_id++;
        return r
    }

    /**
     * Selecciona un valor concreto de un campo.
     */
    function _set_seleccion(nm, v) {
        var cb;
        e = this.objetos[nm];
        if(e) {
            e._set_seleccion(v);
            cb = this.callbacks[nm];
            if(cb) {
                cb(nm, v);
            }
        }
    }

    /**
     * Establece que un select es esclavo de otro. Ni el select maestro, ni el
     * esclavo, tienen que existir en el momento de llamar a esta función, puede
     * ser llamada en cualquier momento.
     * 
     * @param: string. Nombre del select maestro.
     * @param: string. Nombre del select esclavo.
     * @author: a_vera.
     */
    function _set_maestro_esclavo(nmm, nms) {
        var m,t,o;
        t = this;
        if(!t.maestros[nmm]) {
            t.maestros[nmm] = [];
        }
        m = t.maestros[nmm];
        ii = m.length;
        for(i=0; i<ii; i++) {
            if(m[i]==nms) {
                break;
            }
        }
        if(i>=ii) {
            m[ii] = nms;
        }
        t.esclavos[nms] = nmm;
        o = t.objetos[nms];
        if(o) {
            o.es_esclavo = 1;
        }
        o = t.objetos[nmm];
        if(o) {
            o._set_conjuntos(o.actual);
        }
        
    }
}

/**
 * Crea un objeto _tp_lvm_select.
 * @param: string. Nombre del objeto.
 * @param: array. Lista de opciones.
 * @author: a_vera.
 */
function _tp_lvm_select(nm, opts) {
    var t, m, i, ii;
    t = this;
    t.nombre = nm;          // Nombre del campo select. Selects con el mismo nombre se
                            // consideran el mismo (cambian juntos).
    t.ids = [];             // Array de ID's de los campos SELECT que comparten el
                            // mismo nombre.
    t.c_global = 0;         // Id del conjunto global
    t.disabled = 0;         // Deshabilitado
    t.es_esclavo = 0;       // Es esclavo?
    t.actual = 0;           // ID de la opción seleccionada actual.
    t.tipo = 0;             // Tipo de select.
    t.v_actual = 0;         // ID de la opción actual.
    t.t_actual = '';        // Texto de la opción actual.
    t.c_actual = 0;         // Conjunto de la opción actual.
    t.opciones = opts;      // Lista de opciones (todas, no sólo un conjunto).
    t.opcion_xtra;          // 
    t.conjunto = 0;         // ID del conjunto actual.
    t.no_eventos = 0;       // Indica que se deben ignorar los eventos.
    t.xtra_params = false;
    t.clases={casilla: 'CHCK',
              casillas: 'CHCKS',
              campo: 'SLCT',
              opcion:'OPTN',
              actual:'OPTN_ACTUAL',
              extra:'OPTN_XTRA',
              extra_actual:'OPTN_XTRA'};
    t.dibujar = _dibujar;
    t._dibujar_opciones = _dibujar_opciones
    t._get_obj = _get_obj;
    t._add_opcion = _add_opcion;
    t._set_conjuntos = _set_conjuntos;
    t._set_seleccion = _set_seleccion;
    t._selecciona = _selecciona;
    t._selecciona_indice = _selecciona_indice;
    t._borrar_opciones = _borrar_opciones;
    t._set_conjunto = _set_conjunto;
    t._index_of = _index_of;
    t._get_opcion = _get_opcion;
    t._xtra_hidden = _xtra_hidden;
    
    //------------------------------------
    // Se comprueba si este select es un esclavo, en cuyo caso se establece
    // como conjunto el seleccionado en su maestro.
    //------------------------------------
    m = lvm_slcts.esclavos[nm];         // m = nombre del maestro (o undefined)
    if(m) {                             // Si no es undefined.
        m = lvm_slcts.objetos[m];       // | m = objeto _tp_lvm_select maestro.
        if(m) {                         // | Si es un objeto válido.
            t.conjunto = m.actual;      // | | El conjunto del esclavo es el valor
                                        // | | seleccionado actual del maestro.
        }                               // | 
    }                                   // 
    // Aquí termina la inicialización del constructor, se definen los métodos
    // antes de cerrar '}'.

    /**
     * Dibuja en la posición actual un campo SELECT con las opciones del objeto
     * _tp_lvm_select. Es posible llamar múltiples veces a este método, para dibujar
     * el mismo campo en diferentes posiciones de la página (cambiarán juntos).
     * 
     * @author: a_vera.
     */
    function _dibujar() {
        switch(this.tipo) {
        case 1:
            _dibujar_1(this);
            break;
        default:
            _dibujar_0(this);
            break;

        }
        //------------------------------------------------------------
    }   //-------------------------------- Fin: _tp_lvm_select::dibujar

    function _dibujar_1(t) {
        var id,cls,e,idh,eh;
        cls = t.clases.casillas;
        id = lvm_slcts.get_id();
        idh = lvm_slcts.get_id();
        t.ids[t.ids.length] = id;
        document.write("<INPUT TYPE='HIDDEN' ID='" + idh + 
                       "' NAME='" + t.nombre + "' VALUE=''>" + 
                       "<DIV ID='" + id + "' class='"+ cls +"'>\r\n" +
                       "</DIV>");
        e = t._get_obj(id);
        if(!e) {
            return;
        }
        eh = t._get_obj(idh);
        if(!eh) {
            return;
        }
        eh._sv_frm = [_lvms_on_submit, id];
        t._dibujar_opciones(e);
        //------------------------------------------------------------
    }   //----------------------------- Fin: _tp_lvm_select::dibujar_1

    function _lvms_on_submit(e, c) {
        var id,dx,xx,i,ii,s,ss,nm;
        id=c._sv_frm[1];
        d = t._get_obj(id);
        if(!d) {
            return;
        }
        s = '';
        ss = '';
        xx = _sv.get_childs(d, 'INPUT');
        ii = xx.length;
        for(i=0; i<ii; i++) {
            x = xx[i];
            if(x.type=='checkbox') {
                if(x.checked) {
                    s += ss + String(x.value);
                    ss = ',';
                }
            }
        }
        c.value = s;
    }

    function _dibujar_0(t) {
        var id,cls,ev,e,dis, dis_nm;
        cls = t.clases.campo;
        ev = "_tp_lvm_select_aux(this)";
        id = lvm_slcts.get_id();
        t.ids[t.ids.length] = id;
        if(t.disabled) {
            dis_nm = '_RO'
            dis = ' disabled=1';
            document.write("<INPUT TYPE='HIDDEN' ID='" + id + "_HDN'" +
                           " NAME='" + t.nombre + "'" +
                           " VALUE='" + t.v_actual + "'" +
                           ">\r\n");
        } else {
            dis = '';
            dis_nm = '';
        }
        xtra = '';
        if(t.xtra_params!=false) {
            xtra = ' ' + t.xtra_params;
        }
        document.write("<SELECT ID='" + id + "'" +
                       " NAME='" + t.nombre + dis_nm + "'" +
                       " CLASS='" + cls + "'" +
                       " onChange='" + ev + "'" + 
                       " onClick='" + ev + "'" +
                       " onKeyPress='" + ev + "'" + 
                       " onKeyDown='" + ev + "'" + 
                       " onKeyUp='" + ev + "'" + 
                       xtra +
                       dis + 
                       ">\r\n");
        document.write("</SELECT>");
        e = t._get_obj(id);
        if(e) {
            nm = e.getAttribute('sv_name2');
            if(nm!=null && nm!='') {
                t._xtra_hidden(nm, "_name2");
            }
            nm = e.getAttribute('sv_name3');
            if(nm!=null && nm!='') {
                t._xtra_hidden(nm, "_name3");
            }
            t._dibujar_opciones(e);
        }
        //------------------------------------------------------------
    }   //----------------------------- Fin: _tp_lvm_select::dibujar_0

    function _xtra_hidden(hnm, vnm) {
        var t,id
        t = this;
        id = lvm_slcts.get_id();
        if(t[vnm]!=undefined) {
            return;
        }
        document.write("<input type='hidden' value=''"+
                       " id='" + id + "'" +
                       " name='" + hnm + "'" +
                       ">");
        t[vnm] = id;
    }

    /**
     * Dibuja las opciones del conjunto actual en un campo select que se pasa
     * como parámetro, seleccionando la actual (modificando la selección si
     * es necesario).
     * 
     * @author: a_vera.
     */
    function _dibujar_opciones(e) {
        switch(this.tipo) {
        case 1:
            _dibujar_opciones_1(e, this);
            break;
        default:
            _dibujar_opciones_0(e, this);
            break;

        }
        t = this;                       // Referenciar this.
    }
    
    function _dibujar_opciones_0(e, t) {
        var o,os,cs,cv,ct,cc,cf,c,cls;
        var i,ii,j,k,sj,opt;
        var m,mm,ov,ot;

        if(!e) {                        // Si el elemento no es válido.
            return;                     // | Final
        }                               // 
        os = t.opciones;                // os = array de opciones.
        cv = t.v_actual;                // cv = ID actual.
        ct = t.t_actual;                // ct = Texto actual.
        cc = t.c_actual;                // cc = Conjunto actual.
        c  = t.conjunto;                // c = Conjunto actual.
        cs = t.actual;                  // cs = ID seleccionado actual.
        cf = 0;                         // cf = Flag encontrado actual.
        j = 1;                          // j = Número de opciones en el SELECT.
        sj = -1;                        // sj tendrá la opcion actual (número de orden
                                        // en el campo select).
        k = -1;                         // k tendrá la opcion actual (número de orden
                                        // en la lista de opciones).
        //--------------------------------
        // Primero, se añade la opción vacía.
        //--------------------------------
        if(cs==0) {                     // Si la opción seleccionada actual es vacío.
            sj=0;                       // | Guardar el índice.
        }                               // 
        cls=t.clases.opcion;
        if(cv==0) {                     // Si el actual es vacío.
            cls=t.clases.actual;
            cf=1;                       // | marcar que ya ha sido tratado.
        }                               // 
        opt=t._add_opcion(e,'',0,cls);  // Crear la opción vacía.
        //--------------------------------
        // Aquí, se ha añadido la opción vacía, el siguiente bucle añade las opciones
        // del array de opciones.
        //--------------------------------
        ii = os.length;                 // ii = Número de opciones.
        for(i=0; i<ii; i++) {           // Para cada opción.
            cls=t.clases.opcion;
            o = os[i];                  // | o se refiere a la opción.
            ov = parseInt(o[0]);        // | ov contiene el valor de la opción.
            if(ov==cv) {                // | Si coincide con el actual
                cf = 1;                 // | | marcar que ya ha sido tratado.
                cls=t.clases.actual;
            }                           // | 
            if(cs==ov) {                // | Si es el seleccionado actual.
                k = i;                  // | | Guardar posición en k.
            }                           // | 
            if(t.es_esclavo) {
                if((c!=t.c_global)
                    && (o[2]!=c)){      // | Si no pertenece al conjunto a mostrar
                    continue;           // | | Ignorarla.
                }                       // |
            }
            ot = o[1];                  // | ot = texto de la opcion.
            t._add_opcion(e,ot,ov,cls); // | Crear la opción.
            if(cs==ov) {                // | Si es la opción seleccionada actual
                sj = j;                 // | | Guardar el índice.
            }                           // | 
            j++;                        // | Incrementar el contador.
        }                               // 
        //--------------------------------
        // Aquí, las opciones del conjunto se han añadido al campo select, pero puede
        // suceder que haya que añadir una mas, la actual, se debe hacer si pertenece
        // al conjunto y no se ha tratado en el bucle anterior (flag cf).
        //--------------------------------
        if(cf==0) {                     // Si falta la opción actual.
            if(cs==cv) {                // | Si es la seleccionada actual.
                k = i;                  // | | Guardar posición en k.
            }                           // | 
            if((c==0) || (cc==c)) {     // | Si pertenece al conjunto.
                cls=t.clases.extra_actual;
                if(!cls) {
                    cls=t.clases.actual;
                }
                if(cs==cv) {            // | | Si es la seleccionada actual.
                    sj = j;             // | | | Guardar el índice.
                }                       // | | 
                t._add_opcion(e,ct,cv,cls);//| | Crear la opción.  
                j++;                    // | | Incrementar el contador de opciones.
            }                           // | 
        }                               // 
        //--------------------------------
        // Aquí, ya todas las opciones han sido añadidas, pero aún es posible
        // que la selección actual (cs) no esté entre ellas. Si se da ese caso
        // se selecciona la opción vacía, modificando la selección actual.
        //--------------------------------
        if(sj<0) {                      // Si aún no sabemos cual seleccionar.
            cs = 0;                     // | Cambiar la selección actual.
            sj = 0;                     // | Seleccionar la vacía.
        }                               // 
        //--------------------------------
        // Aquí, sj contiene el número de opción que debe ser seleccionada
        // y cs contiene el ID de la misma.
        //--------------------------------
        t._selecciona_indice(e, sj);
        if(e.selectIndex!=sj) {         // Si la opción sj no está ya seleccionada.
            e.selectedIndex = sj;       // | Seleccionar.
        }                               // 
        if(cs!=t.actual) {              // Si la selección ha cambiado.
            t.actual = cs;              // | Actualizarla.
            t._set_conjuntos(cs);       // | También en los esclavos.
        }                               // 
        //-----------------------------------------------------------------------
    }   //-------------------------------- Fin: _tp_lvm_select::_dibujar_opciones

    function _dibujar_opciones_1(e, t) {
        var o,os,cs,cv,ct,cc,cf,c,cls,cb;
        var i,ii,j,sj,opt, f;
        var m,mm,ov,ot,p,ic;

        if(!e) {                            // Si el elemento no es válido.
            return;                         // | Final
        }                                   // 
        os = t.opciones;                    // os = array de opciones.
        cv = t.v_actual;                    // cv = ID actual.
        ct = t.t_actual;                    // ct = Texto actual.
        cc = t.c_actual;                    // cc = Conjunto actual.
        c  = t.conjunto;                    // c = Conjunto actual.
        cs = t.actual;                      // cs = ID seleccionado actual.
        cls=t.clases.casilla;
        ii = os.length;                     // ii = Número de opciones.
        for(i=0; i<ii; i++) {               // Para cada opción.
            o = os[i];                      // | o se refiere a la opción.
            ov = parseInt(o[0]);            // | ov contiene el valor de la opción.
            f = 1;                          // | En principio, se mostrará la opción.
            if(t.es_esclavo) {              // | 
                if((c!=t.c_global)          // | 
                    && (o[2]!=c)){          // | Si no pertenece al conjunto a mostrar
                    f = 0;                  // | | Ignorarla.
                }                           // |
            }
            if(!f) {                        // 
                cs[i] = 0;                  // | 
                continue;
            }
            ot = o[1];                  // | ot = texto de la opcion.
            p = _sv.create_element('p'); 
            cb = _sv.create_element('input'); //# Se crea un elemento INPUT
            cb.type = 'checkbox';       //# Con tipo checkbox.
            //cb.name = t.nombre + '_CB_' + String(i);
            //---------------------------------
            //# Ojo: Tal vez no haga falta nunca, pero si existe la posibilidad de que
            //# el valor de una opción tenga comas (ahora son siempre números) será
            //# necesario escaparlas de alguna forma, para que al formar la cadena de
            //# resultado no exista confusión posible. En PHP también debería
            //# considerarse la posibilidad de que las cadenas vengan con caracteres
            //# escapados.
            //---------------------------------
            cb.value = String(ov);          //#
            _sv.append_child(p,cb);
            ic = _sv.create_element('span');
            ic.innerHTML = ot;
            _sv.append_child(p,ic);
            _sv.append_child(e,p);
            if(cs[i]) {
                cb.checked=true;
            }
            j++;                        // | Incrementar el contador.
        }                               // 
        //-----------------------------------------------------------------------
    }   //-------------------------------- Fin: _tp_lvm_select::_dibujar_opciones

    /**
     * Borra las opciones de un campo que se pasa como parámetro.
     * 
     * @author: a_vera.
     */
    function _borrar_opciones(e) {
        var i, ii, nn;
        nn = e.nodeName;
        if(nn=='DIV') {
            e.innerHTML ='';
        } else {
            ii = e.options.length;
            for(i=0; i<ii; i++) {
                e.remove(0);
            }
        }
    }

    /**
     * Obtiene un elemento HTML por ID.
     * 
     * @author: a_vera.
     */
    function _get_obj(oid) {
        return document.getElementById(oid);
    }

    /**
     * Establece el nombre de un select esclavo.
     * 
     * @author: a_vera.
     */
    function set_esclavo(nm) {
    }
                                
    /**
     * Establece un ID de conjunto para el select.
     * 
     * @author: a_vera.
     */
    function _set_conjunto(cid) {
        var t, s, i, ii, e;
        t = this;
        cid = parseInt(cid);
        if(cid==t.conjunto) {
            return;
        }
        t.conjunto = cid;
        s = t.ids;
        ii = s.length;
        for(i=0; i<ii; i++) {
            e=t._get_obj(t.ids[i]);
            t._borrar_opciones(e);
            t._dibujar_opciones(e);
        }
    }


    /**
     * _tp_lvm_select::_index_of.
     * 
     * Obtiene el número de opción dentro del campo SELECT correspondiente e un
     * determinado ID de opción.
     */
    function _index_of(id) {
        var t,os,i,ii,v;
        id = parseInt(id);
        if(!id) {
            return 0;
        }
        t = this;
        os = t.opciones;
        ii = os.length;
        for(i=0; i<ii; i++) {
            v = parseInt(os[i][0]);
            if(v==id) {
                return i+1;
            }
        }
        v = parseInt(t.opcion_xtra[0]);
        if(v==id) {
            return ii+1;
        }
        return 0;
    }
    
    /**
     * _tp_lvm_select::_get_opcion.
     * 
     * Obtiene el array de opción correspondiente a un número de opción dentro del
     * campo SELECT.
     */
    function _get_opcion(i) {
        var t,os,ii;
        if(!i) {
            return [0, ''];
        }
        i--;
        t = this;
        os = t.opciones;
        ii = os.length;
        if(i<ii) {
            return os[i];
        }
        return t.opcion_xtra;
    }

    /**
     * _tp_lvm_select::_set_seleccion.
     * 
     * Establece el ID de la selección actual. 
     */
    function _set_seleccion(id) {
        var t,s,ss,i,ii,e,ind,v2,v3;
        t = this;                       // Referenciar this.
        ind = t._index_of(id);
        if(t.no_eventos) {              // Si no se permiten eventos.
            return;                     // | Final.
        }                               // 
        id = parseInt(id);              // id = Valor del select como entero.
        if(id==t.actual) {              // Si coincide con el actual.
            return;                     // | Final.
        }                               // 
        t.no_eventos = 1;               // Bloquear eventos.
        try {                           // El try es para evitar que un error deje
                                        // los eventos bloqueados.
            //----------------------------
            // Primero establecer la opcion actual en los selects.
            //------------------------------
            ii = t.ids.length;          // | ii = Número de campos select.
            for(i=0; i<ii; i++) {       // | Para cada uno de ellos.
                e=t._get_obj(t.ids[i]); // | | e = Campo SELECT.
                if(e) {                 // | | Si es válido
                    t._selecciona(e,id,ind);// | | | Seleccionar la opción.
                }                       // | | 
            }                           // | 
            t._set_conjuntos(id);       // | Establecer como conjunto para los esclavos.
            t.actual=id;                // | Actualizar selección.
        } catch(ex) {                   // Si se produce un error.
            t.no_eventos = 0;           // | Desbloquear eventos.
            throw ex;                   // | Volver a disparar el error.
        }                               // 
        t.no_eventos = 0;               // Desbloquear eventos.
        //--------------------------------------------------------------------
    }   //-------------------------------- Fin: _tp_lvm_select::_set_seleccion

    /**
     * _tp_lvm_select::_set_conjuntos
     * 
     * Establece el ID de conjunto para los esclavos del objeto.
     * 
     * @param: ID de conjunto para los esclavos.
     * @author: a_vera
     */
    function _set_conjuntos(sid) {
        var s,ss,i,ii,nm;

        nm = this.nombre;               // nm = Nombre del propio objeto.
        ss = lvm_slcts.maestros[nm];    // ss = Lista de esclavos o undefined.
        if(!ss) {                       // Si es undefined.
            return;                     // | Final
        }                               // 
        ii = ss.length;                 // ii = Número de esclavos.
        for(i=0; i<ii; i++) {           // Para cada esclavo.
            s=lvm_slcts.objetos[ss[i]]; // | s = esclavo o undefined.
            if(s) {                     // | Si existe.
                s._set_conjunto(sid);   // | | Establecer el el conjunto actual.
            }                           // | 
        }                               // 
        //----------------------------------------------------------------------------
    }   //-------------------------------- Fin: _tp_lvm_select::_set_conjuntos

    /**
     * Selecciona una opción dentro de un campo select cuyo valor sea un entero dado.
     * La selección es a bajo nivel, no dispara eventos.
     * 
     * @param: object. Elemento (un select) en el que hay que seleccionar.
     * @param: integer. ID de la opción a seleccionar.
     * @author: a_vera
     */
    function _selecciona(e, v, i) {
        var t,o,ii,v2;
        t = this;
        t._selecciona_indice(e, i);
        ii = e.options.length;          // ii = Número de opciones.
        for(i=0; i<ii; i++) {           // Para cada opción.
            o = e.options[i];           // | o = opción.
            v2 = parseInt(o.value);     // | v2 = valor de la opción.
            if(!v2) {
                v2 = 0;
            }
            if(v!=v2) {                 // | Si no coincide con el valor buscado.
                continue;               // | | Probar con el siguiente.
            }                           // | 
            if(e.selectedIndex==i) {    // | Si ya está seleccionada esta opción.
                break;                  // | | Salir sin más.
            }                           // | 
            this.no_eventos = 1;        // | Bloquear eventos.
            e.selectedIndex = i;        // | Seleccionar.
            this.no_eventos = 0;        // | Desbloquearlos.
            break;                      // | Devolver el índice
        }                               // 
        if(i>=ii) {
            i=-1;
        }
        return i;                   // | Devolver el índice
        //-----------------------------------------------------------------
    }   //-------------------------------- Fin: _tp_lvm_select::_selecciona

    function _selecciona_indice(e, i) {
        var t,o,ii,v2,v3;
        t = this;
        o = t._get_opcion(i);
        ii = o.length;
        if(ii>2) {
            v2 = o[2];
            if(o>3) {
                v3 = o[3];
            } else {
                v3 = 0;
            }
        } else {
            v2 = 0;
            v3 = 0;
        }
        _sv.set_prop(e, 'sv_value2', v2);
        _sv.set_prop(e, 'sv_value3', v3);
        if(t._name2!=undefined) {
            e = t._get_obj(t._name2);
            e.value = v2;
        }
        if(t._name3!=undefined) {
            e = t._get_obj(t._name3);
            e.value = v3;
        }
    }

    /**
     * Añade una opción a un campo SELECT, con un texto a mostrar y un valor.
     * 
     * @param: object. Elemento (un select) al que se debe añadir la opción.
     * @param: string. Texto a mostrar en la opción.
     * @param: integer. Valor de la opción.
     * @author: a_vera
     */
    function _add_opcion(e, ot, ov, cl) {
        var opt;
        opt = document.createElement('option');
        opt.text = ot;
        if(!ov) {
            opt.value = '';
        } else {
            opt.value = ov;
        }
        if(cl) {
            opt.className=cl;
        }
        try {
            e.add(opt, null);
        } catch(ex) {
            e.add(opt);
        }
        return opt;
    }
}
