Binding af en TypeScript ViewModel til HTML ved hjælp af Knockout — Visual Studio Magazine
9 mins read

Binding af en TypeScript ViewModel til HTML ved hjælp af Knockout — Visual Studio Magazine


Den praktiske klient

Binding af en TypeScript ViewModel til HTML ved hjælp af Knockout

Det er fantastiske byggeobjekter i TypeScript, men det er ikke meget godt, medmindre du kan binde disse objekter til en webside. Her er, hvordan du integrerer TypeScript med Knockout (og en advarsel om, hvor testdrevet udvikling ser ud til at stoppe).

I denne kolonne vil jeg endelig levere det, jeg har bygget hen imod de sidste par måneder: En HTML-side i klienten, der bruger TypeScript til at integrere Web API-tjenester med en ViewModel på klientsiden. I løbet af de sidste par kolonner har jeg bygget en klientside ViewModel i TypeScript og nogle Web API-tjenester. I kolonnen om Web Services brugte jeg TypeScript med jQuery og JSON til at kalde en metode på tjenesten og få en liste over kundeobjekter tilbage. I denne kolonne vil jeg integrere en TypeScript ViewModel ved hjælp af Knockout til at oprette en side, der viser denne liste over kunder i en rulleliste og derefter opdaterer en tekstboks med data fra objektet valgt i rullelisten (se Patrick Steeles artikel for mere om Knockout .)

Før jeg skriver nogen kode, bruger jeg NuGet til at tilføje både Knockout JavaScript-biblioteket og DefinitelyTyped Knockout-filen til mit test- og webprojekt, der understøtter arbejde med Knockout i TypeScript. Typedefinitionerne i DefinitelyTyped-filen giver mig IntelliSense-understøttelse, når jeg skriver kode og tillader TypeScript-kompileren at kontrollere, at jeg bruger Knockout-biblioteket korrekt.

For at få design-time-understøttelse af Knockout, trækker jeg DefinitelyTyped-filen fra Solution Explorer over til min ViewModel-kodefil i Visual Studios editor-vindue. Når jeg slipper filen, tilføjer Visual Studio en TypeScript-reference som denne:

/// <reference path="typings/knockout/knockout.d.ts" />

Jeg får nu IntelliSense-understøttelse til Knockout (og nogle nye Knockout-datatyper til at erklære egenskaber), mens jeg skriver min TypeScript-kode.

Integrering med Knockout
For at bruge min ViewModel med Knockout til at gøre de kundeobjekter, jeg henter fra min tjeneste tilgængelige, skal jeg tilføje et Knockout-observerbart array til at holde kundeobjekterne (senere kan jeg binde det array til HTML-elementer i min brugergrænseflade ved hjælp af Knockouts bindende syntaks). Da jeg begyndte at skrive denne kode, besluttede jeg, at den grænseflade, jeg oprettede tidligere for at definere min ViewModel, tilføjede arbejde og ikke gav mig meget (noget) til gengæld. Så jeg slettede min ViewModel-grænseflade og tilføjede lige en egenskab til min ViewModel.

Jeg ringer til ejendommen for at holde min samling af kunder og erklærer den som en KnockoutObservableArray (som defineret i DefinitelyTyped-filen). Fordi jeg bruger TypeScript 9.1, kan jeg bruge den generiske version af KnockoutObservableArray-typen og specificere den type objekt, jeg vil holde i arrayet (i dette tilfælde objekter, der implementerer min ICustomer-grænseflade, defineret i mit AdventureWorksEntities-modul ).

Det er dog ikke nok at definere en egenskab i en klasse — egenskaber vises ikke på et TypeScript-objekt, medmindre det er sat til en værdi. Den bedste måde at håndtere dette på er at initialisere egenskaben i ViewModels konstruktør. Jeg kan tilføje egenskaben til min ViewModel og initialisere den i ét trin ved at tilføje en offentlig parameter til min ViewModels konstruktør:

