nested foreach $data and $parent

2,626 views
Skip to first unread message

jjf...@gmail.com

unread,
Apr 26, 2013, 11:55:04 AM4/26/13
to knock...@googlegroups.com
Hello, I'm trying to create a departments list with a nested employee list. My problem is all employees show up in each department: http://jsfiddle.net/jjfrick/66H53/

Gunnar Liljas

unread,
Apr 26, 2013, 12:04:30 PM4/26/13
to knock...@googlegroups.com
...since that's what you're binding to.

<ul data-bind="foreach:
ko.utils.arrayFilter($parent.employees(),function(i){return
$data==i.Department()})">

2013/4/26 <jjf...@gmail.com>:
> Hello, I'm trying to create a departments list with a nested employee list.
> My problem is all employees show up in each department:
> http://jsfiddle.net/jjfrick/66H53/
>
> --
> You received this message because you are subscribed to the Google Groups
> "KnockoutJS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to knockoutjs+...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

jjf...@gmail.com

unread,
Apr 26, 2013, 12:17:12 PM4/26/13
to knock...@googlegroups.com
Thanks Gunnar, Do I replace: <ul data-bind="foreach: $parent.employees()"> ?
I can't get it to work.

Gunnar Liljas

unread,
Apr 26, 2013, 12:21:40 PM4/26/13
to knock...@googlegroups.com
It should work, but my example was wrapped...

http://jsfiddle.net/66H53/18/

/G

2013/4/26 <jjf...@gmail.com>:

jjf...@gmail.com

unread,
Apr 26, 2013, 12:28:56 PM4/26/13
to knock...@googlegroups.com, jjf...@gmail.com
Awesome! Thanks Gunnar - I have much to learn!

jjf...@gmail.com

unread,
Apr 26, 2013, 4:32:10 PM4/26/13
to knock...@googlegroups.com, jjf...@gmail.com
Hello Gunnar or anyone else with advice.
This approach works but it takes about 10 seconds after a refresh to appear. There are roughly 300 combined employees & departments. Does that sound normal for ko? Our web server is in-house.

Gunnar Liljas

unread,
Apr 27, 2013, 5:28:17 AM4/27/13
to knock...@googlegroups.com
I think it would be much better if you processed the data/created the
hierarchy "manually", before binding, instead of
filtering/distincting...

2013/4/26 <jjf...@gmail.com>:

jjf...@gmail.com

unread,
Apr 29, 2013, 2:37:17 PM4/29/13
to knock...@googlegroups.com
Is it possible to do something like:
    <!-- ko if: $parent.Department() == $data.Department() -->
          <h2 data-bind="text: Department()"></h2>
    <!-- /ko --> 
...say I sort my array by Department,LastName. then if the last Department in the foreach loop is equal to the current Department then don't show Department.

I have a fiddle but it doesn't work: http://jsfiddle.net/jjfrick/SSb85/
Message: TypeError: $parent.Department is not a function;

Casey Corcoran

unread,
Apr 29, 2013, 2:58:22 PM4/29/13
to knock...@googlegroups.com
You don't have a "Department" observable in your viewModel (which $parent references) hence the error.

I added one at line 9 and it works fine...


-- 
Casey Corcoran
Sent with Sparrow

--

jjf...@gmail.com

unread,
Apr 30, 2013, 4:13:25 PM4/30/13
to knock...@googlegroups.com
I thought I'd tried a different approach. I'm trying to match departments().DetpID and employees().DeptID: http://jsfiddle.net/jjfrick/SSb85/

Patrick Steele

unread,
Apr 30, 2013, 4:40:38 PM4/30/13
to knock...@googlegroups.com
I created a method to pull out employees for a particular department and then did a foreach over that:

http://jsfiddle.net/psteele/SSb85/37/

Is that what you were looking for?
On Tue, Apr 30, 2013 at 4:13 PM, <jjf...@gmail.com> wrote:
I thought I'd tried a different approach. I'm trying to match departments().DetpID and employees().DeptID: http://jsfiddle.net/jjfrick/SSb85/

jjf...@gmail.com

unread,
May 1, 2013, 8:11:10 AM5/1/13
to knock...@googlegroups.com
Patrick, Thank you! That's exactly what I was looking. I changed the Department name to a h2 and it looks great. http://jsfiddle.net/jjfrick/Zt8PZ/ Now I will test this on our intranet for speed. Thanks again.

jjf...@gmail.com

