Jak używać nokaut.js z ASP.NET MVC ViewModels?

Bounty

Minęło trochę czasu i wciąż mam kilka pytań. Mam nadzieję, że dodając nagrodę, może te pytania otrzymają odpowiedź.
  1. Jak używać helperów html z knockout.js
  2. Dlaczego dokument był gotowy do działania (zobacz pierwszą edycję, aby uzyskać więcej informacji)

  3. Jak zrobić coś takiego, Jeśli używam mapowania knockout z moimi modelami widoku? Ponieważ nie mam funkcji ze względu na mapowanie.

    function AppViewModel() {
    
        // ... leave firstName, lastName, and fullName unchanged here ...
    
        this.capitalizeLastName = function() {
    
        var currentVal = this.lastName();        // Read the current value
    
        this.lastName(currentVal.toUpperCase()); // Write back a modified value
    
    };
    
  4. Chcę używać wtyczek na przykład Chcę być w stanie rollback obserwables tak, jakby Użytkownik anulował żądanie chcę być w stanie wrócić do ostatniej wartości. Z moich badań wynika, że można to osiągnąć przez ludzi tworzących wtyczki takie jak editables

    Jak używać czegoś takiego, Jeśli używam mapowania? Naprawdę nie chcę iść do metody, w której mam w widoku ręczne mapowanie, gdy mapuję każde pole MVC viewMode do pola Modelu ko jako Chcę jak najmniej wbudowanego javascript, jak to możliwe, a to po prostu wydaje się podwoić pracę i dlatego lubię to mapowanie.

  5. Jestem zaniepokojony, że aby ułatwić tę pracę (za pomocą mapowania) stracę dużo mocy KO, ale z drugiej strony jestem zaniepokojony, że ręczne mapowanie będzie po prostu dużo pracy i sprawi, że moje poglądy zawierają zbyt wiele informacji i może stać się w przyszłości trudniejsze do utrzymania(powiedzmy, jeśli usunę właściwość w modelu MVC muszę przenieść ją również w Ko viewmodel)


Original Post

Używam asp.net mvc 3 i patrze na nokaut bo wyglada calkiem fajnie ale ciezko mi sie zorientowac jak to dziala z asp.net mvc szczególnie zobaczyć modele.

Dla mnie teraz robię coś takiego

 public class CourseVM
    {
        public int CourseId { get; set; }
        [Required(ErrorMessage = "Course name is required")]
        [StringLength(40, ErrorMessage = "Course name cannot be this long.")]
        public string CourseName{ get; set; }


        public List<StudentVm> StudentViewModels { get; set; }

}

Chciałbym mieć maszynę Wirtualną, która ma kilka podstawowych właściwości, takich jak CourseName i będzie miała prostą walidację. Model maszyny Wirtualnej może zawierać inny widok modele w nim, jak również w razie potrzeby.

Następnie przekazałbym tę maszynę wirtualną do widoku, gdybym użył helperów html, aby pomóc mi wyświetlić ją użytkownikowi.

@Html.TextBoxFor(x => x.CourseName)

Mogę mieć jakieś pętle foreach lub coś, aby uzyskać dane z kolekcji modeli widoku uczniów.

Następnie, kiedy składałem formularz, używałem jquery i serialize array i wysyłałem go do metody akcji kontrolera, która wiązałaby go z powrotem do viewmodel.

Z nokautem.js jest inaczej jak teraz mam do tego viewmodels i ze wszystkich przykładów widziałem, że nie używają helperów html.

Jak wykorzystać te 2 funkcje MVC z nokautem.js?

Znalazłem ten film i to krótko (ostatnie kilka minut wideo @ 18:48) idzie w sposób, aby korzystać z viewmodels w zasadzie o skrypt wbudowany, który ma knockout.js viewmodel, który otrzymuje przypisane wartości w ViewModel.

Czy to jedyny sposób, aby to zrobić? A może w moim przykładzie z kolekcja viewmodels w nim? Czy muszę mieć pętlę foreach lub coś takiego, aby wyodrębnić wszystkie wartości i przypisać je do knockout?

Co do helperów html to filmik nic o nich nie mówi.

Są to obszary 2, które dezorientują mnie jak niewiele osób wydaje się mówić o tym i pozostawia mnie zdezorientowany jak początkowe wartości i wszystko dociera do widoku, gdy kiedykolwiek przykład jest tylko jakąś twardo zakodowaną wartością przykład.


Edit

Próbuję tego, co zasugerował Darin Dimitrov i wydaje się to działać (musiałem jednak wprowadzić pewne zmiany w jego kodzie). Nie jestem pewien, dlaczego musiałem użyć document ready, ale jakoś wszystko bez niego nie było gotowe.

@model MvcApplication1.Models.Test

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title>Index</title>
    <script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
    <script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
    <script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
   <script type="text/javascript">

   $(function()
   {
      var model = @Html.Raw(Json.Encode(Model));


// Activates knockout.js
ko.applyBindings(model);
   });

</script>

</head>
<body>
    <div>
        <p>First name: <strong data-bind="text: FirstName"></strong></p>
        <p>Last name: <strong data-bind="text: LastName"></strong></p>
        @Model.FirstName , @Model.LastName
    </div>
</body>
</html>

Musiałem owinąć go wokół gotowego dokumentu jquery, aby działał.

Ja też dostaję to Ostrzeżenie. Nie wiem, o co w tym wszystkim chodzi.
Warning 1   Conditional compilation is turned off   -> @Html.Raw

Więc mam punkt wyjścia, myślę, że przynajmniej zaktualizuje kiedy zrobiłem trochę więcej zabawy i jak to działa.

Próbuję przejrzeć interaktywne samouczki, ale zamiast tego używam modelu widoku.

Nie wiem, jak poradzić sobie z tymi częściami

function AppViewModel() {
    this.firstName = ko.observable("Bert");
    this.lastName = ko.observable("Bertington");
}

Lub

function AppViewModel() {
    // ... leave firstName, lastName, and fullName unchanged here ...

    this.capitalizeLastName = function() {
        var currentVal = this.lastName();        // Read the current value
        this.lastName(currentVal.toUpperCase()); // Write back a modified value
    };


edycja 2

Udało mi się rozwiązać pierwszy problem. Nie mam pojęcia o drugim problemie. Ale jednak. Ktoś ma jakieś pomysły?

 @model MvcApplication1.Models.Test

    @{
        Layout = null;
    }

    <!DOCTYPE html>

    <html>
    <head>
        <title>Index</title>
        <script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
        <script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
        <script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
       <script type="text/javascript">

       $(function()
       {
        var model = @Html.Raw(Json.Encode(Model));
        var viewModel = ko.mapping.fromJS(model);
        ko.applyBindings(viewModel);

       });

    </script>

    </head>
    <body>
        <div>
            @*grab values from the view model directly*@
            <p>First name: <strong data-bind="text: FirstName"></strong></p>
            <p>Last name: <strong data-bind="text: LastName"></strong></p>

            @*grab values from my second view model that I made*@
            <p>SomeOtherValue <strong data-bind="text: Test2.SomeOtherValue"></strong></p>
            <p>Another <strong data-bind="text: Test2.Another"></strong></p>

            @*allow changes to all the values that should be then sync the above values.*@
            <p>First name: <input data-bind="value: FirstName" /></p>
            <p>Last name: <input data-bind="value: LastName" /></p>
            <p>SomeOtherValue <input data-bind="value: Test2.SomeOtherValue" /></p>
            <p>Another <input data-bind="value: Test2.Another" /></p>

           @* seeing if I can do it with p tags and see if they all update.*@
            <p data-bind="foreach: Test3">
                <strong data-bind="text: Test3Value"></strong> 
            </p>

     @*took my 3rd view model that is in a collection and output all values as a textbox*@       
    <table>
        <thead><tr>
            <th>Test3</th>
        </tr></thead>
          <tbody data-bind="foreach: Test3">
            <tr>
                <td>    
                    <strong data-bind="text: Test3Value"></strong> 
<input type="text" data-bind="value: Test3Value"/>
                </td>
            </tr>    
        </tbody>
    </table>

Controller

  public ActionResult Index()
    {
              Test2 test2 = new Test2
        {
            Another = "test",
            SomeOtherValue = "test2"
        };

        Test vm = new Test
        {
            FirstName = "Bob",
            LastName = "N/A",
             Test2 = test2,

        };
        for (int i = 0; i < 10; i++)
        {
            Test3 test3 = new Test3
            {
                Test3Value = i.ToString()
            };

             vm.Test3.Add(test3);
        }

        return View(vm);
    }
Author: Arsen Mkrtchyan, 2012-06-15

3 answers

Myślę, że podsumowałem wszystkie pytania, jeśli coś przeoczyłem proszę dać mi znać ( Jeśli mógłbyś podsumować wszystkie pytania w jednym miejscu byłoby miło =))

Uwaga. Kompatybilność z dodatkiem Wtyczki ko.editable

Pobierz Pełny kod

Jak używać helperów html z knockout.js

To proste:

@Html.TextBoxFor(model => model.CourseId, new { data_bind = "value: CourseId" })

Gdzie:

  • value: CourseId oznacza, że wiążesz właściwość value z kontrolki input z właściwością CourseId z twojego modelu i Twojego modelu skryptu

Wynik jest następujący:

<input data-bind="value: CourseId" data-val="true" data-val-number="The field CourseId must be a number." data-val-required="The CourseId field is required." id="CourseId" name="CourseId" type="text" value="12" />

Dlaczego dokument był gotowy, aby działał (zobacz pierwszą edycję, aby uzyskać więcej informacji)

Nie rozumiem jeszcze, dlaczego musisz użyć zdarzenia ready do serializacji modelu, ale wydaje się, że jest to po prostu wymagane (nie martw się o to) {[24]]}

