terzo articoloQuesto articoli, incentrato sull’interazione del modello (@model) in una pagina MVC (Model View) e il codice HTML/Javascript, è la naturale prosecuzione dei precedenti:

Realizzare un’applicazione sigle page

Utilizzare un modello per “passare le informazioni” ad una vista

In questi articoli abbiamo visto come costruire lo scheletro della pagina e quindi come “immettere” nella pagina tutti i dati necessari per il corretto funzionamento. Il passo successivo è costruire la pagina utilizzando le informazioni del modello.

Confrontandomi con Andrea, che si occupa di interfacce e usabilità, è emersa l’idea di gestire la prenotazione al solito modo del carrello della spesa di un “normale” E-Commerce. Per fare questo però è indispensabile sapere quante stanze sono disponibili per ogni tipologia. Nella struttura della classe RoomType questa informazione era mancante, per questo l’abbiamo modificata, aggiungendo il campo AvailableQuantity, nel seguente modo:

public class RoomType
{
    public string Code { get; set; }
    public string Description { get; set; }
    public int MinPersonsIn { get; set; }
    public int MaxPersonsIn { get; set; }
    public int MaxAdultCapablity { get; set; }
    public int EnfantCapability { get; set; }
    public double EnfantCost { get; set; }
    public int MaxAgeForChildren { get; set; }
    public double MinPrice { get; set; }
    public double MaxPrice { get; set; }
    public int AvailableQuantity { get; set; }
    public RoomType()
    {
        this.Code = "";
        this.Description = "";
        this.MinPersonsIn = 1;
        this.MaxPersonsIn = 1;
        this.EnfantCapability = 0;
        this.EnfantCost = 0;
        this.MaxAgeForChildren = 0;
        this.MaxAdultCapablity = 1;
        this.MinPrice = 0;
        this.MaxPrice = 0;
        this.AvailableQuantity = 1;
    }
 
}

In un approccio alla progettazione di questo tipo diventa più semplice aggiungere informazioni gestibili. L’unica cosa che abbiamo fatto è stata quella di aggiornare il modello, non preoccupandoci per il momento di come questi dati verranno gestiti nell’indispensabile database di supporto.

Quello che andremmo a fare è costruire _AvailabilityResult.cshtml. Poiché la pagina si basa su un modello, la sua prima istruzione è:

@model IEnumerable<BookingSystem.Models.AvailabilityResponse>

Quindi questa pagina si aspetta che il controller le invii una serie di oggetti AvailabilityResponse sui quali è possibile fare iterazioni (IEnumerable).

Per comodità riportiamo la struttura della classe:

public class AvailabilityResponse
    {
        public RoomType AvailRoomType { get; set; }
        public List<PriceByConposition> CompositionsPrices { get; set; }
        public AvailabilityResponse()
        {
            this.AvailRoomType = new RoomType();
            this.CompositionsPrices = new List<PriceByConposition>();
        }
    }

Quello che notiamo è che possiamo dividere la classe in una “testata”, la RoomType e in una serie di “righe”, le PriceByConposition. Con questa premessa ogni oggetto del modello dovrà essere utilizzato “leggendo” una volta sola la testata e iterando la lettura su tutte le righe, con del codice tipo il seguente:

@foreach(var rt in Model)
{
    <ul>
        <li>
            //Testata
                <ul>
                    @foreach(var rp in rt.CompositionsPrices)
                    {
                        //iterazione sulle singole righe
                    }
                </ul>
        </li>
    </ul>
}

Scendendo più nel dettaglio vediamo che tipo di informazioni possiamo immaginare di gestire dalla testata:

//inizio testata
@rt.AvailRoomType.Description Numero persone da @rt.AvailRoomType.MinPersonsIn a @rt.AvailRoomType.MaxPersonsIn<br />
@if(rt.AvailRoomType.EnfantCapability>0)
{
    <span>
        Si possono ospitare fino a @rt.AvailRoomType.EnfantCapability bebè richiedendo il letto apposito disponibili @rt.AvailRoomType.AvailableQuantity stanze
        Bebè: 
        <select id="selEnfant-@rt.AvailRoomType.Code" data-cost="@rt.AvailRoomType.EnfantCost" onclick="setEnfantCost(this)">
            <option value="0">
                ----
            </option>
            @for (int b = 1; b <= rt.AvailRoomType.EnfantCapability; b++)
            {
                <option value="@b">
                    @b
                </option>
            }
        </select>
        <br />
    </span>
}
Prezzo da @rt.AvailRoomType.MinPrice a @rt.AvailRoomType.MaxPrice
//fine testata

Per ogni oggetto del Model (@model) rt rappresenta un oggetto di tipo RoomType, quindi possiamo richiamare le informazioni che sono in esso contenute. Come evidente dal @ abbiamo scelto “razor” come metodo di Markup.
Per il numero di bebè in stanza abbiamo optato per una select perché da specifiche ogni bebè aggiunto ha un costo fisso, proprietà EnfantCost della classe RoomType. Quindi la scelta del numero di bebé dovrà avere influenza anche sul costo finale.

