简体   繁体   中英

Knockout.js: How to update the DOM using recursive templates

My goal is to be able to build a HTML form using a JSOM array as my data source and using Knockout's recursive template feature.

Details:

  1. I'm working with only one nested JavaScript array.
  2. My data structure is the following:
    • A form contains one item
    • An item contains contains a field or a group.
    • A group contains items
  3. Basically the page consists of one DIV which through template recursion generates other DIVs: one contains the groups and the other contains the items. Visually you have two blocks of data on the page.
  4. The LHS panel displays all the groups configured as heading level 1.

Using recursive template I've got a modest demo version working as you can see here .

My issue: I would like to know how to populate the template on RHS as you click a group on the LHS. Say for example, if I click on Group 1 I expect to see all items for Group 1 including the items for sub-groups (Group 1.1). However, if I click group 1.1 I only want to see the group items for group 1.1. At the moment I cannot even get the items form sub-group 'Group 1.1' to display. The showGroupItems function doesn't return true.

My code is as follows:

HTML

<script id="formDef" type="text/html">
    <!--<p>FormDef</p>-->
    <div style="float: left; width: 300px;">
        <!--<p>formDef</p>-->
        <!-- ko template: { name: renderGroup, foreach: formItems } -->
        <!-- /ko -->
    </div>
    <div style="float: left;">
        <!-- ko template: { name: 'formElementNodeTemplate', foreach: formItems } -->
        <!-- /ko -->
    </div>
</script>


<script id="formElementNodeTemplate" type="text/html">
    <!-- ko if: showGroupItems -->
        <ul data-bind="">
            <li>
                <span data-bind="text: text"></span>
                <br />
                <!-- ko template: { name: renderTemplate, foreach: formItems } -->
                <!-- /ko -->
            </li>
        </ul>
    <!-- /ko -->
</script>

<script id="group" type="text/html">
    <!--<p>Group</p>-->
    <span data-bind="text: text, click: $root.selectSection"></span>
    <br />
    <!-- ko template: { name: renderGroup, foreach: formItems } -->
    <!-- /ko -->
</script>

<script id="empty" type="text/html">
</script>

<script id="field" type="text/html">
    <!--<p>Paragraph</p>-->
    <ul>
        <li><span data-bind="text: text"></span></li>
    </ul>
</script>

<div data-bind="template: { name: 'formDef', data: $data }"></div>

Script

function SortByOrdinal(a, b) {
    return ((a.ordinal < b.ordinal) ? -1 : ((a.ordinal > b.ordinal) ? 1 : 0));
}

var FormGroup = function (formItem) {
    var self = this;

    self.def = formItem;
    self.isGroup = true;
    self.text = formItem.text; // title
    self.ordinal = formItem.ordinal;
    self.showGroupItems = ko.observable(false);

    var formItems = [];
    $(formItem.group.formItems).each(function (indx, fi) {
        if (fi.group != null)
            formItems[formItems.length] = new FormGroup(fi);
        else
            formItems[formItems.length] = new FormField(fi);
    });

    formItems.sort(SortByOrdinal);

    self.formItems = ko.observableArray(formItems);

    self.renderTemplate = function (item) {
        if (item.isGroup)
            return 'formElementNodeTemplate';
        else
            return 'field';
    };

    self.renderGroup = function (item) {
        if (item.isGroup && item.def.group.headingLevel == 1)
            return "group";
        else
            return "empty";
    };

      return self;
}

var FormField = function (formItem) {
    var self = this;

    self.def = formItem;
    self.isGroup = formItem.group != null;
    self.text = formItem.text;
    self.ordinal = formItem.ordinal;

    return self;
}

