Tutorial: Agrega TextBox dinámicos en tus páginas ASP.NET

En este tutorial aprenderás a agregar TextBox de manera dinámica en tus páginas ASP.NET. No es difícil hacerlo, pero no es tan trivial como parece. Si tu alguna vez lo haz intado te habrás topado con el problema de mantener el número de TextBox ya creados y sus valores a través de ‘postbacks’ de la página.

A través de las herramientas que ASP.NET proporciona, te mostraré como agregar estos TextBox, mantener sus propiedades, agregarles Validadores a los inputs y también almacenar sus valores a través de los ‘N postbacks’ que sufra la página.

El problema

La aplicación del mundo real que quiero describir para este ejemplo es un sistema de encuestas. Si te preocupa el diseño de tu interfaz seguramente deses agregar una opción para que el usuario agrege cuantas respuestas quiera a una encuesta y que esto sea de manera dinámica. Estaríamos haciendo un pésimo diseño si únicamente colocaramos 10 cajas de texto sin dar más opción.

En fin, una solución de 5 minutos sería esta:

<asp:LinkButton ID="AddAnswer" runat="server"
                text="Add answer..."
                onClick="AddAnswer_Click"  />

<asp:Panel ID="AnswerPanel" runat="server" />

La intención de esta solución es que al dar click en el LinkButton este tiene que crear un nuevo TextBox y agregarlo a la lista de controles de AnswerPanel. (NOTA: no es necesariamiente un Panel, también puede ser un Placeholder)

protected void IncrementAnswer_Click(object sender, EventArgs e)
{
    TextBox tb = new TextBox();
    tb.Columns = 10;
    AnswerPanel.Controls.Add(tb);
}

Ok, parece que la solución es así de trivial. Inclusive ésta solución funciona hasta el hecho de que las TextBox aparecerán dentro de AnswerPanel. ¿Ya lo habías hecho? ¿Qué pasa cuando pones otro botón para recuperar el valor de estos TextBox? ¿Dónde los buscarías?

La respuesta sensata  a las preguntas anteriores es que tendrías que buscar dentro de los Controles hijos de AnswerPanel y a cada TextBox que te encuentres debes solicitarle su propiedad Text. ¿Esto funciona? ¿Al hacer un ‘postback’ los controles TextBox siguen dentro de AnswerPanel? La respuesta es no.

La solución

Bien. La solución dije que no es difícil de aplicar pero tampoco es trivial. Confieso que yo inclusive he pecado de querer hacer TextBox dinámicos de la manera que ya expliqué. Pero para eso estamos, para aprender y para compartir lo aprendido.

Para comenzar te muestro la interfaz del ejemplo.

pic01

El código completo lo podrás descargar al final del artículo. Por el momento sólo te mostraré una pequeña parte del mismo:

<asp:LinkButton ID="IncrementAnswer" runat="server"
                Text="Agregar respusta..." 
                onclick="IncrementAnswer_Click"/>
<br /><br />
<asp:Panel ID="AnswerListPanel" runat="server"
           ScrollBars="Auto"
           Height="66px">
    <asp:Repeater ID="DynamicTextBoxes" runat="server"
                  OnInit="DynamicTextBoxes_Init"
                  OnItemDataBound="DynamicTextBoxes_ItemDataBound"
                  OnItemCommand="DynamicTextBoxes_ItemCommand">
        <ItemTemplate>
            <div style="padding:1px;">
                <asp:Label ID="AnswerLabel" runat="server"
                           Text="Respusta no. " />
                <asp:TextBox ID="AnswerBox" runat="server"
                             Columns="45" />
                <asp:LinkButton ID="RemoveAnswer" runat="server"
                            Text="x" ToolTip="Elimina esta respuesta!" />
                <asp:RequiredFieldValidator ID="AnswerRFV" runat="server"
                            ControlToValidate="AnswerBox" Display="Dynamic"
                            ValidationGroup="CreateVG"
                            Text="*" ErrorMessage="Answer " />
            </div>
        </ItemTemplate>
    </asp:Repeater>
