ASP .NET - KnockoutJS conceitos básicos


A KnockoutJS é uma library JavaScript Open Source que usa o padrão MVVM (Model-View-ViewModel) para criar interfaces de usuário através de ligações declarativas de dados, interface de atualização automática, rastreamento de dependência e modelagem do lado cliente. O conceito de MVVM deve ser muito intuitivo e familiar, especialmente para pessoas com experiência em WPF. (Veja as referências para saber mais sobre o padrão MVVM)

Você pode obter mais informações e detalhes bem como baixar a library JavaScript no site oficial do KnockoutJS - http://knockoutjs.com/

Existe um tutorial on-line rápido que pode ser acessado neste link: http://learn.knockoutjs.com/

Neste artigo eu vou tratar de conceitos básicos para utilização de Knockout em páginas ASP .NET mostrando como persistir o view-model entre os postbacks.

Recursos usados:

  1. Microsoft Visual Studio Express 2012 for web
  2. Library Knockout (versão atual 2.2.1)
  3. Library jQuery (versão atual 1.9.1

Obs: Eu estou usando as versões para desenvolvimento que não estão compactadas. Para produção use as versões compactadas das library jQuery e Knockout.

Criando o projeto ASP .NET

Abra o Visual Studio Express 2012 for web e no menu FILE clique em New Project e selecione o template Visual C# -> Web e a seguir ASP .NET Web Application informando o nome Knockout_Basico;

A seguir vamos criar uma pasta no projeto chamada Scripts. Clique com o botão direito do mouse sobre o projeto e selecione Add -> New Folder e informe o nome Scripts.

Agora vamos incluir a biblioteca Knockout.js que você já baixou do site oficial na pasta Scripts. Clique com o botão direito sobre a pasta e selecione Add-> Existing Item e selecione o arquivo knockout-2.2.1.js do na sua máquina local.

Vamos agora incluir uma página no projeto. Clique no menu PROJECT e a seguir em Add New Item e selecione o template Web Form informando o nome Default.aspx e clicando em Add;

Neste momento o seu projeto deverá ter a seguinte estrutura:

Vamos abrir a página Default.aspx e no modo Source vamos incluir o código abaixo:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Knockout_Basico.Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Knockout Básico</title>
    <script type="text/javascript" src="Scripts/knockout-2.2.1.js"></script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <span class="auto-style4"><strong><span class="auto-style5">Macoratti.net - Knockout Básico</span>
</strong></span>
        <hr>
     <table border="1">
    <tbody><tr>
        <td class="auto-style3">
            <span class="auto-style2">Alterar a visibilidade da próxima linha</span>
            <input type="checkbox" data-bind="checked: isRowVisible" class="auto-style2" />
<span class="auto-style2"> </span>
        </td>
    </tr>
    <tr data-bind="visible: isRowVisible">
        <td class="auto-style2">
            Esta é a linha cuja visibilidade pode ser alterada.
        </td>
    </tr>
   </tbody></table>
    </div>
    </form>
</body>
    <script type="text/javascript">
        var viewModel = { isRowVisible: ko.observable(false) };
        ko.applyBindings(viewModel);
    </script>
</html>
Vejamos o código destacado em azul:
  • Na seção <head> estamos definindo a library do Knockout que estamos usando e sua localização na pasta Scripts

<script type="text/javascript" src="Scripts/knockout-2.2.1.js"></script>

  • Efetuamos a vinculação usando o atributo data-binding ao controle HTML checkbox onde a propriedade checked
    será true quando o elemento for visível;
  • A seguir estamos efetuando vinculações aplicando os atributos data-binding para os elementos HTML.
    A sintaxe é bem simples: data-bind="visible : isRowVisible" significa que este elemento é visível se a
    propriedade isRowVisible for true.
  • No outro bloco de script temos o coração do Knockout: o objeto ViewModel.
    O objeto ViewModel possui propriedades que são usadas nas vinculações. Assim isRowVisible é uma propriedade do
    viewmodel. Como vemos os valores dessa propriedade são atribuídos com ajuda da função ko.observable. Essa função
    permite que o sistema controle as alterações da propriedade enviando-as aos elementos HTML.
<script type="text/javascript">
     var viewModel = { isRowVisible: ko.observable(false) };
      ko.applyBindings(viewModel);
</script>

A chamada a  ko.applyBindings(viewModel); é quem faz com que tudo ocorra.

Usando controles ASP .NET

No exemplo acima usamos um controle HTML ( <input type="checkbox" ) e se usarmos o controle ASP .NET checkbox conforme abaixo:

<asp:CheckBox ID="chkChangeVisibility" runat="server" data-bind="checked: isRowVisible" />

Se comentarmos a linha onde usamos o controle HTML e inserirmos a linha acima ao rodarmos o projeto vamos constatar que o código não funciona. Para que ele funcione temos que definir no code-behind do arquivo Default.aspx.cs o código abaixo no evento Load:

using System;

namespace Knockout_Basico
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            chkChangeVisibility.InputAttributes["data-bind"] = "checked: isRowVisible";
        }
    }
}

Persistindo o View Model entre os postbacks

É usual para uma página ASP.NET fazer postagens para o servidor. Se fizermos uma postagem na página do , nosso exemplo, vai parecer que após a postagem, todas as nossas alterações são perdidas. Este é um problema comum com alterações JavaScript. Nosso Model View será carregado novamente do zero e o estado inicial será estabelecido novamente. É o comportamento normal para o modelo ASP .NET.