Jak zrobić coś takiego, Jeśli używam mapowania knockout z moim widokiem modelki? Ponieważ nie mam funkcji ze względu na mapowanie.

Jeśli dobrze rozumiem, musisz dodać nową metodę do modelu KO, cóż, to łatwe łączenie modeli

Aby uzyskać więcej informacji, w sekcji-mapowanie z różnych źródeł -

function viewModel() {
    this.addStudent = function () {
        alert("de");
    };
};

$(function () {
    var jsonModel = '@Html.Raw(JsonConvert.SerializeObject(this.Model))';
    var mvcModel = ko.mapping.fromJSON(jsonModel);

    var myViewModel = new viewModel();
    var g = ko.mapping.fromJS(myViewModel, mvcModel);

    ko.applyBindings(g);
});

O ostrzeżeniu, które otrzymałeś

Ostrzeżenie 1 kompilacja warunkowa jest wyłączona - > @ Html.Raw

Musisz użyć cudzysłowów

Zgodność z ko.edytowalna wtyczka

Myślałem, że to będzie bardziej skomplikowane, ale okazuje się, że integracja jest naprawdę łatwa, aby twój model był edytowalny wystarczy dodać następującą linię: (pamiętaj, że w tym przypadku używam modelu mieszanego, z serwera i dodawania rozszerzenia w kliencie i edytowalny po prostu działa... jest super):

    ko.editable(g);
    ko.applyBindings(g);

