Knockout JS模式用于同一页面中的多个视图模型,维护DOM结构

Knockout JS pattern for multiple viewmodels in same page maintaining DOM structure

本文关键字:模型 视图 维护 结构 DOM 用于 模式 JS 一页 Knockout      更新时间:2023-09-26

我最近开始使用knockout js。我试图在一个项目中以一种非传统的方式使用它。我需要维护服务器端代码发出的DOM id和结构。但是,我想利用knockout来促进客户端操作,方法是利用模式将页面上的字段引用为javascript对象。

我有一个概念验证片段工作,但我想知道当我开始添加更多的部分时,它将如何扩展。我想得到一些反馈,看看更有经验的淘汰赛用户有什么要说的。

的例子在这些jsfiddle, jsfiddle:例如:

HTML

<!-- 
  * We would then have a whole other section like fieldset0 after fieldset0 for a different people category
  * We need to use the ids returned by the page, just add knockout on top without modifying DOM structure (except for sorting people based on sort order field, after removing item2 and adding again, it should go after item3 - if all 3 were on the page)
-->
<fieldset name="fieldset0" id="el_fieldset0">
    <fieldset name="fieldset1" id="el_fieldset1">
         <h1><span id="person1"></span></h1>
        <input id="hasdata1" type="hidden" value="Yes" />
        <label for="name1">name</label>
        <input id="name1" value="andrew" />
        <label for="age1">age</label>
        <input id="age1" value="30" />
        <label for="grade1">grade</label>
        <select id="grade1">
            <option selected>A</option>
            <option>B</option>
            <option>C</option>
            <option>F</option>
        </select> <a id="remove1" class="remove" href="javascript:void(0)">remove</a>
    </fieldset>
    <fieldset name="fieldset2" id="el_fieldset2">
         <h1><span id="person2"></span></h1>
        <input id="hasdata2" type="hidden" value="Yes" />
        <label for="name2">name</label>
        <input id="name2" value="brandon" />
        <label for="age2">age</label>
        <input id="age2" value="40" />
        <label for="grade2">grade</label>
        <select id="grade2">
            <option>A</option>
            <option selected>B</option>
            <option>C</option>
        </select> <a id="remove2" class="remove" href="javascript:void(0)">remove</a>
    </fieldset>
    <fieldset name="fieldset3" id="el_fieldset3">
         <h1><span id="person3"></span></h1>
        <input id="hasdata3" type="hidden" value="" />
        <label for="name3">name</label>
        <input id="name3" value="calvin" />
        <label for="age3">age</label>
        <input id="age3" value="50" />
        <label for="grade3">grade</label>
        <select id="grade3">
            <option>A</option>
            <option>B</option>
            <option selected>C</option>
        </select> <a id="remove3" class="remove" href="javascript:void(0)">remove</a>
    </fieldset> <a id="addItem" class="add" href="javascript:void(0)">add</a>
</fieldset>
Javascript

