Mapping: foreach binding work only the first time - foreach

I have the following snippet JQuery inside an HTML file:
$.getJSON("/events/", function (data) {
viewModel = ko.mapping.fromJS(data);
ko.applyBindings(viewModel);
});
The code is executed when, for example, the user presses a button and returns JSON like:
{"Events":[{"Name":"Event1"},{"Name":"Event2"},{"Name":"Event3"}]}
This result is linked (using KnockoutJS) to:
<ul data-bind="foreach: Events">
<li><span data-bind="text: Name"></span></li>
</ul>
Everything works fine with the first call to $.GetJSON. I get what I want, which is (browser output):
Event1
Event2
Event3
But in subsequent calls to "$. GetJSON" I get the following error in Firebug:
NotFoundError: Node was not found.
containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
And I get no list item.
What I can be doing wrong?
Thank you very much in advance.

The whole point of Knockout is to handle the interaction between your view and view model. In this case, you're trying to update a list of events in response to a button click. The button click should be linked to a function in your view model, while the list should be linked to an observable array that's updated when you click the button. I've put this together in a fiddle here: http://jsfiddle.net/mbest/uZb3e/ and the important parts are below:
<button type=button data-bind="click: updateEvents">Update</button>
<ul data-bind="foreach: data.Events">
<li><span data-bind="text: Name"></span></li>
</ul>
Javascript:
var viewModel = {
data: ko.mapping.fromJS({"Events":[]}),
updateEvents: function() {
var self = this;
$.getJSON("/events/", function (data) {
ko.mapping.fromJS(newData, self.data);
});​
}
};
ko.applyBindings(viewModel);​

My friend Thomas Brattli found the solution:
<ul data-bind="template: { name: 'template', foreach: Events }"></ul>
<script id="template" type="text/html">
<li><span data-bind="text: Name"></span></li>
</script>
Thanks !

Related

Dynamic checkbox control group using jquery mobile and knockout

