venerdì 17 febbraio 2012

Knockoutjs e il pattern MVVM lato client - Introduzione

In questi giorni sto sperimentando l'utilizzo della libreria Knockoutjs per realizzare un box di offerte.
Nel post precedente descrivevo la parte server del progetto e il caricamento dei dati via jquery.

Avendo sperimentato i vantaggi dell'utilizzo del ViewModel nella costruzione delle pagine ho pensato se fosse possibile fare una cosa simile anche sul client realizzando un Model View ViewModel lato client.

Googolando un pò ho trovato Knockoutjs leggendo la documentazione mi è sembrata la soluzione al problema che volevo risolvere.

E' possibile definire un viewmodel, definire l'associazione del viewmodel con gli elementi della pagina e infine eseguire il binding. E' definito anche un sistema di templating con un minimo di funzioni di elaborazioni quali istruzioni condizionali e cicli.


<div class="Informazioni">
        <div style="float:left; width: 150px; margin:0px 14px 0px 14px;">
            <strong>1 adulto + 1 bambino</strong>
            <div data-bind="template: { name: 'offerta-template', foreach: primoBox}"></div>
            </div>
La libreria sfrutta l'attributo data-bind, l'istruzione template istituisce l'associazione del div ad un blocco di codice che verrà ripetuto per quanti oggetti verranno trovati nel vettore "primoBox".

 <script type="text/html" id="offerta-template">
<a data-bind="attr: {href: urlInfoStruttura}"><img data-bind="attr: {src: imagePath}" border="0" /></a><br />
<p><a data-bind="attr: {href: urlInfoStruttura}"><h2><span data-bind="text: nomeStruttura" style="font-size: 11px;"></span></h2></a>
<span data-bind="text: localitaStruttura" style="font-size: 11px; color: #E15204"></span><br />
<strong>da <span data-bind="text: puntiStep" style="color: #E15204"></span> <span style="color: #E15204">punti</span></script>
</p>
</script>
 Questo blocco definisce il template, si può notare l'associazione tra l'html e le proprietà del viewmodel ad esempio il tag a avrà come valore dell'attributo href il valore della proprietà urlInfoStruttura associato all'oggetto attuale del vettore primoBox.

var HomeViewModel = {
primoBox: ko.observableArray([]),
secondoBox: ko.observableArray([]),
terzoBox: ko.observableArray([]),
quartoBox: ko.observableArray([]),
offerteCompleto: ko.observableArray([]),
};
questa è la definizione del viewmodel associato alla pagina.

ko.dependentObservable(function() {
$.getJSON("http://..?num=8&callback=?",  
function(data) {
 CaricaDati(data);
})},HomeViewModel);
ko.applyBindings(HomeViewModel);

Questo blocco di codice è il cuore del gioco.  Abbiamo il caricamento di un json con i dati e l'associazione della pagina al viewmodel, infine il binding dei dati. La funzione carica dati è quella che prende i dati dal json e carica le proprietà di HomeViewModel. Ad esempio:

HomeViewModel.offerteCompleto.push({nomeStruttura: item.NomeStruttura, imagePath: imgpath, idStruttura: item.IdStruttura, localitaStruttura: item.Localita,puntiStep: "2000", urlInfoStruttura: urlitem});
 Knockoutjs è un file da 39 kb per ora il grosso vantaggio è una maggiore leggibilità e la migliore modularità del codice... Ma è solo l'inizio.

lunedì 13 febbraio 2012

jquery asp.net mvc e chiamate cross domain

Pensavo fosse una cosa semplicissima.

Il progetto: chiamare una pagina nel dominio del motore di ricerca e ottenere le strutture messe in offerta in tempo reale.

La soluzione: creare un nuovo controllore, la relativa azione, configurare il routing in modo che la richiesta di un indirizzo corrisponda la restituzione di json completo di tutte le strutture.

Sembrava una cosa semplicissima il controller sfruttando la potenza del framework .Net:

[HttpGet]
        public JsonResult GetListaOfferte(int num = 0)
        {
            IEnumerable<ProdottoListinoViewModel> listaRisultati = _siteService.GetProdottoOffertaCatalogo();
            return Json(listaRisultati.Distinct(new ProdottoListinoViewModelComparer()).ToList().Take(num), JsonRequestBehavior.AllowGet);
        } 
Il passo successivo è stata la scrittura del javascript:


$.ajax({
url: "(..)/api/GetListaOfferte",
data: {num: "8"},
type: "GET", contentType: "application/json; charset=utf-8", success: function(response) {
alert(response);
}, dataType: "json"
});

Da questo punto in poi sono cominciati i guai. Il problema è che $.ajax subisce i limiti di sicurezza definiti dalla same-domain policy.

La soluzione è stata utilizzare JSONP e quindi implementare un action filter (grande .Net) e poi cambiare la chiamata ajax:


public class JsonpFilterAttribute : ActionFilterAttribute    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext == null)
                throw new ArgumentNullException("filterContext");
            
            string callback = filterContext.HttpContext.Request.QueryString["callback"];
            if (callback != null && callback.Length > 0)
            {
               
                JsonResult result = filterContext.Result as JsonResult;
                if (result == null)
                {
                    throw new InvalidOperationException("JsonpFilterAttribute must be applied only " +
                        "on controllers and actions that return a JsonResult object.");
                }
                filterContext.Result = new JsonpResult                {
                    ContentEncoding = result.ContentEncoding,
                    ContentType = result.ContentType,
                    Data = result.Data,
                    Callback = callback
                };
            }
        }
    }


aggiungere l'attributo [JsonpFilter] all'azione del controller.
E infine modificare la chiamata ajax:


$.getJSON(
"http://(...)/api/GetListaOfferte?callback=?",
{num: "8" },
function(data) {
alert(data);
});