Dynamically generate <td>s using knockout js for-each bidning

4,229 views
Skip to first unread message

chat...@gmail.com

unread,
Feb 15, 2013, 12:22:41 AM2/15/13
to knock...@googlegroups.com
Hello All,

I'm quite a newbie to knockoutjs and have a complex table to data-bind. A sample of the table is attached here.



I have a server side list as below, that returns data for the first row of this grid.

  public List<double> MinWeeklyPayrollHrs { get; set; }

Is there anyway to have the first two columns in the table as static, and then dynamically generated the <td> s for the 5 weeks' data.

Some sample code would be highly appreciated.

Thanks,
Chathu

nagas...@gmail.com

unread,
Feb 15, 2013, 3:50:29 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com
If i understand what you mean, you may simply mix static html tags and others with data-bind to dynamically load (and edit the data). Something like




<table>
   
<tr>
      [... header cells]
   
</tr>
   
<tr>
     
<td rowspan="2">WeeklyPayrollhours</td>
     
<td>Minimum</td>
     
<td><input data-bind="value: minimumWeek1Hours" /></td>
     <td><input data-bind="value: minimumWeek2Hours" /></td>
     [... other minimum hours cells]
   
</tr>
     [... other rows following the same pattern as above]
</table>


If you have your observable in arrays (either observable themselves or not) you can leverage virtual elements to build even the structure at runtime - e.g.

<table>
   
<tr>
      [... header rows]
   
</tr>
   
<tr>
     
<td rowspan="2">WeeklyPayrollhours</td>
     
<td>Minimum</td>
     
<!--ko foreach: minimumHoursArray -->
     
<td><input data-bind="value: $data"/></td>
     
<!--/ko-->
   [...so onfor other rows]
</table>

I know the examples are a bit contrived, but i've not got much time at hand right now.

chat...@gmail.com

unread,
Feb 15, 2013, 4:02:11 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
Hi,

Thanks for your reply. But I don't understand how I do the following:
 <!--ko foreach: minimumHoursArray --> 
     
<td><input data-bind="value: $data"/></td>
<!--/ko-->

Can you please explain, how I do the iteration?
I earlier tried 
 <td data-bind="foreach:minimumHoursArray"><input data-bind="value: $data"/></td>

But this didnt work.

nagas...@gmail.com

unread,
Feb 15, 2013, 4:15:45 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
Sorry for the confusing example, but this part should already work exactly as it's written - comments and all - assuming you have a minimumHoursArray in your binding scope, which must be an array of observables to use the code "as is".

Basically the comment using this particular syntax (called virtual elements) will act as a "fake" element in your dom, that can be populated with repeating nodes generated through bindings.

For some more explanations about the way virtual elements work, look at http://knockoutjs.com/documentation/foreach-binding.html - see Note 4 - Using foreach without a container element.

chat...@gmail.com

unread,
Feb 15, 2013, 4:32:56 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
gee, thanks it worked :) I had earlier bound it wrong.
 I also have another problem if you don't mind please help me. Sorry for this big trouble

Say now the five week <input> tags are generated. I want to copy textbox values from week1 to the others.

I defined the input tag as below:
 <!--ko foreach:WeeklyData -->
<td><input id="Weeklymaxhr" data-bind="value: $data"/></td>
<!--/ko-->

However, at runtime, all the <input> tags generated have the same ID. Is there a way that I can do the copy using knockout object itself?

nagas...@gmail.com

unread,
Feb 15, 2013, 4:47:05 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com
If i understand your question correctly, you want to generate the input tags with dynamically generated ids? If that is the case you can do it through the attr binding and the $index meta-attribute:

instead of writing simply:

<input id="Weeklymaxhr" data-bind="value: $data" />

try changing it to:

<input data-bind="value: $data, attr: { id: 'Weeklymaxhr' + $index }" />