var FormDef = function (formDef) {
    var self = this;
    self.text = formDef.text;
    self.formDef = formDef;
    self.showGroupItems = true;

    var formItems = [];
    $(formDef.formItems).each(function (indx, di) {
        if (di.group != null)
            formItems[formItems.length] = new FormGroup(di);
        else
            formItems[formItems.length] = new FormField(di);
    });

    self.formItems = ko.observableArray(formItems);

    self.renderTemplate = function () {
        return 'formElementNodeTemplate'
    };

    self.renderGroup = function (item) {
        if (item.isGroup && item.def.group.headingLevel == 1)
            return "group";
        else
            return "empty";
    };

    self.selectedSection = ko.observable();

    self.selectSection = function (item) {

        if (self.selectedSection()) self.selectedSection().showGroupItems(false);

        item.showGroupItems(true);

        self.selectedSection(item);

    };


    return self;
}

var def = {
    "formItems": [
       {
           "field": null,
           "group": {
               "formItems": [
                  {
                      "field": {
                          "paraId": "{value:'1'}"
                      },
                      "group": null,
                      "text": "This is field 1.1",
                      "ordinal": 1
                  },
                  {
                      "field": {
                          "paraId": "{value:'2'}"
                      },
                      "group": null,
                      "text": "This is field 1.2",
                      "ordinal": 2

                  },

                  {
                      "field": null,
                      "group": {
                          "formItems": [
                                  {
                                      "field": {
                                          "paraId": "{value:'3'}"
                                      },
                                      "group": null,
                                      "text": "This is field 1.1.1",
                                      "ordinal": 1
                                  }
                          ],
                          "headingLevel": 1
                      },
                      "text": "Group 1.1",
                      "ordinal": 3
                  }
               ],
               "headingLevel": 1
           },

           "text": "Group 1",
           "ordinal": 2
       }
    ],
    "id": 2,
    "text": "Project 1"
};

var viewModel = new FormDef(def);

ko.applyBindings(viewModel);

I was able to solve the problem by creating other templates.

Here's the code:

HTML

<script id="formDef" type="text/html">
    <!--<p>FormDef</p>-->
    <div style="float: left; width: 300px;">
        <!--<p>formDef</p>-->
        <!-- ko template: { name: renderGroup, foreach: formItems } -->
        <!-- /ko -->
    </div>
    <div style="float: left;">
        <!-- ko template: { name: 'formElementNodeTemplate', foreach: formItems } -->
        <!-- /ko -->
    </div>
</script>


<script id="formElementNodeTemplate" type="text/html">

    <!-- ko if: showGroupItems -->
    <ul id="expList">

        <li>
            <span data-bind="text: text"></span>  <span data-bind="text: showGroupItems"></span>
            <br />
            <ul>
                <!-- ko template: { name: renderTemplate, foreach: formItems } -->
                <!-- /ko -->
            </ul>
        </li>
    </ul>
    <!-- /ko -->
    <!-- ko ifnot: showGroupItems -->
    <!-- ko template: { name: 'drillDownToSelectedGroup', foreach: formItems } -->
    <!-- /ko -->
    <!-- /ko -->

</script>

<script id="drillDownToSelectedGroup" type="text/html">
    <!-- ko if: isGroup -->
    <!-- ko if: def.group.headingLevel == 1 -->
    <!-- ko template: { name: renderTemplate } -->
    <!-- /ko -->
    <!-- /ko -->
    <!-- /ko -->
</script>

<script id="group" type="text/html">
    <!--<p>Group</p>-->
    <span data-bind="text: text, click: $root.selectSection"></span>
    <br />
    <!-- ko template: { name: renderGroup, foreach: formItems } -->
    <!-- /ko -->
</script>

<script id="empty" type="text/html">
</script>

<script id="field" type="text/html">
    <!--<p>Paragraph</p>-->
    <ul>
        <li><span data-bind="text: text"></span></li>
    </ul>
</script>

<div data-bind="template: { name: 'formDef', data: $data }"></div>

Script

function SortByOrdinal(a, b) {
    return ((a.ordinal < b.ordinal) ? -1 : ((a.ordinal > b.ordinal) ? 1 : 0));
}