Stąd wystarczy grać {[23] } ze swoimi wiązaniami używając rozszerzeń dodanych przez wtyczkę, na przykład mam przycisk do zacznij edytować moje pola w ten sposób i w tym przycisku rozpoczynam proces edycji:

    this.editMode = function () {
        this.isInEditMode(!this.isInEditMode());
        this.beginEdit();
    };

Następnie mam przyciski commit i cancel o następującym kodzie:

    this.executeCommit = function () {
        this.commit();
        this.isInEditMode(false);
    };
    this.executeRollback = function () {
        if (this.hasChanges()) {
            if (confirm("Are you sure you want to discard the changes?")) {
                this.rollback();
                this.isInEditMode(false);
            }
        }
        else {
            this.rollback();
            this.isInEditMode(false);
        }
    };

I wreszcie, mam jedno pole, aby wskazać, czy pola są w trybie edycji, czy nie, jest to po prostu powiązanie właściwości enable.

this.isInEditMode = ko.observable(false);

O twoim pytaniu o tablicę

Mogę mieć jakieś pętle foreach lub coś, aby uzyskać dane z kolekcji modeli widoku uczniów.

Następnie, kiedy składałem formularz, używałem jQuery i serialize array i wysyłałem go do metody akcji kontrolera, która wiązałaby go z powrotem do viewmodel.

Możesz zrobić to samo z KO, w poniższym przykładzie stworzę następujące wyjście:

Tutaj wpisz opis obrazka