Now the id attribute of the input tag will be set to the result of the expression you pass in the binding - the $index meta is a special context attribute (i think it's only available inside a foreach binding) that contains the 0-based index of the element being rendered.

For more info on the $index see note 2 in http://knockoutjs.com/documentation/foreach-binding.html and about the attr binding see http://knockoutjs.com/documentation/attr-binding.html

I hope that helps - and if it doesn't, please post a snippet of the target html you want to achieve.

Cris

chat...@gmail.com

unread,
Feb 15, 2013, 8:23:43 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com
Hi,

Unfortunately, this didn't work :(
 <tr  class="formFields">
<td>Maximum</td>
 <!--ko foreach:MaxWeeklyPayrollHrs -->
 <td style="width:10%"><input class="txtbox" style="width:50px;" align="middle"  data-bind="value: $data, attr:{ 'id': 'Weeklymaxhr' + $index }"/></td>
<!--/ko-->
</tr>    

When I inspect using firebug, this is what I see:
<input align="middle" data-bind="value: $data, attr:{ 'id': 'Weeklyminhr' + $index }" style="width:50px;" class="txtbox" id="Weeklyminhrfunction d(){if(0&lt;arguments.length){if(!d.equalityComparer||!d.equalityComparer(c,arguments[0]))d.H(),c=arguments[0],d.G();return this}b.r.Wa(d);return c}">

Any idea what's wrong?

nagas...@gmail.com

unread,
Feb 15, 2013, 10:13:55 AM2/15/13
to knock...@googlegroups.com, chat...@gmail.com
Yes, i forgot that:

$index is an observable and is updated whenever the index of the item changes

so basically you just need to extract the value from it by using parenthesis, like any other observable:  

<input class="txtbox" style="width:50px;" align="middle"  data-bind="value: $data, attr:{ 'id': 'Weeklymaxhr' + $index() }"/>

chat...@gmail.com

unread,
Feb 18, 2013, 12:34:11 AM2/18/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
Thanks a lot! This worked out pretty nicely. Thanks again for helping out.

chat...@gmail.com

unread,
Feb 18, 2013, 1:12:22 AM2/18/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
Hello Nagas,

Sorry for bothering again, I have another problem to be solved.

I need to create a grid like the one shown here. So I built my data model and view as shown below(Only part of the data item is yet generated):


 public class DailyItem
    {
        public int DayOfWeek { get; set; }
        public string Required { get; set; }
        public int SetupTime { get; set; }
        public int CloseTime { get; set; }
        public double MinWorkShift { get; set; }
    }

    public class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public List<Data> Vehicles { get; set; }
        public int OwnerVehicle { get; set; }
        public List<double> WeeklyData { get; set; }
        public List<DailyItem> Week1 { get; set; }     
    }
This is the server side model. And then in knockout I have the following:
var DailyItem = function (Day, Required, Setup, Close, MinShift) {
    this.Day = ko.observable(DayOfWeek);
    this.Required = ko.observable(Required);
    this.Setup = ko.observable(SetupTime);
    this.Close = ko.observable(CloseTime);
    this.MinShift = ko.observable(MinWorkShift);
};

function ViewModel(item) {
    var parse = JSON.parse(item.d);
    this.ID = ko.observable(parse.ID);
    this.Name = ko.observable(parse.Name);
    this.Email = ko.observable(parse.Email);
    this.WeeklyData = ko.observable(parse.WeeklyData);
    this.Vehicles = ko.observableArray(parse.Vehicles);
    this.OwnerVehicle = ko.observable(parse.OwnerVehicle);
    this.DailyItemList = ko.observableArray();
    var records = (function () {
        $.map(parse.Week1, function (data) {
            return new DailyItem(data)
        });
    });
    this.DailyItemList(records);
}

And below is the UI portion related to DailyItemList:
  <tbody data-bind="foreach: DailyItemList">
                                <tr>
                                    <td data-bind="text:Day"></td>
                                    <td><input id="Text2" data-bind="value: Required"/></td>
                                    <td><input id="Text3" data-bind="value: Setup"/></td>
                                    <td><input id="Text4" data-bind="value: Close"/></td>
                                </tr>   
                            </tbody>
              

But this binding doesn't work. I don't see any data or the <input> generated on the UI. Could you please tell me what I'm doing wrong here?