var FormGroup = function (formItem) {
    var self = this;

    self.def = formItem;
    self.isGroup = true;
    self.text = formItem.text; // title
    self.ordinal = formItem.ordinal;
    self.showGroupItems = ko.observable(false);

    var formItems = [];
    $(formItem.group.formItems).each(function (indx, fi) {
        if (fi.group != null)
            formItems[formItems.length] = new FormGroup(fi);
        else
            formItems[formItems.length] = new FormField(fi);
    });

    formItems.sort(SortByOrdinal);

    self.formItems = ko.observableArray(formItems);

    self.renderTemplate = function (item) {
        if (item.isGroup)
            return 'formElementNodeTemplate';
        else
            return 'field';
    };

    self.renderGroup = function (item) {
        if (item) {
            if (item.isGroup && item.def.group.headingLevel == 1)
                return "group";
            else
                return "empty";
        }
    };

    // Cascade down changes
    self.showGroupItems.subscribe(function (newValue) {
        $(self.formItems()).each(function (indx, fi) {
            if (fi.isGroup)
                fi.showGroupItems(newValue);
        })
    });

    return self;
}

var FormField = function (formItem) {
    var self = this;

    self.def = formItem;
    self.isGroup = formItem.group != null;
    self.text = formItem.text;
    self.ordinal = formItem.ordinal;

    return self;
}

var FormDef = function (formDef) {
    var self = this;
    self.text = formDef.text;
    self.formDef = formDef;
    self.showGroupItems = true;

    var formItems = [];

    $(formDef.formItems).each(function (indx, di) {
        if (di.group != null)
            formItems[formItems.length] = new FormGroup(di);
        else
            formItems[formItems.length] = new FormField(di);
    });

    self.formItems = ko.observableArray(formItems);

    self.renderTemplate = function () {
        return 'formElementNodeTemplate'
    };

    self.renderGroup = function (item) {
        if (item.isGroup && item.def.group.headingLevel == 1)
            return "group";
        else
            return "empty";
    };

    self.selectedSection = ko.observable();


    self.selectSection = function (item) {

        if (self.selectedSection()) self.selectedSection().showGroupItems(false);

        item.showGroupItems(true);

        self.selectedSection(item);

    };


    return self;
}

var def = {
    "formItems": [
       {
           "field": null,
           "group": {
               "formItems": [
                  {
                      "field": {
                          "paraId": "{value:'1'}"
                      },
                      "group": null,
                      "text": "This is field 1.1",
                      "ordinal": 1
                  },
                  {
                      "field": {
                          "paraId": "{value:'2'}"
                      },
                      "group": null,
                      "text": "This is field 1.2",
                      "ordinal": 2

                  },

                  {
                      "field": null,
                      "group": {
                          "formItems": [
                                  {
                                      "field": {
                                          "paraId": "{value:'3'}"
                                      },
                                      "group": null,
                                      "text": "This is field 1.1.1",
                                      "ordinal": 1
                                  }
                          ],
                          "headingLevel": 1
                      },
                      "text": "Group 1.1",
                      "ordinal": 3
                  }
               ],
               "headingLevel": 1
           },

           "text": "Group 1",
           "ordinal": 2
       },
              {
                  "field": null,
                  "group": {
                      "formItems": [
                         {
                             "field": {
                                 "paraId": "{value:'2'}"
                             },
                             "group": null,
                             "text": "This is field 2.1",
                             "ordinal": 1
                         },
                         {
                             "field": {
                                 "paraId": "{value:'3'}"
                             },
                             "group": null,
                             "text": "This is field 2.2",
                             "ordinal": 2

                         },

                         {
                             "field": null,
                             "group": {
                                 "formItems": [
                                         {
                                             "field": {
                                                 "paraId": "{value:'4'}"
                                             },
                                             "group": null,
                                             "text": "This is field 2.2.1",
                                             "ordinal": 1
                                         }
                                 ],
                                 "headingLevel": 1
                             },
                             "text": "Group 2.1",
                             "ordinal": 3
                         }
                      ],
                      "headingLevel": 1
                  },

                  "text": "Group 2",
                  "ordinal": 3
              }
    ],
    "id": 2,
    "text": "Project 1"
};

var viewModel = new FormDef(def);

ko.applyBindings(viewModel);

See final work in this fiddle .

If you find this helpful please don't forget to up-vote.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM