KnockoutJS and multiple nested models


John Du

I'm trying to find some tutorials on how to create nested view models with more than two levels like:

  • shop
    • Order
      • PO line
    • Order
      • PO line
      • PO line
  • shop
    • Order
      • PO line

All orders for the store are listed and when I click on an order I should see an order line with edit and delete order line functionality. I've somehow got this working by following some tutorials, but it messes up and I want to start over (finally, I started using jQuery to get what I wanted, but it felt like cheating and halfway through things). Are there any tutorials for this, or any pointers on where I should start (KnockoutJS or other frameworks?) Yes, I've followed the tutorials on kickoutjs.com, but I'm stuck on the functionality of the third level .

Thanks in advance.

EDIT: Follow this http://jsfiddle.net/peterf/8FMPc/light/

JS (Simplified)

// required by sharepoint
ExecuteOrDelayUntilScriptLoaded(loadTeams, "sp.js");                    

ko.observable.fn.beginEdit = function (transaction) {
    var self = this;
    var commitSubscription, rollbackSubscription;

    if (self.slice) {
        self.editValue = ko.observableArray(self.slice());
    }
    else {
        self.editValue = ko.observable(self());
    }

    self.dispose = function () {        
        commitSubscription.dispose();
        rollbackSubscription.dispose(); 
    };

    self.commit = function () {
        self(self.editValue());
        self.dispose();
    };

    self.rollback = function () {
        self.editValue(self());        
        self.dispose();
    };

    commitSubscription = transaction.subscribe(self.commit, self, "commit");    
    rollbackSubscription = transaction.subscribe(self.rollback, self, "rollback");

    return self;
}

 function TeamModel (){
    var self = this;
    self.Team = function(title, members) {
        this.title = title;
        this.members = members;
    }
    self.editingItem = ko.observable();   
    self.editTransaction = new ko.subscribable();        
    self.isItemEditing = function(task) {
        return task == self.editingItem();
    };

    self.editTask = function (task) {
        if (self.editingItem() == null) {
            task.beginEdit(self.editTransaction);
            self.editingItem(task);
        }
    };

    self.removeTask = function (task) {
        if (self.editingItem() == null) {
            var answer = confirm('Are you sure you want to delete this task? ' + task.title());
            if (answer) {
                // SharePoint client object model to delete task
            }
        }
    };        

    self.applyTask = function (task) {
        self.editTransaction.notifySubscribers(null, "commit");        
        // SharePoint client object model to update task                    
        //  hides the edit fields
        self.editingItem(null);                                         
    };

    self.cancelEdit = function (task) {
        self.editTransaction.notifySubscribers(null, "rollback");        
        self.editingItem(null);
    };

    self.Member = function(name, id) {
        this.name = name;
        this.Tasks = ko.observableArray([]);
        this.Task = function(id, title, priority, userComment, managerComment) {
            this.id = ko.observable(id);
            this.title = ko.observable(title);
            this.priority = ko.observable(priority);
            this.userComment = ko.observable(userComment);
            this.managerComment = ko.observable(managerComment);
            this.beginEdit = function(transaction) {
                //this.title.beginEdit(transaction);
                //this.userComment.beginEdit(transaction);
            }                                                                                                                               
        }
        this.id = id;
        this.retrieveTasks = function() {
            if(this.Tasks().length === 0) {
                // First click, expand                                                    
                // SharePoint client object model to get tasks
            } else {
                // Collapse
                //this.Tasks.removeAll();                   
            }       
        }
    }
    self.Teams = ko.observableArray([]);                                 
    self.retrieveTeams = function() {  
            // SharePoint client object model to get a list of teams and their members
            self.Teams.push(new self.Team(oListItem.get_item('Title'), members));  
    }               
}

function loadTeams() {
    var VM = new TeamModel();
    VM.retrieveTeams();     
    VM.availableRankings = ["1","2","3","4","5","6","7","8","9","10"]
    ko.applyBindings(VM);           
}

HTML

