Compartir vía


Parte 5: Crear una IU dinámica con Knockout.js

por Rick Anderson

Descargar el proyecto completado

Crear una IU dinámica con Knockout.js

En esta sección, usaremos Knockout.js para agregar funcionalidad a la vista Administrador.

Knockout.js es una biblioteca de JavaScript que facilita el enlace de HTML a datos. Knockout.js usa el patrón Model-View-ViewModel (MVVM).

  • El modelo es la representación del lado servidor de los datos en el dominio empresarial (en nuestro caso, productos y pedidos).
  • La vista es la capa de presentación (HTML).
  • El modelo de vista es un objeto de JavaScript que contiene los datos del modelo. El modelo de vista es una abstracción de código de la interfaz de usuario. No tiene conocimiento de la representación HTML. En su lugar, representa características abstractas de la vista, como "una lista de elementos".

La vista está vinculada a los datos del modelo de vista. Las actualizaciones del modelo de vista se reflejan automáticamente en la vista. El modelo de vista también obtiene eventos de la vista, como clics de botón, y realiza operaciones en el modelo, como la creación de un pedido.

Diagram of interaction between H T M L data, the view-model, j son, and the Web A P I controller.

Diagrama que muestra la interacción entre los datos de H T M L, el modelo de vista, j son y el controlador web A P I. El cuadro de datos H T M L está etiquetado como vista. Una flecha doble con la etiqueta enlace de datos vincula el cuadro de datos H T M L al cuadro de modelo de vista. Una flecha doble con la etiqueta H T T P requests y j son model from server vincula el modelo de vista al controlador de Web A P I.

En primer lugar, definiremos el modelo de vista. Después, enlazaremos el marcado HTML al modelo de vista.

Agregue la siguiente sección de Razor a Admin.cshtml:

@section Scripts {
  @Scripts.Render("~/bundles/jqueryval")
  <script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.1.0.js")"></script> 
  <script type="text/javascript">
  // View-model will go here
  </script>
}

Puede agregar esta sección en cualquier lugar del archivo. Cuando se representa la vista, la sección aparece en la parte inferior de la página HTML, justo antes de la etiqueta </body>de cierre.

Todo el script de esta página irá dentro de la etiqueta de script indicada por el comentario:

<script type="text/javascript">
  // View-model will go here
  </script>

En primer lugar, defina una clase de modelo de vista:

function ProductsViewModel() {
    var self = this;
    self.products = ko.observableArray();
}

ko.observableArray es un tipo especial de objeto en Knockout, denominado observable. De la documentación de Knockout.js: un observable es un "objeto JavaScript que puede notificar cambios a los suscriptores". Cuando el contenido de un cambio observable, la vista se actualiza automáticamente para que coincida.

Para rellenar la matriz products, realice una solicitud AJAX a la API web. Recuerde que almacenamos el URI base para la API en el contenedor de vistas (consulte la Parte 4 del tutorial).

function ProductsViewModel() {
    var self = this;
    self.products = ko.observableArray();

    // New code
    var baseUri = '@ViewBag.ApiUrl';
    $.getJSON(baseUri, self.products);
}

A continuación, agregue funciones al modelo de vista para crear, actualizar y eliminar productos. Estas funciones envían llamadas AJAX a la API web y usan los resultados para actualizar el modelo de vista.

function ProductsViewModel() {
    var self = this;
    self.products = ko.observableArray();

    var baseUri = '@ViewBag.ApiUrl';

    // New code
    self.create = function (formElement) {
        // If the form data is valid, post the serialized form data to the web API.
        $(formElement).validate();
        if ($(formElement).valid()) {
            $.post(baseUri, $(formElement).serialize(), null, "json")
                .done(function (o) { 
                    // Add the new product to the view-model.
                    self.products.push(o); 
                });
        }
    }

    self.update = function (product) {
        $.ajax({ type: "PUT", url: baseUri + '/' + product.Id, data: product });
    }

    self.remove = function (product) {
        // First remove from the server, then from the view-model.
        $.ajax({ type: "DELETE", url: baseUri + '/' + product.Id })
            .done(function () { self.products.remove(product); });
    }

    $.getJSON(baseUri, self.products);
}

Ahora la parte más importante: cuando el DOM está lleno cargado, llame a la función ko.applyBindings y pase una nueva instancia de ProductsViewModel:

$(document).ready(function () {
    ko.applyBindings(new ProductsViewModel());
})

El método ko.applyBindings activa Knockout y conecta el modelo de vista a la vista.

