i recently discover Knockout and i'm struggling for getting properties of an object in a foreach:
Here is my code :
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Created By</th>
</tr>
</thead>
<tbody data-bind="foreach: assets">
<tr class="assets" data-bind="click: $parent.detailPage">
<td>
<span data-bind="text: FileName"></span>
</td>
<td>
<span data-bind="text: CreatedBy"></span>
</td>
</tr>
</tbody>
and my script :
<script>
function ViewModel(assets) {
var self = this;
self.assets = assets;
self.detailPage = function (asset) {
location.href = '#Url.Action("Details", "Assets")/' + asset.Id;
};
};
var jsonModel = new ViewModel(#Html.Raw(Json.Encode(Model)));
var viewModel = ko.mapping.fromJS(jsonModel);
ko.applyBindings(viewModel);
In my assets, i have an id and i would like to open my view using the id of the object i click on.
But when i execute that, the url become : http://localhost:62677/Assets/Details/[object Object]
Any idee for doing this properly ?
Thanks !
Assuming that asset.Id is a knockout observable, try this
self.detailPage = function (asset) {
location.href = '#Url.Action("Details", "Assets")/' + asset.Id();
};
Looks like asset.Id is an object.
Try to investigate why it is object and not some number or string.
Related
How can I display in a table the accumulate value for each item in a observableArray with KnockoutJS?
I need somethin like:
function ViewModel(){
var self = this;
self.Item = function(day,note){
this.day = ko.observable(day);
this.note = ko.observable(note);
};
}
var itemsFromServer = [
{day:'Mo', note:1},
{day:'Tu', note:2},
{day:'We', note:3},
{day:'Th', note:4},
{day:'Fr', note:5},
{day:'Su', note:6},
];
var vm = new ViewModel();
var arrItems = ko.utils.arrayMap(itemsFromServer, function(item) {
return new vm.Item(item.day, item.note);
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr><th>Day</th><th>Note</th><th>Accumulate</th></tr>
</thead>
<tbody data-bind="foreach: arrItems">
<tr>
<td data-bind="text: day"></td>
<td data-bind="text: note"></td>
<td >the currente 'note' + the anterior 'note'</td>
</tr>
</tbody>
</table>
The last column should display the sum of current item + anterior item.
Thanks.
I'm not exactly sure what value you want the third column to be, but the main approach remains the same:
Give your Item class access to their "sibling items" by passing a reference to the array
In a computed property, do a "look behind" by looking up the items own index.
Perform some sort of calculation between two (or more) Item instances and return the value
For example, this acc property returns the acc of the previous Item and ones own note property:
var Item = function(day, note, siblings){
this.day = ko.observable(day);
this.note = ko.observable(note);
this.acc = ko.pureComputed(function() {
var allItems = siblings();
var myIndex = allItems.indexOf(this);
var base = myIndex > 0
? allItems[myIndex - 1].acc()
: 0
return base + this.note();
}, this);
};
function ViewModel() {
var self = this;
self.items = ko.observableArray([]);
self.items(itemsFromServer.map(function(item) {
return new Item(item.day, item.note, self.items);
})
);
}
var itemsFromServer = [
{day:'Mo', note:1},
{day:'Tu', note:2},
{day:'We', note:3},
{day:'Th', note:4},
{day:'Fr', note:5},
{day:'Su', note:6},
];
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Day</th>
<th>Note</th>
<th>Accumulate</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: day"></td>
<td data-bind="text: note"></td>
<td data-bind="text: acc"></td>
</tr>
</tbody>
</table>
I have two separate knockout observable arrays. if you call a function in a foreach construct and function is in $root. the element is available to you. however it appears that is not the case for computed functions. Is it possible to make it work for computed functions? here is a fiddle illustrating the situation.
https://jsfiddle.net/ubvyeba8/1/
or you may run the code snippet below.
function employee(empid, first, last){
var self = this;
this.empid = ko.observable(empid);
this.first = ko.observable(first);
this.last = ko.observable(last);
}
function model() {
var self = this;
this.employees = ko.observableArray('');
this.employeesThatWorkInHR = ko.observableArray(['1','4','5'])
this.testComputable = ko.computed(function(emp){
if (emp){
return true;
}else{
return false;
}
},this);
this.testFunction = function(emp){
if (emp){
alert('true');
}else{
alert('false');
}
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
mymodel.employees.push(new employee('1','Fred','Smith'));
mymodel.employees.push(new employee('2','John','Jones'));
mymodel.employees.push(new employee('3','Mary','Jane'));
mymodel.employees.push(new employee('4','Sue','Green'));
mymodel.employees.push(new employee('5','Terrence','Small'));
});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>First</th>
<th>Last</th>
<th>is emp passed to computed</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: employees">
<tr>
<td data-bind="text: empid"></td>
<td data-bind="text: first"></td>
<td data-bind="text: last"></td>
<td data-bind="text: $root.testComputable"></td>
<td><button class="btn" data-bind="click: $root.testFunction">
is emp passed to function
</button></td>
</tr>
</tbody>
</table>
What are you trying to accomplish by using a computed in this instance? A computed function is used when one value depends on values of other observables, so the computed function will track the dependent values and update accordingly. If what you want is to subscribe to changes in your observable arrays, then you can use a subscription function described here (search for 'Explicitly subscribing to observables').
A subscription function does accept the updated value as its function argument, so maybe you can use that for your purposes. A computed function takes no argument (which is why emp is never defined in your example), but you do have 'this' available to you to access other properties within your viewmodel.
I'm building a simple ordering app in Meteor and have come a cropper trying to get the order total even though I can get it to log in the console as a bona fide number - it is being rendered as NaN. Any help would be greatly appreciated.
Note the totals of individual products are appearing fine.
I have the following files:
meteorder/client/templates/orders/order_build.js:
Template.order.helpers({
'orderitems': function() {
var orderCart = [];
var orderItems = OrderItems.find({});
var total = 0;
orderItems.forEach(function(orderItem) {
var item = _.extend(orderItem, {});
var product = Products.findOne({
_id: orderItem.product
});
orderItem.productname = product.description;
orderItem.price = (Number(product.price) * orderItem.qty);
total += orderItem.price;
orderCart.push(orderItem);
});
orderCart.subtotal = total;
orderCart.tax = orderCart.subtotal * .23;
orderCart.total = orderCart.subtotal + orderCart.tax;
return orderCart;
}
})
meteorder/client/templates/orders/order_build.html:
<template name="order">
<div class="page-header">
<h1>
<small>My Order</small>
</h1>
</div>
<table class="span4 table table-striped table-bordered table-hover">
<thead>
<th>Qty</th>
<th>Product</th>
<th>Price</th>
<th></th>
</thead>
<tbody>
{{#each orderitems}}
<tr>
<td>{{qty}}</td>
<td>{{productname}}</td>
<td>{{currency price}}</td>
<td><span class="label-important label removeci">X</span></td>
</tr>
{{else}}
<tr>
<td colspan="3">No Products in Order</td>
</tr>
{{/each}}
<tr>
<td class="totalcol" colspan="2">SubTotal:</td>
<td colspan="2">{{currency orderCart.subtotal}}</td>
</tr>
<tr>
<td class="totalcol" colspan="2">Tax 6%:</td>
<td colspan="2">{{currency orderCart.tax}}</td>
</tr>
<tr>
<td class="totalcol" colspan="2">Total:</td>
<td colspan="2">{{currency orderCart.total}}</td>
</tr>
</tbody>
</table>
</template>
meteorder/client/lib/main.js:
Template.registerHelper('currency', function(num){
return '€' + Number(num).toFixed(2);
});
meteorder/server/server.js:
Meteor.methods({
addToOrder:function(qty,product,session){
check(qty, Number);
check(product, String);
check(session, String);
if(qty > 0){
OrderItems.insert({qty:qty,product:product,sessid:session});
console.log('reaching this fn', typeof qty, typeof product, typeof session);
} else{
console.log('Quantity is Zero');
}
},
removeOrderItem:function(id){
check(id, String);
OrderItems.remove({_id:id});
console.log('successfully deleted');
}
});
Here is a link to the GitHub repo
And the latest version of the deployed App
Thanks in advance!
Edit:
Just adding this in for Matthias:
Template.productItem.events({
'click .addOrder':function(evt,tmpl){
var qty1 = tmpl.find('.prodqty').value;
var qty = parseInt(qty1);
var product = this._id;
var sessid = Meteor.default_connection._lastSessionId; //stops others adding to your cart etc
Meteor.call('addToOrder',qty, product, sessid);
console.log('this is the quantity:', typeof qty, product, sessid);//stops others ad
}
});
to see if it gives a better picture of why the cart is not populating. Thanks
You're trying to use orderCart as both an array of objects and an object. You push a bunch of orderItem objects on to the array but at the end you attempt to set orderCart.subtotal etc...
Change your helper to have a separate summary object:
var summary = {};
summary.subtotal = total;
summary.tax = summary.subtotal * .23;
summary.total = summary.subtotal + summary.tax;
return {items: orderCart, summary: summary}
Then in your html do:
{{#each orderitems.items}}
...
{{/each}}
Finally in your summary line use {{currency orderitems.summary.tax}} etc...
Your values are rendered as NaN because orderCart is undefined.
You could define a separate helper to fix your code:
Template.order.helpers({
orderItems: function () {
return OrderItems.find().map((orderItem) => {
let product = Products.findOne({
_id: orderItem.product
});
if (product) {
orderItem.productname = product.description;
orderItem.price = calcPrice(product, orderItem);
return orderItem;
}
});
},
orderCart: function () {
let orderCart = {subtotal: 0};
OrderItems.find().forEach((orderItem) => {
let product = Products.findOne({
_id: orderItem.product
});
if (product) orderCart.subtotal += calcPrice(product, orderItem);
});
orderCart.tax = orderCart.subtotal * .23;
orderCart.total = orderCart.subtotal + orderCart.tax;
return orderCart;
}
});
function calcPrice(product, orderItem) {
return (Number(product.price) * orderItem.qty);
}
<template name="order">
<div class="page-header">
<h1>
<small>My Order</small>
</h1>
</div>
<table class="span4 table table-striped table-bordered table-hover">
<thead>
<th>Qty</th>
<th>Product</th>
<th>Price</th>
<th></th>
</thead>
<tbody>
{{#each orderItems}}
<tr>
<td>{{qty}}</td>
<td>{{productname}}</td>
<td>{{currency price}}</td>
<td><span class="label-important label removeci">X</span></td>
</tr>
{{else}}
<tr>
<td colspan="3">No Products in Order</td>
</tr>
{{/each}}
{{#with orderCart}}
<tr>
<td class="totalcol" colspan="2">SubTotal:</td>
<td colspan="2">{{currency orderCart.subtotal}}</td>
</tr>
<tr>
<td class="totalcol" colspan="2">Tax 6%:</td>
<td colspan="2">{{currency orderCart.tax}}</td>
</tr>
<tr>
<td class="totalcol" colspan="2">Total:</td>
<td colspan="2">{{currency orderCart.total}}</td>
</tr>
{{/with}}
</tbody>
</table>
</template>
Please note: I noticed a lot of missing semicolons. I strongly recommend to fix that, as this may cause several issues on deployment due to Meteor's minifying process.
I'm trying to generate HTML table from json in AngularJS.
I receive JSON in format like this:
My Service for getting the data looks like this :
customAPI.getUsers = function() {
return $http({
method: 'JSONP',
url: 'http://arka.foi.hr/WebDiP/2013_projekti/WebDiP2013_069/api/admin/users.php'
});
};
and controller for that code looks like this
controller('usersController', function($scope, customAPIservice) {
$scope.filterName = null;
$scope.usersList = [];
/*Search*/
$scope.searchFilter = function(user) {
var keyword = new RegExp($scope.filterName, 'i');
return !$scope.filterName || keyword.test(user.korisnici.korisnik_ime) || keyword.test(user.korisnici.korisnik_prezime);
};
customAPIservice.getUsers().success(function(response) {
$scope.usersList = response.korisnici;
});
});
and my view looks like this :
<input type="text" ng-model="nameFilter" placeholder="Trazi..."/>
<h2 >Korisnici</h2>
<table>
<thead>
<tr>
<th colspan="6">Korisnici sustava</th>
</tr>
<th>Surname</th>
</thead>
<tbody>
<tr ng-repeat="user in usersList| filter: searchFilter">
<td>{{$index + 1}}</td>
<td>{{user.korisnik.korisnik_prezime}}</td>
<td>{{user.korisnik.korisnik_username}}</td>
</tr>
<tr ng-show="usersList == ''">
<td colspan="5">
<img src="img/ajax-loader.gif" />
</td>
</tr>
</tbody>
</table>
I think I messed up somewhere with binding the data with the view but I' still pretty new with angular so I can't find what is wrong. Also I've looked up over internet and couldn't find anything.Please help.
You are not correctly access the properties in your data. Use:
/*Search*/
$scope.searchFilter = function(user) {
var keyword = new RegExp($scope.filterName, 'i');
return !$scope.filterName || keyword.test(user.korisnik.korisnik_ime) || keyword.test(user.korisnik.korisnik_prezime);
};
I would like to implement a button on my page that can read the line where it is and send it in a variable to the contents of this line.
my table
description----factor-----dependencies-----button(<-- read only this line)
description1---factor1----dependencies1----button
description2---factor2----dependencies2----button
I'm using jquery libraries, knockout, and in particular I am using the SimpleGrid (knockout) to create a view model , but only for convenience (so it is not mandatory).
My initial idea was to create a form for each line and use an instruction that already in the examples of knockout was used.
example : http://knockoutjs.com/examples/cartEditor.html .
Any other suggestions?
P.S. in my case I modified directly SimpleGrid to add form at this point
templateEngine.addTemplate("ko_simpleGrid_grid", "\
<table class=\"ko-grid\" cellspacing=\"0\">\
<thead>\
<tr data-bind=\"foreach: columns\">\
<th data-bind=\"text: headerText\"></th>\
</tr>\
</thead>\
<tbody data-bind=\"foreach: itemsOnCurrentPage\">\
<tr data-bind=\"foreach: $parent.columns\">\
<td data-bind=\"text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] \"></td>\
</tr>\
</tbody>\
</table>");
thanks for your attention
Here is an example of how you can access the line viewModel from a button on the line ( http://jsfiddle.net/bG4TK/2/). Is this what you need?
html:
<table>
<tbody>
<!--ko foreach:lines -->
<tr>
<td><input data-bind="value:description" /></td>
<td><input data-bind="value:factor" /></td>
<td><input data-bind="value:dependencies"/></td>
<td><button data-bind="click:sendStuff">send</button></td>
</tr>
<!--/ko-->
</tbody>
</table>
<button data-bind="click:addLine">add line</button>
js:
var Line = function(description, factor, dependencies){
var self = this;
self.description = ko.observable(description);
self.factor = ko.observable(factor);
self.dependencies = ko.observable(dependencies);
self.sendStuff = function(){
alert("I'm sending the line: " + self.description() + " " + self.factor() + " " + self.dependencies());
}
}
var VM = function(){
var self = this;
self.lines = ko.observableArray([
new Line("descr1", "factor1", "deps1"),
new Line("descr2", "factor2", "deps2"),
new Line("descr3", "factor3", "deps3"),
]);
self.addLine = function(){
self.lines.push(new Line("newDescr", "newFactor", "newDeps"))
}
}
ko.applyBindings(new VM());