1

I'm binding a nested listview with knockoutjs and styling it with JQuery mobile like this

        <ul id="machine-list" data-bind="foreach: machineList" data-role="listview">
            <li><span data-bind="text:location"></span>
                <ul  data-bind="foreach: machines" data-role="listview">
                    <li><span data-bind="text:machine"></span></li>
                </ul>
            </li>
        </ul>  

When it goes to the nested list there's nothing there. I know the binding is working because if I remove the data-role I get the nested list. Can anyone help? Cheers.

2
  • what version of jQM are u using? Commented Jul 3, 2013 at 6:35
  • Latest I think it's from a CDN on the jqm site. I'm not by my computer now. Commented Jul 3, 2013 at 10:10

1 Answer 1

1

If you're using the latest version of jQuery Mobile, you must know that nested lists have only partial support. Here's the source link.. As mentioned we'll have to use the page -> page navigation system by ourselves, so here goes.

Setup

I built up an example for this sometime back. You could adapt your code to that. I have a json object array like this :

[
      {
        "name": "DC Comics",
        "characters": [
            "All-Star Squadron",
            "Birds of Prey",
             ...
        ]
      }, 
      {
        "name": "Marvel Studios",
        "characters": [
            "Alpha Flight",
            "Avengers",
            "Champions",
            "Defenders",
            ...
         ]
       }
       ....
]

So, it might be better if you have two pages, one for showing the main list aka the groups and when clicked, another page to show the sub items ie., the characters.

On page load, I'd like to show something like this :

Home page

And when one of those links are clicked, I'd like to show the stuff which is present inside characters array. Like for example, if I choose "DC Comics", the current context would be :

{
   "name": "DC Comics",
   "characters": [
       "All-Star Squadron",
       "Birds of Prey",
             ...
    ]
}

From here, I'd take out the characters property and show it out in a list in a new page. (This is what nested lists used to do, anyway). So, something like this is what is needed:

characters

Two pages, Two ViewModels

Now to achieve this, there might be ways. But the best method would be to use multiple ViewModels, one for each page, so making it 2 and bind it when that particular page is initialised. So for a particular page,

Markup :

<div data-role="page" id="groups-page"></div>

View Model :

We'd have a seperate ViewModel:

//view model for the parent page.
var groupModel = {
    //data array
    groupInfo: [
          {
            "name": "DC Comics",
            "characters": [
                "All-Star Squadron",
                "Birds of Prey",
                 ...
            ]
          }, 
          {
            "name": "Marvel Studios",
            "characters": [
                "Alpha Flight",
                "Avengers",
                "Champions",
                "Defenders",
                ...
             ]
           }
           ....
    ]
}

pageinit event : And bind it in pageinit event of #group-page:

//events pertaining to Page 1 - the groups
$(document).on({
    "pageinit": function () {
        //apply partial binding to groups-page alone. Reason we're doing this is because only this page will be available onload
        ko.applyBindings(groupModel, this);

    }
}, "#groups-page");

Note that the second object in applyBindings (this) indicates the DOM element with id set to groups-page.

Similarly, you'd do the same thing for the second page, #character-page :

Markup :

<div data-role="page" id="character-page"></div> 

ViewModel :

 //view model for the character page - basically an empty view model to be filled with data when a link is clicked.
var characterModel = {
    name: ko.observable(),
    characterInfo: ko.observableArray()
}

pageinit event :

//events pertaining to Page 1 - the groups
$(document).on({
    "pageinit": function () {
        //apply partial binding to character-page alone. 
        ko.applyBindings(groupModel, this);

    }
}, "#character-page");

The reason we're using observables here is because everytime something is clicked, we'd be updating the observables which will automatically change the content of the second page listview.

Populating the first page

This is how my HTML structure looks like :

<ul data-bind="foreach: groupInfo">
     <li> 
       <a href="#">
         <span data-bind="text: name"></span>
         <span data-bind="text: characters.length" class="ui-li-count"></span>
       </a>
    </li>
</ul>

And after applyBindings() is bound only to the first page, I'd call a refresh on those listviews in the page :

 //apply partial binding to groups-page alone. Reason we're doing this is because only this page will be available onload
ko.applyBindings(groupModel, this);

//refresh the listview
$("ul", this).attr("data-role", "listview").listview().listview("refresh");

This would lie in the pageinit event of the #group-page.

Click event of the groups, changePage to characters

For this, I'd use the click binding of KO on the a tag of the listview in first page:

<a href="#" data-bind="click: $root.getInfo">
  <span data-bind="text: name"></span>
  <span data-bind="text: characters.length" class="ui-li-count"></span>
</a>

where $root is the context in ViewModel level (see docs).

Oh wait! getInfo must be added to groupModel, the model for the first page. So the viewmodel is changed to this :

//view model for the parent page.
var groupModel = {
    //data array
    groupInfo: [
    //groups
    ],
    //click event to be fired when an anchor tag is clicked 
    getInfo: function (data, e) {
        //here "data" variable gives the clicked elements' corresponding array and "e" gives the event object

        //prevents defaultbehaviour of anchor tag
        e.preventDefault();

        //setting up the character array in View model of page 2
        characterModel.characterInfo(data.characters);
        //setting up name variable of ViewModel in page 2
        characterModel.name(data.name);

        //change the page
        $.mobile.changePage("#character-page", {
            transition: "slide"
        });
    }
}

Note that I'm populating the characterModel only now. Since the properties inside this model are observables, every time these variables are updated, DOM is also updated. The HTML of the second page is :

<ul data-bind="foreach: characterInfo">
    <li data-bind="text: $data"></li>
 </ul>

$data is used to get the stuff inside characterInfo, if you didn't know. When this is done, you'd have something like this :

unrefreshed listview

The next step would be to refresh the listview, but not in pageinit. Reason being, pageinit is fired only once and since character-page gets updated every single time a click happens, you'd have use a pageshow or pagebeforshow method. More info here. Now the events bound to the character-page are :

//events pertaining to Page 2 - the characters
$(document).on({
    "pageinit": function () {
        ko.applyBindings(characterModel, this);
    },
    "pagebeforeshow": function () {
        //refresh listview before page is shown
        $("ul", this).attr("data-role", "listview").listview().listview("refresh");
    }
}, "#character-page");  

The above snip would refresh your styles everytime the pagebeforeshow event of "#character-page" is triggered (which is what we want) and finally give you this :

Final output

So that's it! Hope this helped :)

And, before we forget,

here's a demo.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.