</asp:Panel>

Mi interfaz está compuesta de un LinkButton y un Repeater. Los eventos que me interesa controlar del Repeater son: Init, ItemDataBound e ItemCommand.

Dentro del repeater tengo un div que contiene:

  1. Label: Este dará información del no. de respuesta
  2. TextBox: El usuario pondrá el texto de la respuesta aquí
  3. LinkButton: Si el usuario quere borrar la respuesta dará clic aquí
  4. RequiredFieldValidator: Debo estar seguro que el usuario escribió algo y no dejó el campo vació.

¿Por qué si tengo todo esto dentro del Repeater, la interfaz no muestra nada aún?

Si haz manejado Controles del tipo, sabrás que el Repeater (GridView, DataList, etc) requere de un DataSource para funcionar. Ya voy a hablar de eso, por el momento nota que utilizo el mismo código ASP.NET para describir cómo serán mis elementos dinámicos. No tengo que crearlos ‘programáticamente’ en ‘code behind’. Simplemente los describo aquí tal como quiero que sean, con formato, características e inclusive con ‘Validators’ que pertenecen al ‘ValidationGroup’ de todo el formulario.

Bien, ya tengo el código de mi página ASP.NET. Antes de irme con los eventos del Repeater quiero mostrarte el siguiente código que coloqué en el ‘CodeBehind’ de la página que almacena mi formulario.

/// <summary>
/// Obtiene del ViewState la lista de respuestas de la encuesta
/// del usuario
/// </summary>
private List<string> GetAnswerDataSource()
{
    List<string> answerDataSource = null;
    if (ViewState["AnswerDataSource"] != null)
    {
        answerDataSource = (List<string>)ViewState["AnswerDataSource"];
    }
    else
    {
        answerDataSource = new List<string>();
        answerDataSource.Add(string.Empty);
        ViewState["AnswerDataSource"] = answerDataSource;
    }
    return answerDataSource;
}

/// <summary>
/// Guarda en el ViewState la lista recibida
/// </summary>
private void SetAnswerDataSource(List<string> answerDataSource)
{
    ViewState["AnswerDataSource"] = answerDataSource;
}

Nos apoyaremos en el ‘ViewState’ para almacenar una Lista de Strings, misma que guardará las respuestas de la encuesta. En el código anterior te he marcado las líneas de interés en dónde verás que el método GetAnswerDataSource regresa al usuario un List<string> que es almacenado en el ViewState. Nota que si ViewState no tiene una definición de “AnswerDataSource” (línea 14), esta se crea con un campo vacío y se agrega al ViewState. También se ha incluido un método para sobreescribir cualquier List<string> que ya exista en el ViewState por el que se envía de parámetro al método SetAnswerDataSource.

Ahora más código…

/// <summary>
/// Agrega a la lista de respuestas un espacio en blanco
/// para que se genera un nuevo TextBox
/// </summary>
private void IncrementTextboxCount()
{
    List<string> dataSource = GetAnswerDataSource();
    dataSource.Add(string.Empty);
    SetAnswerDataSource(dataSource);
}

/// <summary>
/// Se elimina la respuesta seleccionada y por consecuencia
/// el textbox relacionado
/// </summary>
private void DeleteTextbox(int index)
{
    List<string> dataSource = GetAnswerDataSource();
    dataSource.RemoveAt(index);
    SetAnswerDataSource(dataSource);
}

Ahora bien, estos dos métodos son bastante lineales. Para agregar un nuevo campo de respuesta a la lista debo obtenerla a través del método GetAnswerDataSource y agregar un string vació al final de esta lista y grabarla de nuevo en el ViewSate.

Para poder eliminar una respuesta que haya dado el usuario basta saber el índice de la misma, borrarla de la lista y almacenarla de nuevo en el ViewState con el método SetAnswerDataSource.

