Comment utiliser knockout.js avec ASP.NET les Modèles de vue MVC?

Bounty

ça fait longtemps et j'ai encore quelques questions en suspens. J'espère qu'en ajoutant une prime, on répondra à ces questions.

  1. comment utilisez-vous les helpers html avec knockout.js
  2. pourquoi le document était-il prêt pour qu'il fonctionne(voir la première édition pour plus d'information)

  3. Comment dois-je faire quelque chose comme ça si j'utilise le mapping knockout avec mes modèles de vue? Comme je n'ai pas de fonction en raison de la cartographie.

    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. je veux utiliser des plugins par exemple je veux pouvoir faire reculer les observables comme si un utilisateur annulait une requête je veux pouvoir revenir à la dernière valeur. D'après mes recherches, cela semble être réalisé par des personnes faisant des plugins comme editables

    Comment faire J'utilise quelque chose comme ça si j'utilise la cartographie? Je ne veux vraiment pas aller à une méthode où j'ai dans ma vue cartographie manuelle où j'ai mappé chaque champ MVC viewMode à un champ de modèle KO car je veux aussi peu javascript en ligne que possible et cela semble juste doubler le travail et c'est pourquoi j'aime cette cartographie.

  5. je crains que pour rendre ce travail facile (en utilisant la cartographie) je vais perdre beaucoup de pouvoir KO mais d'un autre côté je suis préoccupé que la cartographie manuelle sera juste beaucoup de travail et fera mes vues contiennent trop d'informations et pourraient devenir à l'avenir plus difficile à maintenir (dire si je supprime une propriété dans le modèle MVC je dois la déplacer aussi dans le KO viewmodel)


Original Post

j'utilise asp.net mvc 3 et moi regardant knockout comme il a l'air assez cool, mais je passe un moment difficile comprendre comment cela fonctionne avec asp.net mvc voir surtout les modèles.

Pour moi, maintenant, je fais quelque chose comme ça

 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; }

}

j'aurais une Vm qui a des propriétés de base comme CourseName et qui aura une validation simple en plus. Le modèle Vm peut contenir d'autres modèles de vue.

je passerais alors cette Vm à la vue si j'utilisais des helpers html pour m'aider à l'afficher à la utilisateur.

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

j'ai peut-être des boucles foreach ou quelque chose pour extraire les données de la collection des modèles de vue des étudiants.

alors quand je soumettrais le formulaire, j'utiliserais jquery et serialize array et je l'enverrais à une méthode d'action de controller qui le relierait au viewmodel.

avec K. O.js c'est tout différent car vous avez maintenant des modèles de vue pour cela et de tous les exemples que j'ai vu ils n'utilisent pas html Helper.

comment utilisez-vous ces 2 fonctionnalités de MVC avec knockout.js?

j'ai trouvé cette vidéo et il brièvement(dernières quelques minutes de la vidéo @ 18:48) va dans une façon d'utiliser les modèles de vue en ayant essentiellement un script en ligne qui a le K. O.js viewmodel qui reçoit les valeurs attribuées dans le ViewModel.

Est-ce la seule façon de le faire? Que diriez-vous dans mon exemple d'avoir une collection de viewmodel-t-il? Dois-je avoir une boucle foreach ou quelque chose pour extraire toutes les valeurs et l'affecter dans knock-out?

quant aux helpers html, la vidéo ne dit rien à leur sujet.

ce sont les 2 zones qui me confond le diable car peu de gens semblent en parler et il me laisse confus de la façon dont les valeurs initiales et tout arrive à la vue quand jamais exemple est juste un exemple de valeur codée dur.


modifier

je suis en train d'essayer ce que Darin Dimitrov a suggéré et cela semble fonctionner(j'ai dû faire quelques changements à son code cependant). Je ne sais pas pourquoi j'ai dû utiliser le document prêt, mais d'une certaine façon tout n'était pas prêt sans lui.

@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>

j'ai dû l'enrouler autour d'un document jquery prêt à fonctionner.

j'ai aussi cet avertissement. Pas sûr de ce que c'est tous sur.

Warning 1   Conditional compilation is turned off   -> @Html.Raw

donc j'ai un point de départ je suppose qu'au moins je vais mettre à jour quand j'ai fait un peu plus de jeu autour et comment cela fonctionne.

j'essaie de passer en revue les tutoriels interactifs, mais j'utilise plutôt le modèle de vue A.

Je ne sais pas encore comment aborder ces parties

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

ou

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
    };


modifier 2

j'ai pu résoudre le premier problème. Aucune idée sur le deuxième problème. Pourtant, bien qu'. Quelqu'un a des idées?

 @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>

Contrôleur

  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);
    }
128
demandé sur Arsen Mkrtchyan 2012-06-15 21:15:40

3 réponses

je pense que j'ai résumé toutes vos questions, si j'ai manqué quelque chose s'il vous plaît laissez-moi savoir ( si vous pouvez résumer toutes vos questions en un seul endroit serait agréable =)