<div id="Workload" data-bind="visible: Teams().length>0">
    <div data-bind="foreach: Teams" class="teams">                         
        <div >
            <h3 data-bind="text: title"></h3>
                <div data-bind="foreach: members">
                    <div class="member">
                        <div data-bind="click: retrieveTasks">
                            <span data-bind="text: name" class="name"></span>
                        </div>
                        <table class="tasks" data-bind="visible: Tasks().length>0">
                            <tr>
                                <td class="title">Title</td>
                                <td class="priority">Priority</td>
                                <td class="user-comment">User Comment</td>
                                <td class="manager-comment">Manager Comment</td>                                                                                                                                                                                                                                                                                                                          
                            </tr>
                            <tbody data-bind="foreach: Tasks">
                                <tr class="row">                                                                                                                                    
                                    <td class="rowItem">
                                        <input type="text" class="edit" data-bind="value: title, visible: $root.isItemEditing($data)"/>
                                        <label class="read" data-bind="text: title, visible: !$root.isItemEditing($data)"/>
                                    </td>
                                    <td class="rowItem">
                                        <select class="edit priority" data-bind="options: $root.availableRankings, value: priority, visible: $root.isItemEditing($data)"></select>
                                        <label class="read" data-bind="text: priority, visible: !$root.isItemEditing($data)" />                                                                                                                                   
                                    </td>
                                    <td class="rowItem">
                                        <textarea rows="3" cols="25" class="edit userComment" data-bind="value: userComment, visible: $root.isItemEditing($data)"></textarea>
                                        <label class="read" data-bind="text: userComment, visible: !$root.isItemEditing($data)"/>                                                                                                                         
                                    </td>
                                    <td class="rowItem">
                                        <textarea rows="3" cols="25" class="edit managerComment" data-bind="value: managerComment, visible: $root.isItemEditing($data)"></textarea>
                                        <label class="read" data-bind="text: managerComment, visible: !$root.isItemEditing($data)"/>                                                                                                                               
                                    </td>
                                    <td class="tools">
                                        <a class="button toolButton" href="#" data-bind="click: $root.editTask.bind($root), visible: !$root.isItemEditing($data)">
                                        Edit</a>
                                        <Sharepoint:SPSecurityTrimmedControl runat="server" Permissions="DeleteListItems">
                                        <a class="button toolButton" href="#" data-bind="click: $root.removeTask.bind($root), visible: !$root.isItemEditing($data)">
                                        Remove</a>
                                        </SharePoint:SPSecurityTrimmedControl>                                                                                                                                  
                                        <a class="button toolButton" href="#" data-bind="click: $root.applyTask.bind($root), visible: $root.isItemEditing($data)">
                                        Apply</a>
                                        <a class="button toolButton" href="#" data-bind="click: $root.cancelEdit.bind($root), visible: $root.isItemEditing($data)">
                                        Cancel</a>
                                    </td>                                                                                                                                    
                                </tr>                                         
                            </tbody>
                        </table>
                </div>
            </div>
        </div>   
    </div>
</div>
Robert Westerlund

A data-bound nested view model with multiple levels is the same as a data-bound nested view model with a single level.

In the examples below, I'll use yours Store -> Order -> OrderRow, assuming Storethere's one storeNameproperty for each Order, one for each, and one orderNumberfor each . I also render an item at each level .OrderRowrunningNumberul

don't use templates

To data-bind a single view's nested view models , the list storesin this example can be similar to the following:

<ul data-bind="foreach: stores">
    <li>
        Store Name: <span data-bind="text: storeName"></span>
    </li>
</ul>

To data-bind a single view's nested view models, Store -> Orderit can be done with steps similar to the following:

Store Name: <span data-bind="text: storeName"></span>
<ul data-bind="foreach: orders">
    <li data-bind="text: orderNumber"></li>
</ul>

To databind a nested viewmodel of a single view, Order -> OrderRowit can be done in the following way:

Order number: <span data-bind="text: orderNumber"></span>
<ul data-bind="foreach: rows">
    <li>
        A row with running number: <span data-bind="text: runningNumber"></span>
    </li>
</ul>

To do this, nest it in multiple levels, as simple as combining the above, move the third code in to replace what's in the lisecond , and use the new second code to replace lithe first content in one .