Zasadniczo tutaj masz dwie listy, utworzone za pomocą Helpers i binded za pomocą KO, mają one Zdarzenie dblClick binded, które po wywołaniu Usuń wybrany element z bieżącej listy i dodaj go do w przeciwieństwie do innych list, zawartość każdej listy jest wysyłana jako dane JSON i ponownie dołączana do modelu serwera

Nuggets:

Zewnętrzne Skrypty .

Kod kontrolera

    [HttpGet]
    public ActionResult Index()
    {
        var m = new CourseVM { CourseId = 12, CourseName = ".Net" };

        m.StudentViewModels.Add(new StudentVm { ID = 545, Name = "Name from server", Lastname = "last name from server" });

        return View(m);
    }

    [HttpPost]
    public ActionResult Index(CourseVM model)
    {
        if (!string.IsNullOrWhiteSpace(model.StudentsSerialized))
        {
            model.StudentViewModels = JsonConvert.DeserializeObject<List<StudentVm>>(model.StudentsSerialized);
            model.StudentsSerialized = string.Empty;
        }

        if (!string.IsNullOrWhiteSpace(model.SelectedStudentsSerialized))
        {
            model.SelectedStudents = JsonConvert.DeserializeObject<List<StudentVm>>(model.SelectedStudentsSerialized);
            model.SelectedStudentsSerialized = string.Empty;
        }

        return View(model);
    }

Model

public class CourseVM
{
    public CourseVM()
    {
        this.StudentViewModels = new List<StudentVm>();
        this.SelectedStudents = new List<StudentVm>();
    }

    public int CourseId { get; set; }

    [Required(ErrorMessage = "Course name is required")]
    [StringLength(100, ErrorMessage = "Course name cannot be this long.")]
    public string CourseName { get; set; }

    public List<StudentVm> StudentViewModels { get; set; }
    public List<StudentVm> SelectedStudents { get; set; }

    public string StudentsSerialized { get; set; }
    public string SelectedStudentsSerialized { get; set; }
}

public class StudentVm
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Lastname { get; set; }
}

Strona CSHTML

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>CourseVM</legend>

        <div>
            <div class="editor-label">
                @Html.LabelFor(model => model.CourseId)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.CourseId, new { data_bind = "enable: isInEditMode, value: CourseId" })
                @Html.ValidationMessageFor(model => model.CourseId)
            </div>

            <div class="editor-label">
                @Html.LabelFor(model => model.CourseName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.CourseName, new { data_bind = "enable: isInEditMode, value: CourseName" })
                @Html.ValidationMessageFor(model => model.CourseName)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.StudentViewModels);
            </div>
            <div class="editor-field">

                @Html.ListBoxFor(
                    model => model.StudentViewModels,
                    new SelectList(this.Model.StudentViewModels, "ID", "Name"),
                    new
                    {
                        style = "width: 37%;",
                        data_bind = "enable: isInEditMode, options: StudentViewModels, optionsText: 'Name', value: leftStudentSelected, event: { dblclick: moveFromLeftToRight }"
                    }
                )
                @Html.ListBoxFor(
                    model => model.SelectedStudents,
                    new SelectList(this.Model.SelectedStudents, "ID", "Name"),
                    new
                    {
                        style = "width: 37%;",
                        data_bind = "enable: isInEditMode, options: SelectedStudents, optionsText: 'Name', value: rightStudentSelected, event: { dblclick: moveFromRightToLeft }"
                    }
                )
            </div>

            @Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
            @Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
            @Html.HiddenFor(model => model.StudentsSerialized, new { data_bind = "value: StudentsSerialized" })
            @Html.HiddenFor(model => model.SelectedStudentsSerialized, new { data_bind = "value: SelectedStudentsSerialized" })
        </div>

        <p>
            <input type="submit" value="Save" data-bind="enable: !isInEditMode()" /> 
            <button data-bind="enable: !isInEditMode(), click: editMode">Edit mode</button><br />
            <div>
                <button data-bind="enable: isInEditMode, click: addStudent">Add Student</button>
                <button data-bind="enable: hasChanges, click: executeCommit">Commit</button>
                <button data-bind="enable: isInEditMode, click: executeRollback">Cancel</button>
            </div>
        </p>
    </fieldset>
}

Skrypty

<script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ko.editables.js")" type="text/javascript"></script>