Thanks in advance

chat...@gmail.com

unread,
Feb 18, 2013, 1:15:47 AM2/18/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
I forgot to add the data populating part. Here it is.

Person p = new Person();
  p.Week1 = new List<DailyItem>() { new DailyItem { DayOfWeek=1, Required="F", SetupTime=0,CloseTime=0, MinWorkShift = 0  },
                new DailyItem { DayOfWeek=2, Required="O", SetupTime=15,CloseTime=15, MinWorkShift = 8.5  }, 
                new DailyItem { DayOfWeek=3, Required="O", SetupTime=15,CloseTime=15, MinWorkShift = 6 }, 
                new DailyItem { DayOfWeek=4, Required="O", SetupTime=15,CloseTime=15, MinWorkShift = 7.5  }, 
                new DailyItem { DayOfWeek=5, Required="O", SetupTime=15,CloseTime=15, MinWorkShift = 6  }, };

nagas...@gmail.com

unread,
Feb 18, 2013, 6:53:46 AM2/18/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
I'm not certain this is the problem, but the statement:

var records = (function () {
        $
.map(parse.Week1, function (data) {
           
return new DailyItem(data)
       
});
   
});


is creating a function, not a list - is this intentional? If you simply want to compute the list assign directly the result of the $.map call - e.g.

var records = $.map(parse.Week1, function(data) { return new DailyItem(data) });

Does that help?

chat...@gmail.com

unread,
Feb 18, 2013, 11:52:13 PM2/18/13
to knock...@googlegroups.com, chat...@gmail.com, nagas...@gmail.com
Okay, that function was not intentional. I was referring to some code I saw. However, I did what you had mentioned but it didn't work out :(

Here's my code:
 this.DailyItemList = ko.observableArray();
 var records = $.map(parse.Week1, function (data) { return new DailyItem(data) });
    debugger;
 this.DailyItemList(records);


when I add this, the entire databinding doesn't happen. Meaning even the other data is not bound. Seems like something is wrong with the var records = .... line, because when I comment it out, the rest of the data binding works fine. 

also I'm wondering why can't the following work?

this.DailyItemList = ko.observableArray(parse.Week1);

Please help. I'm really stuck in here.

chat...@gmail.com

unread,
Feb 19, 2013, 12:25:24 AM2/19/13
to knock...@googlegroups.com, chat...@gmail.com
Okay, I figured out the issue.

My DailyItem object expects 5 parameters but inside the model I was sending a whole data object - just one parameter. Silly me!!

chat...@gmail.com

unread,
Feb 22, 2013, 5:49:06 AM2/22/13
to knock...@googlegroups.com, chat...@gmail.com
Hello Nagas,

Hope you are doing well, and sorry to trouble you again. I had an issue with the $data binding. It binds fine to the UI, but when I update the UI fields and send them back to server, the values are not updated, and show the same old values that were earlier bound? Any idea how to recover that? Following is the code:
 
C#:
Person p = new Person();
p.WeeklyData = new List<double> ();
p.WeeklyData.Add(30);
p.WeeklyData.Add(40);
p.WeeklyData.Add(50);
p.WeeklyData.Add(40);
p.WeeklyData.Add(30);

KnockoutJS:
function ViewModel(item) {
    var parse = JSON.parse(item.d);
    this.ID = ko.observable(parse.ID);
    this.Name = ko.observable(parse.Name);
    this.Email = ko.observable(parse.Email);
    this.WeeklyData = ko.observableArray(parse.WeeklyData);
    this.Vehicles = ko.observableArray(parse.Vehicles);
    this.OwnerVehicle = ko.observable(parse.OwnerVehicle);
}

UI:
 <tr>
    <td colspan="2">Weekly max hours</td>
    <!--ko foreach:WeeklyData -->
    <td><input id="Weeklymaxhr" data-bind="value: $data"/></td>
    <!--/ko-->
 </tr>

Other fields properly update to server when changed, but this one doesnt. please help!

Thanks,
Chathu
Reply all
Reply to author
Forward
0 new messages