Per quanto riguarda invece il costo di ogni combinazione (le righe) il codice:

@foreach(var rp in rt.CompositionsPrices)
{
    <li>
        <input type="checkbox" id="chk-@rt.AvailRoomType.Code-@rp.AdultsNumber-@rp.Children-@rp.EnfatsNumber" class="SpecificCheck-@rt.AvailRoomType.Code" onchange="setCheckBox(this)"/>
        Adulti: @rp.AdultsNumber - Ragazzi: @rp.Children - Bebè: @rp.EnfatsNumber per totale occupanti: @rp.OccupantsNumber
        Trattamento scelto:
        <select id="selTrat-@rt.AvailRoomType.Code-@rp.AdultsNumber-@rp.Children-@rp.EnfatsNumber" onclick="RoomTypeCustomization(this)">
            @foreach (var el in rp.CostBySupplements)
            {
                <option id="selTrat-@rt.AvailRoomType.Code-@rp.AdultsNumber-@rp.Children-@rp.EnfatsNumber-@el.Code-@el.Price">
                    @el.Description
                </option>
            }
        </select>
        <input type="text" value="@rp.Price" id="txtPrice-@rt.AvailRoomType.Code-@rp.AdultsNumber-@rp.Children-@rp.EnfatsNumber" />
        <input type="hidden" value="@rp.Price" id="hdnPrice-@rt.AvailRoomType.Code-@rp.AdultsNumber-@rp.Children-@rp.EnfatsNumber" />
    </li>
 <li>
    Costo per il tipo stanza 
    <input type="text" id="totalCost-@rt.AvailRoomType.Code" value="0">
    <input type="text" id="costNoEnfat-@rt.AvailRoomType.Code" value="0">
    <input type="text" id="costEnfat-rt.AvailRoomType.Code" value="0">                    
</li>
<li>
    <span style="display:none" id="add-@rt.AvailRoomType.Code">
        Aggiungi la stanza alla tua scelta >>
    </span>
</li>

}

Con questo codice la vista dei risultati diventa:

screen

Ovviamente per ora non ci soffermiamo sull’aspetto grafico, ma concentrandoci sulle risposte agli input degli utenti e sulle specifiche di progetto, definiamo una serie di funzioni javascript (richiamate in vari eventi nel codice sopra scritto) di seguito elencate:

<script>
    function RoomTypeCustomization(el)
    {
        var sH = el.id.split('-');
        var price = parseInt(el.value);
        var elBasePrice = document.getElementById("hdnPrice-" + sH[1] + "-" + sH[2] + "-" + sH[3] + "-" + sH[4]);
        var elEnfant = document.getElementById("selEnfant-" + sH[1]);
        var msglist = $("#selEnfant-" + sH[1]);
        var cost = msglist.data("cost") * elEnfant.value;
        var tot = parseInt(elBasePrice.value) + price;
        var enfatCost = parseInt($("#costEnfat-" + sH[1]).val()) * elEnfant.value;
        if ($(el)[0].selectedIndex == 0) {
            $("#chk-" + sH[1] + "-" + sH[2] + "-" + sH[3] + "-" + sH[4]).prop('checked', false);
            tot = 0;
        }
        else
        {
            $("#chk-" + sH[1] + "-" + sH[2] + "-" + sH[3] + "-" + sH[4]).prop('checked', true);
            setSelect(el);
        }
        $("#costNoEnfat-" + sH[1]).val(tot);
        tot += enfatCost;
        $("#totalCost-" + sH[1]).val(tot);
        testVisibility(el);
    }
    function setEnfantCost(el)
    {
        var msglist = $(el);
        var sH = el.id.split('-');
        var elEnfant = document.getElementById("selEnfant-" + sH[1]);
        var cost = msglist.data("cost") * elEnfant.value;
        document.getElementById("costEnfat-" + sH[1]).value = cost;
        var costNoEnfant = $("#costNoEnfat-" + sH[1]).val();
        var tot = parseInt(costNoEnfant) + cost;
        $("#totalCost-" + sH[1]).val(tot);
    }
    function setCheckBox(el)
    {
        var roomType = el.id.split('-')[1];
        $(".SpecificCheck-" + roomType).each(function () {
            $(this).prop('checked', false);
        });
        $(el).prop('checked', true);
    }
    function setSelect(el)
    {
        var roomType = el.id.split('-')[1];
        $("[id^=selTrat-" + roomType + "]").each(function () {
            
            if($(this).attr('id') == el.id)
            {
            }
            else
            {
                var _chk = $(this).attr('id').replace("selTrat", "chk");
                console.log(_chk);
                $("#" + _chk).prop('checked', false);
                $(this)[0].selectedIndex = 0;
            }
        });
    }
    function testVisibility(el)
    {
        var sH = el.id.split('-');
        if(parseInt($("#totalCost-" + sH[1]).val())>0)
        {
            $("#add-" + sH[1]).show();
        }
        else {
            $("#add-" + sH[1]).hide();
        }
    }
</script>

Nel definire il codice HTML e le funzioni javascript abbiamo usato alcune tecniche che saranno oggetto dei successivi articoli