<script type="text/javascript">
    var g = null;
    function ViewModel() {
        this.addStudent = function () {
            this.StudentViewModels.push(new Student(25, "my name" + new Date(), "my last name"));
            this.serializeLists();
        };
        this.serializeLists = function () {
            this.StudentsSerialized(ko.toJSON(this.StudentViewModels));
            this.SelectedStudentsSerialized(ko.toJSON(this.SelectedStudents));
        };
        this.leftStudentSelected = ko.observable();
        this.rightStudentSelected = ko.observable();
        this.moveFromLeftToRight = function () {
            this.SelectedStudents.push(this.leftStudentSelected());
            this.StudentViewModels.remove(this.leftStudentSelected());
            this.serializeLists();
        };
        this.moveFromRightToLeft = function () {
            this.StudentViewModels.push(this.rightStudentSelected());
            this.SelectedStudents.remove(this.rightStudentSelected());
            this.serializeLists();
        };
        this.isInEditMode = ko.observable(false);
        this.executeCommit = function () {
            this.commit();
            this.isInEditMode(false);
        };
        this.executeRollback = function () {
            if (this.hasChanges()) {
                if (confirm("Are you sure you want to discard the changes?")) {
                    this.rollback();
                    this.isInEditMode(false);
                }
            }
            else {
                this.rollback();
                this.isInEditMode(false);
            }
        };
        this.editMode = function () {
            this.isInEditMode(!this.isInEditMode());
            this.beginEdit();
        };
    }

    function Student(id, name, lastName) {
        this.ID = id;
        this.Name = name;
        this.LastName = lastName;
    }

    $(function () {
        var jsonModel = '@Html.Raw(JsonConvert.SerializeObject(this.Model))';
        var mvcModel = ko.mapping.fromJSON(jsonModel);

        var myViewModel = new ViewModel();
        g = ko.mapping.fromJS(myViewModel, mvcModel);

        g.StudentsSerialized(ko.toJSON(g.StudentViewModels));
        g.SelectedStudentsSerialized(ko.toJSON(g.SelectedStudents));

        ko.editable(g);
        ko.applyBindings(g);
    });
</script>

Uwaga: Właśnie dodałem te wiersze:

        @Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
        @Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })

Ponieważ kiedy wysyłam formularz moje pola są wyłączone, więc wartości nie były przesyłane do serwera, dlatego dodałem kilka ukrytych pól, aby zrobić sztuczkę

 179
Author: Jupaol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-12 20:31:32

Możesz serializować swoje ASP.NET model widoku MVC do zmiennej javascript:

@model CourseVM
<script type="text/javascript">
    var model = @Html.Raw(Json.Encode(Model));
    // go ahead and use the model javascript variable to bind with ko
</script>

Istnieje wiele przykładów w dokumentacji knockout, które możesz przejrzeć.

 23
Author: Darin Dimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-15 17:19:15

Aby uzyskać dodatkowe właściwości obliczeniowe po mapowaniu serwera, musisz jeszcze bardziej ulepszyć swoje modele widoku po stronie klienta.

Na przykład:

var viewModel = ko.mapping.fromJS(model);

viewModel.capitalizedName = ko.computed(function() {...}, viewModel);

Więc za każdym razem, gdy mapujesz z raw JSON, będziesz musiał ponownie zastosować obliczone właściwości.

Dodatkowo wtyczka mapowania zapewnia możliwość stopniowej aktualizacji modelu widoku w przeciwieństwie do odtwarzania go za każdym razem, gdy idziesz tam iz powrotem (użyj dodatkowego parametru w fromJS):

// Every time data is received from the server:
ko.mapping.fromJS(data, viewModel);

I to wykonuje przyrostową aktualizację danych w modelu tylko właściwości, które są mapowane. Więcej na ten temat można przeczytać w dokumentacji mapowania

Wspomniałeś w komentarzach do odpowiedzi Darina Pakiet FluentJSON. Jestem jej autorem, ale jej przypadek użycia jest bardziej szczegółowy niż ko.mapowanie. Generalnie używałbym go tylko wtedy, gdy twoje viewmodele są jednokierunkowe(tj. serwer - > klient), a następnie dane są przesyłane z powrotem w innym formacie (lub wcale). Lub jeśli JavaScript viewmodel musi być w znacznie innym formacie niż model serwera.

 2
Author: Paul Tyng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-04 16:15:38