Note. Compatibilité avec le plug-in ko.editable ajouté

télécharger le code complet

comment utilisez-vous les helpers html avec knockout.js

C'est facile:

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

où:

  • value: CourseId indique que vous liez la propriété value du contrôle input avec la propriété CourseId de votre modèle et de votre modèle de script

le résultat est:

<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" />

Pourquoi était-document prêt nécessaire pour le faire fonctionner(voir la première édition pour plus d'informations)

Je ne comprends pas encore pourquoi vous devez utiliser l'événement ready pour sérialiser le modèle, mais il semble qu'il soit tout simplement requis (ne vous inquiétez pas à ce sujet si)

Comment puis-je faire quelque chose comme ça si j'utilise le mapping knockout avec mes modèles view? Comme je n'ai pas de fonction en raison de la cartographie.

si je comprends bien, vous devez ajouter une nouvelle méthode au modèle KO, et bien c'est facile de fusionner modèles

pour plus d'informations, dans la section-cartographie à partir de différentes sources -

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);
});

à propos de l'avertissement que vous receviez

Avertissement 1 la compilation conditionnelle est désactivée - > @Html.Brut

vous devez utiliser des guillemets

compatibilité avec le ko.editable plug-in

j'ai pensé il allait être plus complexe, mais il s'avère que l'intégration est vraiment facile, afin de rendre votre modèle modifiable il suffit d'ajouter la ligne suivante: (rappelez-vous que dans ce cas, j'utilise un modèle mixte, à partir du serveur et l'ajout d'extension dans le client et l'modifiable fonctionne tout simplement... c'est génial):

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

D'ici vous avez juste besoin de jouer avec vos fixations en utilisant les extensions ajoutées par le plug-in, par exemple, j'ai un bouton pour commencez à éditer mes champs comme ceci et dans ce bouton je démarre le processus d'édition:

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

puis j'ai les boutons commit et cancel avec le code suivant:

    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);
        }
    };

Et enfin, j'ai un champ pour indiquer si les champs sont en mode édition ou pas, c'est juste pour lier la propriété activer.

this.isInEditMode = ko.observable(false);

à propos de votre tableau question

je pourrais avoir quelques les boucles foreach ou quelque chose pour obtenir les données de la collecte des Étudiants Modèles.

alors quand je soumettrais le formulaire, j'utiliserais jquery et serialize array et je l'enverrais à une méthode d'action de controller qui le relierait à nouveau au viewmodel.

vous pouvez faire la même chose avec KO, dans l'exemple suivant, je vais créer la sortie suivante:

enter image description here

fondamentalement ici, vous avez deux listes, créées en utilisant Helpers et liées avec KO, ils ont un événement dblClick lié que lors de la mise à feu, supprimer l'élément sélectionné de la liste actuelle et l'ajouter à l'autre liste, lorsque vous postez à la Controller , le contenu de chaque liste est envoyé en données JSON et ré-attaché au modèle de serveur

Pépites:

Externe scripts .

Code du contrôleur

    [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);
    }

Modèle

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; }
}

CSHTML page

@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>
}

Scripts

<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>

Note: je viens d'ajouter ces lignes:

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

parce que quand je soumets le formulaire mes champs sont désactivés, donc les valeurs n'ont pas été transmises au serveur, c'est pourquoi j'ai ajouté quelques champs cachés pour faire l'astuce

179
répondu Jupaol 2016-03-12 20:31:32

vous pourriez sérialiser votre ASP.NET MVC view model dans une variable 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>

il y a beaucoup d'exemples dans la documentation knockout que vous pourriez parcourir.

23
répondu Darin Dimitrov 2012-06-15 17:19:15

pour obtenir les propriétés supplémentaires calculées après la mise en correspondance du serveur, vous aurez besoin d'améliorer davantage vos modèles de vue du côté du client.

par exemple:

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

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

donc à chaque fois que vous cartographiez à partir de JSON brut, vous devez appliquer de nouveau les propriétés calculées.

de plus, le plugin de mapping fournit la possibilité de mettre à jour progressivement un modèle de vue plutôt que de le recréer chaque fois que vous faites des allers-retours (utilisez un paramètre supplémentaire dans fromJS ):

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

et qui exécute une mise à jour de données incrémentielles sur votre modèle de propriétés justes qui sont mappées. Vous pouvez en savoir plus à ce sujet dans la documentation de cartographie

vous avez mentionné dans les commentaires sur la réponse de Darin le paquet FluentJSON . Je suis l'auteur, mais son cas est plus spécifique que ko.cartographie. Je voudrais généralement seulement utilisez cette option si votre viewmodel sont une façon (c'est à dire. serveur -> client) et puis les données sont postées de nouveau dans un format différent (ou pas du tout). Ou si votre modèle de vue javascript doit être dans un format substantiellement différent de votre modèle de serveur.

2
répondu Paul Tyng 2012-07-04 16:15:38