unread,
May 1, 2013, 3:45:24 PM5/1/13
to knock...@googlegroups.com, jjf...@gmail.com
We'll unfortunately this latest solution is no faster than the previous one by Gunnar. Gunnar if you read this could you be so kind as to give a example of what you were referring to..."process the data/created the hierarchy "manually", before binding, instead of filtering/distincting" from your eariler post.
Thanks, Jeff

Casey Corcoran

unread,
May 2, 2013, 11:18:09 AM5/2/13
to knock...@googlegroups.com
Here's a version using a computed:


-- 
Casey Corcoran
Sent with Sparrow

On Wednesday, May 1, 2013 at 3:45 PM, jjf...@gmail.com wrote:

We'll unfortunately this latest solution is no faster than the previous one by Gunnar. Gunnar if you read this could you be so kind as to give a example of what you were referring to..."process the data/created the hierarchy "manually", before binding, instead of filtering/distincting" from your eariler post.
Thanks, Jeff

--

Gunnar Liljas

unread,
May 2, 2013, 12:49:19 PM5/2/13
to knock...@googlegroups.com
That would do it, but I'm guessing there's a problem with the loading. How are you populating the employee array?


2013/5/2 Casey Corcoran <casey.c...@gmail.com>

jjf...@gmail.com

unread,
May 2, 2013, 2:03:33 PM5/2/13
to knock...@googlegroups.com
Here's my viewmodel, but I noticed that the contents from <!-- ko foreach: employees --> is duplicated then my browser-ff returns: A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete. If I hit continue a number of times it removes the duplicates.

$(document).ready(function () {


   
var viewModel = {
        employees
: ko.observableArray([]),
        departments
: ko.observableArray([]),

        loadEmployees
: function () {
           
OData.read(_serviceURL + "/EmployeeLine?&$filter=pClass eq 1&$orderby=DeptName,LastName",
           
function (data) {
                viewModel
.employees.removeAll();
                $
.each(data.results, function (index, item) {
                    viewModel
.employees.push(ko.mapping.fromJS(item));
               
});
           
});
       
},

        loadDepartments
: function () {
           
OData.read(_serviceURL + "/Department?&$filter=showDeptListing eq 1&$orderby=homeDeptDescript",
           
function (data) {
                viewModel
.departments.removeAll();
                $
.each(data.results, function (index, item) {
                    viewModel
.departments.push(ko.mapping.fromJS(item));
               
});
           
});
       
}
   
};

    viewModel
.employeesByDept = ko.computed(function () {
       
var deps = viewModel.departments(),
            emps
= viewModel.employees(),
            listing
= [];
        ko
.utils.arrayForEach(deps, function (dep) {
           
var depid = dep.DeptID();
            listing
.push({
                department
: dep,
                employees
: ko.utils.arrayFilter(emps, function (emp) {
                   
return emp.DeptID() == depid;
               
})
           
});
       
});
       
return listing;
   
});

   
// Activates knockout.js
    ko
.applyBindings(viewModel);
    viewModel
.loadEmployees();
    viewModel
.loadDepartments();
});

I can setup the db as one or two (relational) if it helps. 
Thanks again Gunnar and Casey for you help.

Gunnar Liljas

unread,
May 2, 2013, 3:33:50 PM5/2/13
to knock...@googlegroups.com
The problem is that you're pushing things into the observableArray. That will fire enormous amounts of things in the computed arrays. Do something like this instead.

function (data) {
  var deps=$.map(data.results, function (index, item) {
    return ko.mapping.fromJS(item);
  });
  viewModel.departments(deps);
};

I guess that would make the initial implementations work quite OK too. Maybe you don't need the items' properties to be observable. If so, you could skip the mapping part.


Casey Corcoran

unread,
May 2, 2013, 4:08:22 PM5/2/13
to knock...@googlegroups.com
Right, my thinking was the same, if you're not changing the Employee or Dept properties at runtime I wouldn't use observables either.

And if it's only a few properties you'd probably be better off mapping the observables yourself rather than use the mapping plugin.

-- 
Casey Corcoran
Sent with Sparrow

jjf...@gmail.com

