Initializing knockoutjs content in child jquery mobile page - jquery-mobile

I know there are a lot of questions that cover jquery mobile / knockoutjs integration, however I couldn't find a thread that solved my issue. I have a master view model which contains child view models, and so I initialize this on page load, as that event is only fired on application load:
var viewModel = null;
$(function () {
console.debug("running init");
viewModel = new ViewModel();
ko.applyBindings(viewModel);
});
This works great on the first page of my app, however when I go to a child page, the knockoutjs content doesn't show up because jquery mobile has loaded the html dynamically and knockout doesn't know to update the binded content. I'm trying to tell it to update dynamically by using the $(document).delegate function, however I'm struggling with how it's supposed to be implemented.
<ul id="speeding" data-role="listview" data-bind="foreach: speeding.items">
<li>
<h3 class="ui-li-heading" data-bind="text: Address"></h3>
<p class="ui-li-desc" data-bind="text: Address2"></p>
<p class="ui-li-desc" data-bind="text: PrettyDate"></p>
<p class="ui-li-aside" data-bind="text: SpeedMph"></p>
</li>
<script type="text/javascript">
var loaded = false;
$(document).delegate("#page-speeding", "pagebeforecreate", function () {
if (!loaded) {
loaded = true;
ko.applyBindings(viewModel);
}
else {
$("#speeding").trigger("refresh");
}
});
</script>
</ul>
I'm putting the delegate function within the page it's being called on, as apparently that's a requirement of using delegate. Then on first load of this child page I call ko.applyBindings (I only wanted to call this on application load but I couldn't get trigger("create") to work. On subsequent calls it would call trigger("refresh") (which doesn't work for me.) The issue though is that the delegate function gets added each time I go to the child page. So on first load of the child page, it will call the delegate callback function once. If I go back to the main page, then back to the child page, it will call the delegate callback twice, and so on.
Can someone please provide guidance of the recommended approach to refreshing the knockoutjs bindings on child pages?

This is what ended up working for me. I have no idea if there's a better way or not...
var viewModel = null;
$(function () {
console.debug("running init");
viewModel = new ViewModel();
ko.applyBindings(viewModel);
var pages = [
"scorecard", "speeding", "leaderboard"
];
_.each(pages, function (page) {
$(document).on("pagebeforecreate", "#page-" + page, function () {
console.debug("applying " + page + " bindings");
ko.applyBindings(viewModel, $("#page-" + page)[0]);
});
});
});

Related

Dynamically bind a knockout component with a cshtml template breaks the viewmodel

I have an issue with knockout components in our SPA, on a certain page I'm trying to implement a component; all observables passed through the params attribute are working as they should. But when I create ko.observable(), ko.computed, .. in the component's viewmodel they just don't work as they should. (Btw we're working with asp.net mvc and we need to serve cshtml files as the templates, the razor in the views helps us with several parsing options)
So getting the files are not directly the issue, what is weird is that in the cshtml file, i can't use the unwrapped text: test, I have to write text: test(). computeds aren't updated either, nothing seems to do what it should.
What I think I'm missing is that the component is never added through ko.applyBindings() but that's purely a guess. can anybody shed some light on this please?
What to do with adding a knockout component after page has already loaded. And yes I've searched around, been fiddling all day without avail.. thanks in advance.
This is in our mainpage.
<track-item-list params="value: data, selected: selectedItems"></track-item-list>
Below within script tags:
if (!ko.components.isRegistered('track-item-list')) {
ko.components.register('track-item-list', { require: '#Url.Content("~/Scripts/viewmodels/items/_cItemList.js")' });
}
There's a viewmodel js file:
define(['knockout', 'cshtml!/items/_cItemList'], function (ko, cshtmlString) {
function ItemViewModel(params) {
var self = this;
self.selected = params.selected;
self.test = ko.observable('Hello World!');
}
return { viewModel: ItemViewModel, template: cshtmlString };
});
cshtml template:
<span data-bind="text: selected().length"></span>
<ul data-bind="foreach: selected">
<li data-bind="text: Total"></li>
</ul>
<span data-bind="text: test"></span>
The cshtml file is loaded via a custom script:
define(['jquery'], function($) {
var buildText = {};
return {
load: function (name, req, onLoad, config) {
var self = this,
file = name,
segments = file.split('/');
// If the module name does not have an extension, append the default one
if (segments[segments.length - 1].lastIndexOf('.') == -1) {
file += '.cshtml';
}
$.get(name, function (html, status, xhr) {
console.log('html loaded: ' + name);
onLoad(html);
});
}
};
});

How to show loading div in my view with json result

I am using a loading div in my MVC application. so i am getting json result value and showing a kendo grid. Because of more data it is taking time so for that purpose i used this but unfortunately it's not working.
Below is my code
This is my script used in the view
<script type="text/javascript">
$(document).ready(function () {
var action = "#Url.Content("~/Dashboard/ChartInitialDataBinding/")";
$('#spinner').show()
$.getJSON(action, null, function(something)
{
$('#spinner').hide()
});
});
</script>
This is my loading div
<div id="spinner" style="display:none">
Loading...
</div>
Thanks
You should know from documentation, that getJSON() is just wrap above ajax jquery function.
So don't you want to put your spinner not only in this function, but with all ajax requests? I think it's better, becouse you can define this in onle place and don't thik anymoreabout this problem.
In your Layout View just add this script, after your jquery library loaded:
$(document).ready(function () {
$.ajaxSetup({
beforeSend: function () {
$('#spinner').show()
},
complete: function () {
$('#spinner').hide()
},
error: function () {
$('#spinner').hide()
}
});
});
You can read more about $.ajaxSetup here.
Here is a list of things to check/fix:
-add semicolons after $('#spinner').show() and $('#spinner').hide()
-make sure you have included jQuery
-check if spinner is in view when removing "display:none"

