如何在ASP.NET MVC ViewModels中使用knockout.js?

时间:2012-06-15 17:15:41

标签: asp.net-mvc asp.net-mvc-3 knockout.js

恩惠

已经有一段时间了,我还有几个悬而未决的问题。我希望通过添加赏金,这些问题可能会得到解答。

  1. 如何在knockout.js中使用html助手
  2. 为什么需要准备好文档才能使其正常工作(请参阅第一次编辑以获取更多信息)

  3. 如果我使用我的视图模型的淘汰映射,我该如何做这样的事情?由于映射我没有功能。

    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. 我想使用插件,例如我希望能够回滚observable,就好像用户取消了我希望能够返回到最后一个值的请求一样。根据我的研究,这似乎可以通过制作像editables

    这样的插件来实现

    如果我使用映射,如何使用类似的东西?我真的不想去一个方法,我在我的视图中手动映射我将每个MVC viewMode字段映射到KO模型字段,因为我想尽可能少的内联javascript,这似乎是工作的两倍,那是为什么我喜欢这种映射。

  5. 我担心为了让这项工作变得简单(通过使用映射),我将失去很多KO的力量,但另一方面,我担心手动映射将只是很多工作,并将使我视图包含太多信息,并且可能在将来变得难以维护(例如,如果我移除MVC模型中的属性,我还必须在KO视图模型中移动它)

  6. <小时/> 原帖

    我正在使用asp.net mvc 3而且我正在寻找淘汰赛因为它看起来很酷但我很难弄清楚它是如何与asp.net mvc特别是视图模型一起工作的。

    我现在就做这样的事情

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

    我会有一个Vm,它有一些基本的属性,比如CourseName,它会有一些简单的验证。如果需要,Vm模型也可以包含其他视图模型。

    我会将此Vm传递给View,我会使用html帮助程序来帮助我将其显示给用户。

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

    我可能会有一些foreach循环或其他东西来从Student View Models集合中获取数据。

    然后,当我提交表单时,我将使用jquery和serialize array并将其发送到控制器操作方法,该方法将其绑定回viewmodel。

    使用knockout.js它会有所不同,因为你现在有了它的视图模型,从我看到的所有例子中他们都没有使用html助手。

    你如何将这两个MVC功能与knockout.js一起使用?

    我发现 this video并简要地(视频@ 18:48的最后几分钟)通过基本上具有内联脚本的方式来使用视图模型,该脚本具有被分配值的knockout.js视图模型在ViewModel中。

    这是唯一的方法吗?在我的例子里有一个viewmodels集合怎么样?我是否必须使用foreach循环或某些东西来提取所有值并将其分配到淘汰赛中?

    至于html帮助者,视频没有提及他们。

    这两个区域让我感到困惑,因为没有多少人似乎在谈论它,这让我对初始值和所有内容如何进入视图感到困惑,而实例只是一些硬编码值示例。

    <小时/> 的修改

    我正在尝试Darin Dimitrov提出的建议,这似乎有效(我不得不对他的代码进行一些更改)。不知道为什么我必须准备好文件,但不管怎样,没有它就没有准备好。

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

    我必须把它包装在一个jquery文件中,准备好让它工作。

    我也收到了这个警告。不知道它到底是什么。

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

    所以我有一个起点,我想至少会在我多做一些游戏以及它如何运作时更新。

    我正在尝试浏览交互式教程,但改为使用ViewModel。

    尚不确定如何处理这些部件

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

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

    <小时/> 修改2

    我能够找出第一个问题。没有关于第二个问题的线索。但是。有人有任何想法吗?

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

    控制器

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

3 个答案:

答案 0 :(得分:179)

我想我已经总结了你的所有问题,如果我错过了什么请告诉我(如果你能在一个地方总结你的所有问题那就很好 =))

注意。与添加的ko.editable插件的兼容性

Download完整代码

如何在knockout.js中使用html助手

这很简单:

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

其中:

  • value: CourseId表示您将value控件的input属性与模型和脚本模型中的CourseId属性绑定

结果是:

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

为什么文档准备好需要使其工作(请参阅第一次编辑以获取更多信息)

我还不明白为什么你需要使用ready事件来序列化模型,但它似乎只是必需(不过不用担心)

如果我使用我的视图模型的淘汰映射,我该如何做这样的事情?由于映射我没有功能。

如果我理解正确,你需要在KO模型中附加一个新方法,那就是很容易合并模型

For more info, in the section -Mapping from different 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);
});

关于您收到的警告

  

警告1关闭条件编辑 - &gt; @ Html.Raw

您需要使用引号

与ko.editable插件的兼容性

我认为它会更复杂,但事实证明,集成非常简单,为了使您的模型可编辑,只需添加以下行:(请记住,在这种情况下,我使用的是混合模型,从服务器和在客户端添加扩展程序,可编辑只是工作...这很棒):

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

从这里你只需要使用插件添加的扩展程序播放,例如,我有一个按钮开始编辑我的字段,就像在这个按钮中我开始编辑过程:

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

然后我使用以下代码提交和取消按钮:

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

最后,我有一个字段来指示字段是否处于编辑模式,这只是绑定enable属性。

this.isInEditMode = ko.observable(false);

关于您的数组问题

  

我可能会有一些foreach循环或其他东西来从Student View Models集合中获取数据。

     

然后,当我提交表单时,我将使用jquery和serialize数组并将其发送到控制器操作方法,该方法将其绑定回viewmodel。

您可以对KO执行相同操作,在以下示例中,我将创建以下输出:

enter image description here

基本上,这里有两个列表,使用Helpers创建并与KO绑定,它们有一个dblClick事件绑定,当被触发时,从当前列表中删除所选项目并将其添加到其他列表,当您发布到Controller时,每个列表的内容将作为JSON数据发送并重新附加到服务器模型

掘金:

外部scripts

控制器代码

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

模型

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

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

脚本

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

注意:我刚添加了这些行:

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

因为当我提交表单时,我的字段被禁用,所以这些值没有传输到服务器,这就是为什么我添加了几个隐藏字段来实现这个技巧

答案 1 :(得分:23)

您可以将ASP.NET MVC视图模型序列化为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>

knockout documentation中有很多可以通过的例子。

答案 2 :(得分:2)

要在服务器映射后实现额外的计算属性,您需要进一步增强客户端的视图模型。

例如:

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

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

因此,每次从原始JSON映射时,您都需要重新应用计算属性。

此外,映射插件提供了逐步更新视图模型的功能,而不是每次来回重新创建视图模型(使用fromJS中的其他参数):

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

并且在仅映射的属性模型上执行增量数据更新。您可以在mapping documentation

中详细了解相关内容

您在对Darin回答FluentJSON包的评论中提到过。我是其作者,但它的用例比ko.mapping更具体。如果您的视图模型是单向的(即服务器 - >客户端),我通常只会使用它,然后以某种不同的格式(或根本不会)回发数据。或者,如果您的javascript视图模型需要与服务器模型的格式完全不同。