I'm trying to dynamically create and filter a jquery mobile control group containing checkboxes using knockout binding. The basic idea is that the user selects an option which filters the list of checkboxes in the control group. I've seen similar questions on here but they all seem to be a one-time binding where once bound by ko and enhanced by jqm they remain unchanged. I have that behavior working, the issue occurs when the underlying viewModel changes and ko updates the list of checkboxes in the control group. A full demo of the behavior can be found on jsfiddle here: http://jsfiddle.net/hkrauss2/JAvLk/15/
I can see that the issue is due to jqm creating a wrapper div when enhancing the control group. Ko then puts new elements above the wrapper div when updating the DOM. Basically I'm asking if anyone has solved this issue and also if anyone thinks I'm asking for trouble by integrating these two libraries? Thanks to everyone in advance.
Here is the Html:
<div id="home" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
</div>
<div data-role="content">
<ul id="parent-view" data-role="listview" data-inset="true" data-bind="foreach: parentCategories">
<li></li>
</ul>
<p>
To reproduce the issue select Restaurants, come back and select Nightlife or Bars
</p>
</div>
</div>
<div id="list" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
<a data-rel="back" data-icon="carat-l" data-iconpos="notext">Back</a>
</div>
<div data-role="content">
<form>
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</form>
</div>
</div>
And the basic javascript. Note there are two external js files not listed here. One sets $.mobile.autoInitializePage = false; on the mobileinit event. The other brings in data in the form of a JSON array which is used to initialize the Categories property in the AppViewModel.
// Custom binding to handle jqm refresh
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(element).controlgroup("refresh");
} catch (ex) { }
}
}
function GetView(name) {
return $(name).get(0);
}
// Define the AppViewModel
var AppViewModel = function () {
var self = this;
self.currentParentId = ko.observable(0);
self.Categories = ko.observableArray(Categories); // Categories comes from sampledata.js
self.parentCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == 0;
});
});
self.childCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == self.currentParentId();
});
});
self.OnClick = function (viewModel, $event) {
self.currentParentId(viewModel.id);
return true;
};
};
// Create the AppViewModel
var viewModel = new AppViewModel();
// Apply bindings and initialize jqm
$(function () {
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});
Update
My old solution wraps each element in a ui-controlgroup-controls div, which adds unnecessary markup. However, the enhancement part is essential.
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
The new solution is more dynamic to maintain clean markup with no additional wrappers:
First step: Once controlgroup is created controlgroupcreate (event), add data-bind to its' container .controlgroup("container")
Second step: Add checkbox consisted of input and label. At the same time, for each element, add data-bind
Third step: Apply bindings ko.applyBindings().
The static structure of the controlgroup should be basic, it shouldn't contain any elements statically. If a checkbox is added statically, each dynamically created checkbox will be wrapped in an additional .ui-checkbox div.
<div id="child-view" data-role="controlgroup">
<!-- nothing here -->
</div>
JS
$(document).on("controlgroupcreate", "#child-view", function (e) {
$(this)
.controlgroup("container")
.attr("data-bind", "foreach: childCategories, jqmRefreshControlGroup: childCategories")
.append($('<input type="checkbox" name="checkbox" />')
.attr("data-bind", "attr: {id: 'categoryId' + id}"))
.append($('<label />')
.attr("data-bind", "text: description, attr: {for: 'categoryId' + id}"));
ko.applyBindings(viewModel, GetView('#child-view'));
});
Demo
Old solution
As of of jQuery Mobile 1.4, items should be appended to .controlgroup("container") not directly to $("[data-role=controlgroup]").
First, you need to wrap inner elements of controlgroup in div with class ui-controlgroup-controls which acts as controlgroup container.
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<div class="ui-controlgroup-controls">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</div>
Second step, you need to enhance elements inserted into controlgroup container, using .enhanceWithin().
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
Demo
Omar's answer above works very well. As he mentions in the comments however it does wrap each input/label combination in their own div. This doesn't seem to affect anything visually or functionally but there is another way as outlined below. Basically it uses the containerless control flow syntax to bind the list.
New Html
<div id="child-view" data-role="controlgroup">
<!-- ko foreach: childCategories, jqmRefreshControlGroup: childCategories, forElement: '#child-view' -->
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}"></input>
<label data-bind="text: description, attr: {for: 'categoryId' + id}"></label>
<!-- /ko -->
</div>
Using the containerless syntax means that we lose the reference to the controlgroup div in the custom binding handler. To help get that back I added the id as '#child-view' in a custom binding named forElement. The magic still all happens in the custom binding handler and Omar's enhanceWithin suggestion remains the secret ingredient. Note: I needed to change the argument list to include all arguments passed by ko.
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(allBindings.get('forElement')).enhanceWithin().controlgroup("refresh");
} catch (ex) { }
}
}
Final note: To use a custom handler on a virtual element ko needs to be notified that it is ok. The following is the updated start up statements:
// Apply bindings and initialize jqm
$(function () {
ko.virtualElements.allowedBindings.jqmRefreshControlGroup = true; // This line added
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});

add item to observable array in viewmodel from another partial view (MVC)

I am new to knockoutJS. I am working on an MVC application where I want to implement knockoutJS but the scenario is bit different.
I have a page where I am showing a list. I have 3 links on the page and on click of them I am adding partial views to page accordingly. What I want to do is that whenever I add values/data to partial views, the list which is on page should be updated with knockout. In other words I want to add value to observable array when I save data from partial view.
Please let me know if this is possible or I should keep it in jquery only.
Here is the code:
Main view:
<input type="button" value="Add Partial View" onclick="LoadPartial();" />
<div id="dvContent"></div>
<h4>People</h4>
<ul data-bind="foreach: people">
<li>
Name at position <span data-bind="text: $index"> </span>:
<span data-bind="text: name"> </span>
Remove
</li>
</ul>
<button data-bind="click: addPerson">Add</button>
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script>
function LoadPartial() {
$.ajax({
url: "/home/index",
dataType:"html",
type: "GET",
success: function (data) {
$("#dvContent").html(data);
}
});
}
</script>
<script>
function AppViewModel() {
var self = this;
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
self.addPerson = function () {
self.people.push({ name: "New at " + new Date() });
};
self.removePerson = function () {
self.people.remove(this);
}
}
ko.applyBindings(new AppViewModel());
</script>
Partial View:
<table>
<tr>
<td>Add new Row</td>
<td><input type="button" value="Add" data-bind="click: addPerson"/></td>
</tr>
</table>
Thanks,
JsHunjan
It is easy to accomplish with Knockout. You need to show some code that you have tried though if you want to get some help. I will post a general answer but it isn't going to fix your use case exactly, just basically -
Create an object to hold your new item, you can do this either in the parent or the child view model, but if you do it in the child you need to pass it back to the parent.
Once you hit a save button or add or whatever in the child view model just do a .push() into the observableArray that you created ex... - myObservableArray.push(newItem());
Knockout will recognize all of the changes taking place and perform the actions you want automatically.
Hope this helps.