unread,
May 7, 2013, 1:46:07 PM5/7/13
to knock...@googlegroups.com
Thanks Gunnar and Casey. I'm trying to figure out how to get this working without using ko.observables.
$(document).ready(function () {

   
var viewModel = {

        departments
: [],


        loadDepartments
: function () {
           
OData.read(_serviceURL + "/Department?&$filter=showDeptListing eq 1&$orderby=homeDeptDescript",
           
function (data) {

                $
.each(data.results, function (index, item) {
                    viewModel
.departments.push({
                        homeDeptDescript
: item.homeDeptDescript
                   
});
               
});
                console
.log(viewModel.departments);
           
});
       
},
   
};
   
   
// Activates knockout.js
    ko
.applyBindings(viewModel);
    viewModel
.loadDepartments();

This returns the departments (all i'm working with for testing) in the console but doesn't return anything in html, which looks like this:
<div data-bind="foreach: departments">
 
<h2 data-bind="text: homeDeptDescript"></h2>

   
<hr />
</div>
is that because data-bind only works with observables?

Gunnar Liljas

unread,
May 7, 2013, 4:09:43 PM5/7/13
to knock...@googlegroups.com
Sort of....

That doesn't work since the departments array will be populated after the binding (due to the async call), and the binding will not notice that, since it's not an observable array. You can safely make it an observableArray. Just don't push things into it.



function (data) {
   var tempArray=[];
                $.each(data.results, function (index, item) {
                   
tempArray.push({
                        homeDeptDescript
: item.homeDeptDescript
                   
});
               
});

viewModel.departments(tempArray);
                console
.log(viewModel.departments());
           
});



2013/5/7 <jjf...@gmail.com>

jjf...@gmail.com

unread,
May 10, 2013, 10:02:54 AM5/10/13
to knock...@googlegroups.com
I'm sorry this is taking so long to solve. Now I'm getting...
Error: Unable to parse bindings.
Message: ReferenceError: DeptID is not defined;
Bindings value: foreach: $root.employeesByDept(DeptID) 

ViewModel:
$(document).ready(function () {

   
function employee(FirstName, LastName, DeptID) {
       
return {
           
FirstName: ko.observable(FirstName),
           
LastName: ko.observable(LastName),
           
DeptID: ko.observable(DeptID)
       
};
   
}
   
function department(DeptID, homeDeptDescript) {
       
return {
           
DeptID: ko.observable(DeptID),
            homeDeptDescript
: ko.observable(homeDeptDescript)
       
};
   
}

   
var viewModel = {
        departments
: ko.observableArray([]),
        employees
: ko.observableArray([]),


        loadDepartments
: function () {
           
OData.read(_serviceURL + "/Department?&$filter=showDeptListing eq 1&$orderby=homeDeptDescript",
           
function (data) {

           
var deptArray = [];
                $
.each(data.results, function (index, item) {
                    deptArray
.push({
                        homeDeptDescript
: item.homeDeptDescript
                   
});
               
});
                viewModel
.departments(deptArray);
           
});

       
},

        loadEmployees
: function () {
           
OData.read(_serviceURL + "/EmployeeLine?&$filter=pClass eq 1&$orderby=DeptName,LastName",
           
function (data) {

               
var empArray = [];
                $
.each(data.results, function (index, item) {
                    empArray
.push({
                       
DeptID: item.DeptID,
                       
FirstName: item.FirstName,
                       
LastName: item.LastName,
                       
JobTitle: item.JobTitle
                   
});
               
});
                viewModel
.employees(empArray);
           
});
       
},

        employeesByDept
: function (deptId) {
           
return $.grep(this.employees(), function (e) { return e.DeptID() === deptId(); });

       
}

   
};
   
   
// Activates knockout.js
    ko
.applyBindings(viewModel);
    viewModel
.loadDepartments();

    viewModel
.loadEmployees();
});

View:
<div data-bind="foreach: departments">
 
<h2 data-bind="text: homeDeptDescript"></h2>

   
<!-- ko foreach: $root.employeesByDept(DeptID) -->
       
<div class="deptContainer">
         
<strong><span data-bind="text: FirstName"></span>
         
<span data-bind="text: LastName"></span></strong>&nbsp;&nbsp;
         
<i><span data-bind="text: JobTitle"></span></i>
       
</div>
   
<!-- /ko -->
   
<hr />
</div>

Thanks, Jeff

jjf...@gmail.com

unread,
May 10, 2013, 3:46:50 PM5/10/13
to knock...@googlegroups.com, jjf...@gmail.com
Finally got it working and it's fast!!. I changed the above employeesByDept function by removing () from:
return e.DeptID() === deptId();
like so...
return e.DeptID === deptId;
Thank you all for you help!
Reply all
Reply to author
Forward
0 new messages