/// <summary>
/// Navega a través de todos los textbox para actualizar
/// los valores de las respuestas en el ViewState
/// </summary>
private void UpdateAnswerDataSource()
{
    List<string> dataSource = new List<string>();
    foreach (RepeaterItem item in DynamicTextBoxes.Items)
    {
        dataSource.Add(((TextBox)item.FindControl("AnswerBox")).Text);
    }
    SetAnswerDataSource(dataSource);
}

El método anterior al ser llamado navegará a través de todos los Items del Repeater para obtener cada uno de los TextBox contenidos internamente y tomar de ahí los Textos introducidos por el usuario y almacenarlos en el ViewState a través del método SetAnswerDataSource.

Y finalmente viene la pieza que cierra el círculo.

/// <summary>
/// Controla el Bind del Repeaters
/// </summary>
private void BindDynamicTextBoxes()
{
    List<string> dataSource = GetAnswerDataSource();

    DynamicTextBoxes.DataSource = dataSource;
    DynamicTextBoxes.DataBind();
}

La fuente de datos para el Repeater será la lista de Textos almacenada en el ViewState.

Muy bien, veamos ahora los tres métodos que nos interesan del Repeater: Init, ItemDataBound e ItemCommand.

private int count = 0;
/// <summary>
/// Esto es opcional, se utiliza para cuestiones
/// estéticas. Únicamente pongo el valor de la variable
/// count en 0
/// </summary>
protected void DynamicTextBoxes_Init(object sender, EventArgs e)
{
    count = 0;
}

/// <summary>
/// Cada vez que el repeater agrega una renglón
/// aquí hacemos el Bound. Esto significa que 
/// arreglo las cuestiones estéticas y además coloco
/// como texto del TextBox la respuesta correspondiente
/// </summary>
protected void DynamicTextBoxes_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    ((Label)e.Item.FindControl("AnswerLabel")).Text = ((Label)e.Item.FindControl("AnswerLabel")).Text + (count + 1);
    ((TextBox)e.Item.FindControl("AnswerBox")).Text = (e.Item).DataItem.ToString();
    ((LinkButton)e.Item.FindControl("RemoveAnswer")).CommandArgument = count.ToString();
    ((RequiredFieldValidator)e.Item.FindControl("AnswerRFV")).ErrorMessage = ((RequiredFieldValidator)e.Item.FindControl("AnswerRFV")).ErrorMessage + (count + 1);
    count++;
}

/// <summary>
/// Maneja la opción de borrar respuesta
/// </summary>
protected void DynamicTextBoxes_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    UpdateAnswerDataSource();
    DeleteTextbox(int.Parse(e.CommandArgument.ToString()));
    BindDynamicTextBoxes();
}
  • Init: Sirve para iniciar la variable count en 0
  • ItemDataBound: Usa la variable count para indicar al usuario el número de respuesta que va a introducir. Aquí se agrega el texto correspondiente de la List<string> al campo de texto del Repeater
  • ItemCommand: Aquí recibimos el click del LinkButton que se encuentra en cada una de las respuestas. Los parámetros de este método indican qué renglón generó el evento y por consecuencia qué respuesta debe ser eliminada.

¿Complicado?

Tienes que camboar el enfoque del probema ligeramente. En lugar de controlar la creación de TextBox delegas esa tarea al Repeater. Simplemente te concentras en mantener la información del usuario a través de los ‘PostBacks’ y para ello no requieres guardar TextBox en listas estáticas o en memoria, simplemente usamos el ‘ViewState’ para guardar los simples valores de los TextBox en una Lista de Strings.

Descargar archivo: TutorialControlesDinamicos.zip

Te recomiendo que le des una mirada detallada al ejemplo por qué en él demuestro a través que la información continúa almacenada a través de varios ‘postbacks’.




( Country flag )
biuquote
  • Comentario
  • Vista previa
Loading