<ul data-bind="foreach: stores">
    <li>
        Store Name: <span data-bind="text: storeName"></span>
        <ul data-bind="foreach: orders">
            <li>
                Order number: <span data-bind="text: orderNumber"></span>
                <ul data-bind="foreach: rows">
                    A row with running number: <span data-bind="text: runningNumber"></span>
                </ul>
            </li>
        </ul>
    </li>
</ul>

I basically have the above code running (although buttons are added for adding new ones Store, Orderand OrderRowthe object is in) http://jsfiddle.net/8yF6c/ .

with template

To make your code more maintainable, you can use templates instead. Of course, as usual, with such a small example, the benefits may not be so obvious.

With templates, the code basically looks very similar to the first three cases in the above example. Before merging the html. First, the template for the store:

<script type="text/html" id="storeTemplate">
    Store Name: <span data-bind="text: storeName"></span>
    <ul data-bind="foreach: orders">
        <li data-bind="template: 'orderTemplate'"></li>
    </ul>
</script>

And then the template for the order:

<script type="text/html" id="orderTemplate">
    Order number: <span data-bind="text: orderNumber"></span>
    <ul data-bind="foreach: rows">
        <li data-bind="template: 'orderRowTemplate'"></li>
    </ul>
</script>

And finally the template for the PO line.

<script type="text/html" id="orderRowTemplate">
    A row with running number: <span data-bind="text: runningNumber"></span>
</script>