constructor(private cust: AdventureWorksEntities.ICustomer = null,
            private custs: AdventureWorksEntities.ICustomer[]= [],
            public customers: 
              KnockoutObservableArray<AdventureWorksEntities.ICustomer> = 
                            ko.observableArray([])) {}

Jeg har allerede en metode kaldet fetchCustomers i min ViewModel, der henter alle kundeobjekter fra en Web API-tjeneste. I øjeblikket indlæser denne metode bare den hentede samling i et internt felt kaldet custs (caster samlingen til min ICustomer-grænseflade, mens den gør det). Jeg får den metode til at indlæse min observableArray også:

fetchAllCustomers()
{
  $.getJSON("http://localhost:49306/CustomerManagement",
            cs => {
                    this.custs = <AdventureWorksEntities.ICustomer[]>cs;
                    this.customers = ko.observableArray(this.custs);
                });                  
}

Jeg ønsker at vise denne samling i en rulleliste, der viser kundens efternavn. For at få det til at ske med Knockout, tilføjer jeg et select tag til min side og bruger Knockouts bindende syntaks til at binde tagget til min ViewModel-kundeejendom. Det tag ser sådan ud:

<select id="CustomerList" 
        data-bind="options: customers,  
                   optionsText: 'LastName'"/>

Og her tog jeg en anden beslutning. Da jeg startede dette projekt, definerede jeg grænseflader og klasser til at repræsentere de objekter, som mine Web API-tjenester vil sende til mig. I min kode har jeg dog kun brugt grænsefladerne. Jeg besluttede at slette mine klassedefinitioner for mine objekter på serversiden og kun beholde deres grænseflader. Hver af disse grænseflader definerer egenskaber, der matcher egenskaberne på mine serversideobjekter. Min kundegrænseflade ser for eksempel sådan ud:

export interface ICustomer 
{
  Id: number;
  FirstName: string;
  LastName: string;
  CustomerType: CustomersType;
}

Efter at have hentet samlingen af ​​ICustomer-objekter fra min server, er jeg nødt til at kalde Knockouts applicationBindings-funktion og videregive den funktion til min ViewModel, for at få Knockout til at vise data fra ViewModels kunders egenskab i rullelisten. Som fan af testdrevet udvikling (TDD) vil jeg foretrække at lave en test, der beviser, at jeg med succes kan binde mine data til tagget, før jeg opretter siden.

Jeg foretrækker måske at gøre det, men jeg var ikke i stand til at få det til at virke. Det kan afspejle, at jeg gør noget forkert, eller at jeg arbejder med beta-software, eller at jeg bare spørger for meget fra mine testværktøjer (jeg bruger Qunit-biblioteket til at give mig de testfunktioner, jeg har brug for og Chutzpah Visual Studio-tilføjelsesprogrammet til at køre min kode). Hvis du er interesseret i det mislykkede eksperiment, kan du se denne artikels sidebjælke “Test med HTML, jQuery og Knockout i Chutzpah (eller ej).”

Integrering med siden
På min webside er mit første skridt at tilføje scriptreferencer til jQuery- og Knockout-bibliotekerne — og til mine egne JavaScript-biblioteker. Jeg skal huske at bruge .js-filerne genereret fra mine TypeScript-filer og ikke .ts-filerne, som jeg brugte i min testkode:

<script src="https://visualstudiomagazine.com/articles/2013/11/01/Scripts/jquery-2.0.3.js"></script>
<script src="Scripts/knockout-2.3.0.js"></script>
<script src="Scripts/AdventureWorksEntities.js"></script>
<script src="Scripts/SalesOrderMvvm.js"></script>

Da jeg oprettede en egentlig HTML-side, besluttede jeg at udvide Knockout-bindingerne i min rullelistes data-bind-attribut. Jeg udvider først mine optionerTekstbinding med en funktion, der samler kundens egenskaber for fornavn og efternavn. Jeg tilføjer også en optionsValue-binding, der specificerer, at Id-egenskaben på mit kundeobjekt skal bruges i indstillingselementerne, der genereres af Knockout. Til sidst tilføjer jeg en værdibinding for at få en egenskab kaldet id på min ViewModel opdateret med værdien fra den aktuelt valgte mulighed i rullelisten:

<select id='CustomerList' 
        data-bind="options: customers, 
        optionsText: function (cust) {
              return cust.FirstName + ' ' + cust.LastName},
        optionsValue: 'Id', 
        value: id "/>

Jeg tilføjer også en tekstboks og binder den til den samme id-egenskab på ViewModel:

<input id='CustId' type="text" data-bind="value: id"/>

Mit næste trin er at tilføje denne id-egenskab til min ViewModel og initialisere den. Igen gør jeg det ved at tilføje en offentlig parameter til min ViewModels konstruktør:

constructor(private cust: AdventureWorksEntities.ICustomer = null,
            private custs: AdventureWorksEntities.ICustomer[]= [],
            public customers: 
               KnockoutObservableArray<AdventureWorksEntities.ICustomer> =
                                               ko.observableArray([]),
            public id: KnockoutObservable<number> = ko.observable(0)){} 

Sidens kode til at indlæse rullelisten og koordinere den med tekstboksen på min webside er enkel: instansier ViewModel, kald metoden, der henter og indlæser samlingen af ​​kunder i kundens egenskab, og bind Knockout til ViewModel:

<script>

   $(function () 
   {
     var custVM;
     custVM = new SalesOrderMvvm.CustomerVM();
     custVM.fetchAllCustomers();
     ko.applyBindings(custVM);
   });

Jeg trykker på F5, og når min side kommer op, viser min rulleliste alle min kundes for- og efternavne; når jeg ændrer valget i rullelisten, viser min tekstboks id’et for den aktuelt valgte kunde. Succes!

Fordi jeg vil have en “ren” TypeScript-løsning, omskriver jeg min oprindelige JavaScript-kode til TypeScript, sætter den i en .ts-fil og erstatter min scriptblok med en reference til den genererede JavaScript-fil. Her er den samme kode i TypeScript:

/// <reference path="typings/knockout/knockout.d.ts" />
/// <reference path="SalesOrderMvvm.ts" />
/// <reference path="typings/jquery/jquery.d.ts" />

$(function () 
{
  var custVM: SalesOrderMvvm.CustomerVM;
  custVM = new SalesOrderMvvm.CustomerVM();
  custVM.fetchAllCustomers();
  ko.applyBindings(custVM);
});

Selvom fejlen i min Knockout-testkode kan være resultatet af, at jeg har gjort noget fjollet, har jeg beviser på, at jeg arbejder med værktøjer, der stadig finder ud af, hvordan man arbejder sammen. Efter at have foretaget en ændring af en test, for eksempel, skal jeg ofte trykke Ctrl_S to gange, når jeg gemmer min fil. Første gang jeg gemmer min fil, konspirerer Visual Studio/Web Essentials/TypeScript sammen for at generere JavaScript-versionen af ​​min kode; anden gang jeg gemmer min fil, konspirerer Visual Studio Test og Chutzpah sammen for at finde min test og liste den i Visual Studios Test Explorer-vindue.

På den anden side har jeg nu en arbejdsside, der gør noget næsten nyttigt, og endnu vigtigere, jeg har IntelliSense og TDD-understøttelse gennem hele processen – ja, bortset fra TDD-koden. Jeg er stadig glad.


Om forfatteren

Peter Vogel er systemarkitekt og rektor i PH&V Information Services. PH&V leverer full-stack rådgivning fra UX-design over objektmodellering til databasedesign. Peter tweeter om sine VSM-spalter med hashtagget #vogelarticles. Hans blogindlæg om brugeroplevelsesdesign kan findes på http://blog.learningtree.com/tag/ui/.

Leave a Reply

Your email address will not be published. Required fields are marked *