Ahora que tenemos un modelo de vista, podemos crear los enlaces. En Knockout.js, para ello, agregue atributos data-bind a elementos HTML. Por ejemplo, para enlazar una lista HTML a una matriz, use el enlace foreach:

<ul id="update-products" data-bind="foreach: products">

El enlace foreach recorre en iteración la matriz y crea elementos secundarios para cada objeto de la matriz. Los enlaces de los elementos secundarios pueden hacer referencia a las propiedades de los objetos de matriz.

Agregue los siguientes enlaces a la lista "update-products":

<ul id="update-products" data-bind="foreach: products">
    <li>
        <div>
            <div class="item">Product ID</div> <span data-bind="text: $data.Id"></span>
        </div>
        <div>
            <div class="item">Name</div> 
            <input type="text" data-bind="value: $data.Name"/>
        </div> 
        <div>
            <div class="item">Price ($)</div> 
            <input type="text" data-bind="value: $data.Price"/>
        </div>
        <div>
            <div class="item">Actual Cost ($)</div> 
            <input type="text" data-bind="value: $data.ActualCost"/>
        </div>
        <div>
            <input type="button" value="Update" data-bind="click: $root.update"/>
            <input type="button" value="Delete Item" data-bind="click: $root.remove"/>
        </div>
    </li>
</ul>

El elemento <li> se produce dentro del ámbito del enlace foreach. Esto significa que Knockout representará el elemento una vez para cada producto de la matriz products. Todos los enlaces del elemento <li> hacen referencia a esa instancia de producto. Por ejemplo, $data.Name hace referencia a la propiedad Name del producto.

Para establecer los valores de las entradas de texto, use el enlace value. Los botones están enlazados a funciones en la vista de modelo mediante el enlace click. La instancia del producto se pasa como parámetro a cada función. Para obtener más información, la documentación de Knockout.js tiene buenas descripciones de los distintos enlaces.

A continuación, agregue un enlace para el evento submit en el formulario Agregar producto:

<form id="addProduct" data-bind="submit: create">

Este enlace llama a la función create en el modelo de vista para crear un nuevo producto.

Este es el código completo de la vista Administrador:

@model ProductStore.Models.Product

@{
    ViewBag.Title = "Admin";
}

@section Scripts {
  @Scripts.Render("~/bundles/jqueryval")
  <script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.0.0.js")"></script> 
  <script type="text/javascript">
      function ProductsViewModel() {
          var self = this;
          self.products = ko.observableArray();

          var baseUri = '@ViewBag.ApiUrl';

          self.create = function (formElement) {
              // If valid, post the serialized form data to the web api
              $(formElement).validate();
              if ($(formElement).valid()) {
                  $.post(baseUri, $(formElement).serialize(), null, "json")
                      .done(function (o) { self.products.push(o); });
              }
          }

          self.update = function (product) {
              $.ajax({ type: "PUT", url: baseUri + '/' + product.Id, data: product });
          }

          self.remove = function (product) {
              // First remove from the server, then from the UI
              $.ajax({ type: "DELETE", url: baseUri + '/' + product.Id })
                  .done(function () { self.products.remove(product); });
          }

          $.getJSON(baseUri, self.products);
      }

      $(document).ready(function () {
          ko.applyBindings(new ProductsViewModel());
      })
  </script>
}

<h2>Admin</h2>
<div class="content">
    <div class="float-left">
    <ul id="update-products" data-bind="foreach: products">
        <li>
            <div>
                <div class="item">Product ID</div> <span data-bind="text: $data.Id"></span>
            </div>
            <div>
                <div class="item">Name</div> 
                <input type="text" data-bind="value: $data.Name"/>
            </div> 
            <div>
                <div class="item">Price ($)</div> 
                <input type="text" data-bind="value: $data.Price"/>
            </div>
            <div>
                <div class="item">Actual Cost ($)</div> 
                <input type="text" data-bind="value: $data.ActualCost"/>
            </div>
            <div>
                <input type="button" value="Update" data-bind="click: $root.update"/>
                <input type="button" value="Delete Item" data-bind="click: $root.remove"/>
            </div>
        </li>
    </ul>
    </div>

    <div class="float-right">
    <h2>Add New Product</h2>
    <form id="addProduct" data-bind="submit: create">
        @Html.ValidationSummary(true)
        <fieldset>
            <legend>Contact</legend>
            @Html.EditorForModel()
            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>
    </form>
    </div>
</div>

Ejecute la aplicación, inicie sesión con la cuenta de Administrador y haga clic en el vínculo "Admin". Debería ver la lista de productos y poder crear, actualizar o eliminar productos.