Cannot get jqueryui tabs to work properly in Ember view

I'm trying to run up a little prototype in Ember.JS at the moment with a view to completely re-writing the UI of a web application as an Ember Application running against a WebAPI, but although I've managed to get Ember running OK, I cannot get jqueryui to initialise the tabs correctly.
It seems to work fine if within the view I put static data for tabs to be created from, but if I'm using dynamic data then it just doesn't work.
I have an Ember view template
<script type="text/x-handlebars" id="index">
<div id="tabs" class="ui-tabs">
<ul>
{{#each model}}
<li>
<span class="ui-icon ui-icon-person"></span>
<a {{bindAttr href="route"}} {{bindAttr title="tabTitle"}}><span>{{title}}</span></a>
</li>
{{/each}}
</ul>
{{#each model}}
<div {{bindAttr id="tabTitle"}}>
<p>
Retrieving Data - {{title}}
</p>
</div>
{{/each}}
</div>
</script>
and a view
App.IndexView = Ember.View.extend({
templateName: 'index',
didInsertElement: function () {
var tabs = $("#tabs").tabs();
}
});
and a model
App.Section = DS.Model.extend({
name: DS.attr('string'),
title: DS.attr('string'),
tabTitle: function () {
return 'tab-' + this.get('name');
}.property("name"),
route: function () {
return '#' + this.get('tabTitle');
}.property("tabTitle")
});
App.Section.FIXTURES = [
{
id: 1,
name: 'home',
title: 'Home'
},
{
id: 2,
name: 'users',
title: 'Users'
}
];
It appears to generate the HTML correctly (from checking in Firebug), but this does not work, where as if I replace the template with
<script type="text/x-handlebars" id="index">
<div id="tabs" class="ui-tabs">
<ul>
<li>
<span class="ui-icon ui-icon-person"></span>
<span>Home</span>
</li>
<li>
<span class="ui-icon ui-icon-person"></span>
<span>Users</span>
</li>
</ul>
<div id="tab-home">
<p>
Retrieving Data - Home
</p>
</div>
<div id="tab-users">
<p>
Retrieving Data - Users
</p>
</div>
</div>
</script>
it works perfectly.
I'm assuming that it's something to do with the DOM not being completely rendered by the time the tabs are initialised, but everything I can find says that didInsertElement is the place to do it, and I have had time to dig deeper yet.
I'd be grateful for any ideas.
Edit: I've managed to make this work in a fashion by doing the following:
App.IndexView = Ember.View.extend({
templateName: 'index',
didInsertElement: function () {
Ember.run.next(this, function () {
if (this.$('#tab-users').length > 0) {
var tabs = $('#tabs').tabs();
} else {
Ember.run.next(this.didInsertElement);
}
});
},
});
The problem with this is that 1) it requires me to know what one of the last elements that will be written to the view is called (and obviously with dynamic data I won't necessarily know that), so that I can keep checking for it, and 2) the inefficiency of this technique makes me want to scream!
In addition, we get a good old FoUC (Flash of Unstyled Content) after things have been rendered, but before we then get JQueryUI to style them correctly.
Any suggestions gratefully received.
It's still not nice... but this at least does work, and is reasonably efficient...
From Ember.js - Using a Handlebars helper to detect that a subview has rendered I discovered how to write a trigger, and because of the way that the run loop seems to work, inserting the trigger in the last loop on the page causes it to be called n times, but only after the loop is complete, so a quick state check "hasBeenTriggered" ensures that you only execute the delgate function once.
My code now looks like this:
<script type="text/x-handlebars" id="index">
<div id="tabs" class="ui-tabs">
<ul>
{{#each model}}
<li>
<span class="ui-icon ui-icon-person"></span>
<a {{bindAttr href="route"}} {{bindAttr title="tabTitle"}}><span>{{title}}</span></a>
</li>
{{/each}}
</ul>
{{#each model}}
<div {{bindAttr id="tabTitle"}}>
<p>
Retrieving Data - {{title}}
</p>
</div>
{{trigger "triggered"}}
{{/each}}
</div>
</script>
with the trigger
Ember.Handlebars.registerHelper('trigger', function (evtName, options) {
options = arguments[arguments.length - 1];
var hash = options.hash,
view = options.data.view,
target;
view = view.get('concreteView');
if (hash.target) {
target = Ember.Handlebars.get(this, hash.target, options);
} else {
target = view;
}
Ember.run.next(function () {
target.trigger(evtName);
});
});
and view
App.IndexView = Ember.View.extend({
templateName: 'index',
hasBeenTriggered: false,
triggered: function () {
if (!this.get("hasBeenTriggered")) {
var tabs = $('#tabs').tabs();
this.set("hasBeenTriggered", true);
}
}
});
I'd love to know if there's a better way of doing this, as this still doesn't get round the FOUC problem either (which again can be done with more JS hacks)... :(

knockoutjs 'foreach' bind can not get a property which is defined as a class?

I'm new to use Knockoutjs
I have a viewModel defind as below:
// this is a subclass
var Order = {
selfdata: {
id : ko.observable(),
goodsName : ko.observable(''),
color : ko.observable(''),
size : ko.observable(''),
count: ko.observable(''),
orderDate: ko.observable(''),
remarks: ko.observable('')
},
}
// this is a viewModel
function ShoppingCar() {
var self = this;
self.orders = ko.observableArray([]);
self.show = function (msg) {
return msg.id;
}
//init
$.get('/Shopping/GetOrders', null, function (data) {
var items = $.map(data, function (item) { return Order.init(item) });// I write a init function to init order
self.orders(items);
});
}
//then I use Knockoutjs foreach binding as below:
<ul data-role="listview" data-theme="e" data-divider-theme="d">
<!-- ko foreach: orders -->
<li data-role="list-divider" data-bind="text: $data.selfdata.id">//!I get null value here</li>
<li><a href="#">
<h3 data-bind="text: $data.isValid"></h3>
<p><strong data-bind="text: $data.selfdata.color">//! here has null value too!</strong></p>
<p class="ui-li-aside"><strong>12:14</strong>PM</p>
</a></li>
<!-- /ko -->
So, my question is, how can I display order.selfdata in a ko's foreach?
Have you tried just putting:
selfdata.id
Also, have you debugged and made sure that you are actually populating the Orders object correctly?
You need to close your strong tag. Its getting wiped out by the comments
<p><strong data-bind="text: $data.selfdata.color">//! here has null value too!</strong></p>
should be
<p><strong data-bind="text: $data.selfdata.color"></strong></p>
You also don't need the $data. selfdata.color should be working fine, assuming the data is actually being passed into the observable correctly.
If fixing the comments and removing $data don't work I would use a browser debugger to check and see what data is actually in the observable object.

Display accordion using Jquery Ajax

I have a table full of data, upon clicking on any of the record. A jquery ajax function called and get the detailed information for that record and display it in the div associated with the record.
Now i want to show it in a accordion.
Most of time a jquery accordion works like this
$(document).ready(function() {
$('#accordion').accordion();
});
But here i want that my div gets populated first with data then accordion method gets called. Because if accordion() called first then there is nothing for accordion to display as the request for data is still in processing.
My jquery ajax method is like this
$(function () {
$("span.Consignment").click(function () {
var position = 'div#' + this.innerHTML;
var url = "/Tracking/TrackingConsignment?consno=" + this.innerHTML;
$(position).load(url, function() {
$("a.Consignment").accordion();
return false;
});
});
});
This is my code
#foreach (var lst in item.Item2)
{
<a href="#" class="Consignment">
<table class="gridtable">
<a href="#">
<tr>
<td>
<span class="Consignment" href="#">#lst.ConsignmentNo</span>
</td>
<td>#lst.ConsignmentDate
</td>
</tr>
</a>
</table>
</a>
<div id="#lst.ConsignmentNo">
</div>
}
How should i make it work. First data then accordion.
Setup your accordion in the success of the .load.
.load(url,function(){
$("#accordion").accordion();
})

Resources