var myNamespace = {};
$(document).ready(function () {
    //assign fieldset variables
    myNamespace.$fieldset0 = $("#el_fieldset0"); // parent for all items
    //individual items
    myNamespace.$fieldset1 = $("#el_fieldset1");
    myNamespace.$fieldset2 = $("#el_fieldset2");
    myNamespace.$fieldset3 = $("#el_fieldset3");
    //set the number of objects
    myNamespace.personObjectCount = 3;
    //set utilityArrays
    myNamespace.personObjects = [];
    myNamespace.availablePersonObject = [];
    //assign data-bind attributes to html
    for (var i = 0; i < myNamespace.personObjectCount; i++) {
        var objectIndex = i + 1;
        var currentFielsetStr = "$fieldset" + objectIndex;
        //myNamespace[currentFielsetStr] = $("#el_fieldset"+ objectIndex);
        myNamespace[currentFielsetStr].find("input[id^='name']").attr("data-bind", "value: personName");
        myNamespace[currentFielsetStr].find("input[id^='age']").attr("data-bind", "value: personAge");
        myNamespace[currentFielsetStr].find("select[id^='grade']").attr("data-bind", "value: personGrade");
        myNamespace[currentFielsetStr].find("span[id^='person']").attr("data-bind", "text: personTitle");
        myNamespace[currentFielsetStr].find("input[id^='hasdata']").attr("data-bind", "value: hasData");
        //hasdata identifies visibility and should wipe fields when set to false
        myNamespace[currentFielsetStr].attr("data-bind", "visible: hasData() === 'Yes'");
        //remove link associates with removeItem function
        myNamespace[currentFielsetStr].find("a[id^='remove']").attr("data-bind", "click: removeItem");
        //create each viewmodel object
        myNamespace.personObjects.push({
            "name": myNamespace[currentFielsetStr].find("input[id^='name']").val(),
                "age": myNamespace[currentFielsetStr].find("input[id^='age']").val(),
                "grade": myNamespace[currentFielsetStr].find("select[id^='grade']").val(),
                "hasData": myNamespace[currentFielsetStr].find("input[id^='hasdata']").val(),
                "parentFieldset": myNamespace[currentFielsetStr]
        });
        /*
        //attach event handlers
        myNamespace[currentFielsetStr].on("click", ".remove", function () {
            //retrieve the ko context
            var context = ko.contextFor(this);
            debugger;
            //alert(context);
        });
*/
        //populate available person array
        if (myNamespace.personObjects[i].hasData !== "Yes") {
            debugger;
            myNamespace.availablePersonObject.push(myNamespace.personObjects[i]);
        }
    }
    //add link associates with addItem function, disables if at maximum count/no positions available -- only one per section -- should try to move to binding outside of these (make a root viewmodel for the page containing individual sections?)
    //debugger;
    myNamespace.$fieldset0.find("a[id^='addItem']")
        .attr("data-bind", "click: addItem, enabled: myNamespace.availablePersonObject.length > 0")
        .click(function () {
        debugger;
        if (myNamespace.availablePersonObject.length > 0) {
            //add first item in lsit of available person object (later, based on sort order)
            myNamespace.availablePersonObject[0].parentFieldset.find("input[id^='hasdata']").val("Yes").change();
            myNamespace.availablePersonObject.splice(0, 1);
        }
        if (myNamespace.availablePersonObject.length < 1) {
            //disable add button
        }
    });
    for (var i = 0; i < myNamespace.personObjects.length; i++) {
        //perform knockout bindings
        ko.applyBindings(new myViewModel(myNamespace.personObjects[i]), myNamespace.personObjects[i].parentFieldset[0]);
    }
});
function myViewModel(personObject) {
    var self = this;
    self.personName = ko.observable(personObject.name);
    self.personAge = ko.observable(personObject.age);
    self.personGrade = ko.observable(personObject.grade);
    self.hasData = ko.observable(personObject.hasData);
    self.personTitle = ko.computed(function () {
        return self.personName() + " : " + self.personGrade();
    });
    self.removeItem = function () {
        debugger;
        var _blank = "";
        //var _blank = undefined;
        self.hasData(_blank);
        self.personName(_blank);
        self.personAge(_blank);
        self.personGrade(_blank);
        myNamespace.availablePersonObject.push(personObject);
    };
    /*
    self.addItem = function() {
        //move this to outside the individual element and add to the parent Section
        //or maybe don't have the fields at all
        debugger;        
    };
    */
}

为我需要解决的问题类型实现了解决方案。

为了绑定到不灵活的生成的HTML,我基本上把任务分成了4部分。这将在绑定时维护特定的DOM id(没有动态html生成用于这些目的)(这是为每个对象-视图模型绑定抽象完成的):

  1. 创建一个绑定到ViewModel的模型数据对象(这是在页面加载后从现有的HTML标记中读取的,客户端)。还要在此数据对象中存储每个"对象"的信息,包括通过knockout返回的ViewModel和用于绑定的fieldset。
  2. 使用jQuery和dataObject信息应用绑定——可以与步骤1结合使用
  3. 根据初始数据对象值创建ViewModel。
  4. 使用dataObject的viewModel对页面应用knockout绑定

这里列出的粗略示例http://jsfiddle.net/vcbbg81/vv3zjag4/79/

$("#pageMenu1").before("<!-- ko stopBinding: true -->");
    $("#pageMenu1").after("<!-- /ko -->");
    //$("#employmentMenu1").attr("data-bind", "checked: employmentMenuItem");
    myNamespace.vmPage = {
        "employmentMenuItem": $("#employmentMenu1").attr("data-bind", "checked: employmentMenuItem").prop("checked"),
            "selfEmploymentMenuItem": $("#selfEmploymentMenu1").attr("data-bind", "checked: selfEmploymentMenuItem").prop("checked"),
            "otherMenuItem": $("#otherMenu1").attr("data-bind", "checked: otherMenuItem").prop("checked"),
            "noneMenuItem": $("#noneMenu1").attr("data-bind", "checked: noneMenuItem").prop("checked")
    };
    //myNamespace.vmPage = pageObjects;
    myNamespace.vmPage.viewModel = new aPageViewModel(myNamespace.vmPage);
    ko.applyBindings(myNamespace.vmPage.viewModel, $("#pageMenu1")[0]);