jquerymobile not rendering nested listview with knockoutJS

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.
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 :
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:
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 :
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 :
So that's it! Hope this helped :)
And, before we forget, here's a demo.

Having trouble with jQueryUI accordion and Knockoutjs

I've been able to replicate the problem here: http://jsfiddle.net/NE6dm/
I have the following HTML which I'm using in an app:
<div data-bind="foreach: items, jqAccordion: { active: false, collapsible: true }">
<h3>
</h3>
<div>
hello
</div>
</div>
<button title="Click to return to the complaints list." data-bind="click: addItem">Add Item</button>
The idea is to display an accordion for a bunch of items that will dynamically be added/removed via a Knockout observable array.
Here's some JavaScript code which I use:
// Tab.
var tab = function (questionSet) {
this.id = questionSet.code;
this.title = questionSet.description;
this.questionSet = questionSet;
};
Custom Knockout binding handler:
ko.bindingHandlers.jqAccordion = {
init: function (element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged", function () {
ko.bindingHandlers.jqAccordion.update(element, valueAccessor);
});
},
update: function (element, valueAccessor) {
var options = valueAccessor();
$(element).accordion('destroy').accordion(options);
}
};
var NonSequentialViewModel = function () {
var items = ko.observableArray();
items.push(new tab({ id: 23, description : 'Added Inline' }));
var addItem = function() {
items.push(new tab({ id: 5, description: 'Added by a click' }));
};
return {
addItem: addItem,
items: items
}
}
var nonsequentialViewModel = new NonSequentialViewModel();
ko.applyBindings(nonsequentialViewModel);
Now the problem is this - when I view the HTML page, the item 'Added Inline' appears fine, in that I can collapse and expand it. However, when I click the button 'Add Item', a new item is added to he accordion, but it has not styling at all. For example:
In the above image, the first item is styled correctly, however the remaining items have none of the jQuery UI styling applied. Basically, any item which is added dynamically does not have any accordion styling applied.
I have seen this question
knockout.js and jQueryUI to create an accordion menu
and I've tried using the jsFiddle included in the question, but I cannot see why my code doesn't have the same result.
I'm hoping someone else has experienced this before and can help.
EDIT:
I've looked into this further and see that the problem is this - when I add a new item to the oservable array, the custom handler's update method is not executed. Thus the redrawing of the accordion never happens.
I can't see why the update should not be called. This is realyl doing my head in! :)
EDIT:
I've been able to replicate the problem here: http://jsfiddle.net/NE6dm/
Your NonSequentialViewModel constructor doesn't return items array. Update return statement to this:
return {
items: items,
addItem: addItem
}
Here is working fiddle: http://jsfiddle.net/vyshniakov/MfegM/323/
Old question, but I believe I was having the same problem.
I may need to submit a bug to knockout.js. I just spend several hours trying to figure out similar problems.
In short... if I load your jsfiddle and change the version of knockout to 2.1.0, it appears to work fine.
this:
<script type="text/javascript" src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.2.0.debug.js"></script>
to this:
<script type="text/javascript" src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.1.0.debug.js"></script>
(The only difference being the version 2.2.0 --> 2.1.0)
Further... I ended up settling on several versions:
jquery: 1.9.1
jquery-ui (combined): 1.9.2
knockoutjs: 2.1.0

jQuery UI Tabs: URL instead of AJAX does not work properly

I wrote like there: http://jqueryui.com/demos/tabs/#...follow_a_tab.27s_URL_instead_of_loading_its_content_via_ajax
<script type="text/javascript">
$(function(){
$("#tabs").tabs({
select: function(event, ui) {
var url = $.data(ui.tab, 'load.tabs');
if( url ) {
location.href = url;
return false;
}
return true;
}
});
});
</script>
<div id="tabs">
<ul>
<li>
Home
</li>
<li>
About
</li>
</ul>
</div>
Tabs are created, but initial list (div, ul, li) is visible as well. Another problem: when I hover over tab, I see URL kind of /default.htm#ui-tabs-1, /default.htm#ui-tabs-2 etc. But I want to see URL "/default.htm" over the 1st tab and URL "/about.htm" over the 2nd tab.
What could I do to solve my problem?
UPDATE
In version 1.9 there is powerful widget "menu".
You are miss interpreting the jQuery UI Tabs.
This Tabs are for having content hide/show and if using ajax pull the page info and show it on demand.
if you want those tabs to act as a menu ... then you need a menu, not the jQuery UI Tabs.
If your idea if to use this tabs but to fetch the /about.htm as a new content, then you can use the ajax example
http://jqueryui.com/demos/tabs/#ajax
keep in mind that it will fetch the entire content, so the /about.htm page should not have <html> neither <body> tags
I don't want to encourage you to do this, but the solution is currently available on jQuery UI's website: http://jqueryui.com/demos/tabs/#...follow_a_tab.27s_URL_instead_of_loading_its_content_via_ajax
You may extend it a bit to follow only certain URLs:
select: function(e, ui)
{
var tab = $(ui.tab);
var url = $.data(ui.tab, 'load.tabs');
if(url && tab.attr('data-ajax') == 'false')
{
location.href = url;
return false;
}
return true;
}
Then, when defining tabs:
<li>...</li>

Resources