Para salvar as mudanças feitas vamos usar a mesma lógica que de como ViewState é mantido na ASP.NET. Vamos guardar o nosso Model View em um campo oculto.

Então temos que adicionar um campo oculto para a nossa página definindo o código: <input type="hidden" runat="server" id="hViewStateStorage" />

Vamos definir o código acima logo abaixo da declaração : <form id="form1" runat="server">

Agora devemos escrever nossa Model View para este campo. Na maioria dos casos, o estado inicial do controle é definido no lado do servidor. Nós vamos usar esta técnica também. No lado do servidor, vamos criar um objeto do Model View, serializá-lo no formato JSON e colocar a string JSON no campo oculto.

Alterando o código do evento Load teremos:

using System;
using System.Web.Script.Serialization;

namespace Knockout_Basico
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            chkChangeVisibility.InputAttributes["data-bind"] = "checked: isRowVisible";

            if (!IsPostBack)
            {
                var viewModel = new { isRowVisible = true };
                var serializer = new JavaScriptSerializer { MaxJsonLength = int.MaxValue };
                var json = serializer.Serialize(viewModel);
                hViewStateStorage.Value = json;
            }
        }
    }
}

Como você pode ver, usamos uma classe anônima e isso significa que você não precisa criar uma classe separada para o modelo de exibição no lado do servidor.

Outra possibilidade aqui é mudar o View Model durante o postback. Como ele é armazenado em um campo oculto, você pode extrair o Model-View a partir daí, desserializá-lo, analisar e alterar suas propriedades, serializá-lo e colocá-lo no campo oculto novamente. Neste caso, você precisa criar uma classe separada para o View Model para desserializar o modelo.

Agora temos que extrair o View Model do campo oculto no código JavaScript. Aqui está como fazer isso:

var stringViewModel = document.getElementById('<%=hViewStateStorage.ClientID %>').value;
var viewModel = ko.utils.parseJson(stringViewModel);

Aqui usamos a função parseJson da biblioteca KnockoutJS para converter a representação de string em um objeto JavaScript.

Mas agora estamos diante de um pequeno problema. Como eu disse, todas as propriedades do Model-View devem ser inicializadas usando a função ko.observable, o que não ocorre aqui. O seguinte código resolve o problema:

for (var propertyName in viewModel) {
      viewModel[propertyName] = ko.observable(viewModel[propertyName]);   
}

Agora, o Model View vem do servidor perfeitamente. A única coisa a fazer é guardá-lo para o campo oculto antes de postagem. Eu usei jQuery para assinar o evento de postagem:

$(document.forms[0]).submit(function () {
for (var propertyName in viewModel) {
viewModel[propertyName] = ko.utils.unwrapObservable(viewModel[propertyName]);
}

document.getElementById('<%=hViewStateStorage.ClientID %>').value = ko.utils.stringifyJson(viewModel);
 

E com isso conseguirmos o nosso intento persistir o View Model entre o postbacks.

Veja o código completo do arquivo Default.aspx abaixo:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Knockout_Basico.Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Knockout Básico</title>
    <script type="text/javascript" src="Scripts/knockout-2.2.1.js"></script>
</head>
<body>
    <form id="form1" runat="server">
    <input type="hidden" runat="server" id="hViewStateStorage" />
    <div>
        <span class="auto-style4"><strong><span class="auto-style5">Macoratti.net - Knockout Básico</span></strong></span>
        <hr>
     <table border="1">
    <tbody><tr>
        <td class="auto-style3">
            <span class="auto-style2">Alterar a visibilidade da próxima linha</span>
              <!--<input type="checkbox" data-bind="checked: isRowVisible" class="auto-style2" />-->
              <asp:CheckBox ID="chkChangeVisibility" runat="server" data-bind="checked: isRowVisible" />
            <span class="auto-style2"> </span>
        </td>
    </tr>
    <tr data-bind="visible: isRowVisible">
        <td class="auto-style2">
            Esta é a linha cuja visibilidade pode ser alterada.
        </td>
    </tr>
   </tbody></table>
   <input id="Submit1" runat="server" type="submit" />
    </div>
    </form>
</body>
    <script type="text/javascript">
        var stringViewModel = document.getElementById('<%=hViewStateStorage.ClientID %>').value;
        var viewModel = ko.utils.parseJson(stringViewModel);

        for (var propertyName in viewModel) {
            viewModel[propertyName] = ko.observable(viewModel[propertyName]);
        }

        ko.applyBindings(viewModel);

        $(document.forms[0]).submit(function () {
            for (var propertyName in viewModel) {
                viewModel[propertyName] = ko.utils.unwrapObservable(viewModel[propertyName]);
            }

            document.getElementById('<%=hViewStateStorage.ClientID %>').value =
                                ko.utils.stringifyJson(viewModel);
    });
    </script>
</html>

Joã 12:24 Em verdade, em verdade vos digo: Se o grão de trigo caindo na terra não morrer, fica ele só; mas se morrer, dá muito fruto.

Joã 12:25 Quem ama a sua vida, perdê-la-á; e quem neste mundo odeia a a sua vida, guardá-la-á para a vida eterna.

Referências:


José Carlos Macoratti