Note that the three parts of code above are the same as the first example single-level binding, just wrapped in an element scriptwith a type text/html(to make sure the browser doesn't try to execute it as a script). Then we just need to start using it at the root level storeTemplate.

<ul data-bind="foreach: stores">
    <li data-bind="template: 'storeTemplate'"></li>
</ul>

That's it. As before, my code above works (although the button adds new Store, Orderand OrderRowthe object is added in) http://jsfiddle.net/Ag8U3/ .

Add edit and delete functionality

Adding editing functionality to the above template (or a binding without a template) is as simple as changing the spanelement to a inputbox (if you want other bindings to be aware of the change, then of course you need to change some properties to be able to observe). If you need different "modes", edit mode and view mode, you can choose a template dynamically, examples of templates can be found in the template documentation.

To add delete functionality, just add a function that removes an item from the list when the delete button is clicked (e.g. deleteOrderyou Storecould add a function on the object and self.removeOrder = function(order){ self.orders.remove(order); };then add a button to the order, eg <button data-bind="click: $parent.removeOrder">Remove Order</button>.) Template example, url as http://jsfiddle.net/Ag8U3/1/ .

Related


KnockoutJS and multiple nested models

John Doe I'm trying to find some tutorials on how to create a nested viewmodel with more than two levels, for example: Store Order Order row Order Order row Order row Store Order Order row All the orders are listed for the stores and when I click on an order I

KnockoutJS and multiple nested models

John Du I'm trying to find some tutorials on how to create nested view models with more than two levels like: shop order PO line order PO line PO line shop order PO line All orders for the store are listed and when I click on an order I should see an order lin

KnockoutJS and multiple nested models

John Du I'm trying to find some tutorials on how to create nested view models with more than two levels like: shop Order PO line Order PO line PO line shop Order PO line All orders for the store are listed and when I click on an order I should see an order lin

Querying Nested Models with Multiple Picks

they are old I'm just wondering if the following is possible: I have models with nested associative models. I would like to be able render json:to current_user.reports.minnedand have it eager_loadswipe values from each model. what should I do? Here, I only use

Querying Nested Models with Multiple Picks

they are old I'm just wondering if the following is possible: I have models with nested associative models. I would like to be able render json:to current_user.reports.minnedand have it eager_loadswipe values from each model. what should I do? Here, I only use

Querying Nested Models with Multiple Picks

they are old I'm just wondering if the following is possible: I have models with nested associative models. I would like to be able render json:to current_user.reports.minnedand have it eager_loadswipe values from each model. what should I do? Here, I only use

django rest framework nested fields with multiple models

Momokjaaaaa This is django and django rest framework. I have 2 models: User and Phone. first question: I want to be able to update user data (email) as well as phone data (phone number) in 1 api update response. Phone numbers can be 0 or many. Well, it's actua

Web API 2 Multiple Nested Models

kwv84 I am creating a web service where I can post new orders with multiple lines. role model public class Order { public int OrderID { get; set; } public string Description { get; set; } public string Account { get; set; } public ICollection<

Django insert performance with multiple nested models

Thanh Nguyen I have the following model definition: class Workflow(models.Model): name = models.CharField(max_length=255) class Step(models.Model): workflow = models.ForeignKey(Section, on_delete=models.CASCADE, related_name='steps') title = model

Django - Handling multiple nested models at different levels

CH Andre Meloa I have some questions about Django nested models. As far as I know, to handle nested data structures, I have to deserialize each data and then create objects that will be concatenated with the ForeignKeyField. I can handle this by overriding the

django rest framework nested fields with multiple models

Momokjaaaaa This is django and django rest framework. I have 2 models: User and Phone. first question: I want to be able to update user data (email) as well as phone data (phone number) in 1 api update response. Phone numbers can be 0 or many. Well, it's actua

django rest framework nested fields with multiple models

Momokjaaaaa This is django and django rest framework. I have 2 models: User and Phone. first question: I want to be able to update user data (email) as well as phone data (phone number) in 1 api update response. Phone numbers can be 0 or many. Well, it's actua

Django insert performance with multiple nested models

Thanh Nguyen I have the following model definition: class Workflow(models.Model): name = models.CharField(max_length=255) class Step(models.Model): workflow = models.ForeignKey(Section, on_delete=models.CASCADE, related_name='steps') title = model

Web API 2 Multiple Nested Models

kwv84 I am creating a web service where I can post new orders with multiple lines. role model public class Order { public int OrderID { get; set; } public string Description { get; set; } public string Account { get; set; } public ICollection<

Nested Json for Django multiple foreign key models

Mehmet Ince I have 4 models with relationships via FK. class Journal(models.Model): name = models.CharField(max_length=255) class Volume(models.Model): journal = models.ForeignKey(Journal, related_name='volumes') number = models.IntegerField() cl

Web API 2 Multiple Nested Models

kwv84 I am creating a web service where I can post new orders with multiple lines. role model public class Order { public int OrderID { get; set; } public string Description { get; set; } public string Account { get; set; } public ICollection<

Django - Handling multiple nested models at different levels

CH Andre Meloa I have some questions about Django nested models. As far as I know, to handle nested data structures, I have to deserialize each data and then create objects that will be concatenated with the ForeignKeyField. I can handle this by overriding the

Validating multiple models into one with nested properties

Radek So I'm trying to build a form consisting of fields from two models. Unfortunately, although they are the same, the validation is only for one of them. If there is a white symbol in the first field, the console shows a red "rollback" and the view shows an

Django - Handling multiple nested models at different levels

CH Andre Meloa I have some questions about Django nested models. As far as I know, to handle nested data structures, I have to deserialize each data and then create objects that will be concatenated with the ForeignKeyField. I can handle this by overriding the

django rest framework nested fields with multiple models

Momokjaaaaa This is django and django rest framework. I have 2 models: User and Phone. first question: I want to be able to update user data (email) as well as phone data (phone number) in 1 api update response. Phone numbers can be 0 or many. Well, it's actua

Web API 2 Multiple Nested Models

kwv84 I am creating a web service where I can post new orders with multiple lines. role model public class Order { public int OrderID { get; set; } public string Description { get; set; } public string Account { get; set; } public ICollection<

Web API 2 Multiple Nested Models

kwv84 I am creating a web service where I can post new orders with multiple lines. role model public class Order { public int OrderID { get; set; } public string Description { get; set; } public string Account { get; set; } public ICollection<

Django insert performance with multiple nested models

Thanh Nguyen I have the following model definition: class Workflow(models.Model): name = models.CharField(max_length=255) class Step(models.Model): workflow = models.ForeignKey(Section, on_delete=models.CASCADE, related_name='steps') title = model

Nested Json for Django multiple foreign key models

Mehmet Ince I have 4 models with relationships via FK. class Journal(models.Model): name = models.CharField(max_length=255) class Volume(models.Model): journal = models.ForeignKey(Journal, related_name='volumes') number = models.IntegerField() cl