Meteor returning NaN in the browser - javascript

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.

Related

*ngFor is showing an error of undefined when trying to loop over an object in angular

I get the below error when trying to loop over an object using *ngFor directive in angular:
Type 'Inventory' is not assignable to type 'NgIterable | null | undefined'.
18 <tr *ngFor="let item of searchedInventory;">
but the searchedInventory has data in it when I console logged it -
{id: 1, foodName: 'idli', foodDescription: 'made from rice', date: '2023-02-07 16:14:37.793398+05:30', price: 30, …}
The component.ts file as below,
searchedInventory!: Inventory;
constructor(private inventoryDataService : InventoryDataService, private route : ActivatedRoute){
}
ngOnInit(): void {
this.foodname = this.route.snapshot.params['foodname'];
this.searchedItems();
// console.log(this.foodname);
}
foodname!: String;
searchedItems(){
this.inventoryDataService.retrieveFoodByName(this.foodname).subscribe(
response => {
// console.log(response);
this.searchedInventory = response;
console.log(this.searchedInventory);
}
)
}
my HTML page is,
<h1>List of foods:</h1>
<div class="container">
<table class="table">
<thead>
<tr>
<th>id</th>
<th>Food Name</th>
<th>Food Description</th>
<th>Price</th>
<th>Date</th>
<th>Hotel Name</th>
<th>Hotel Address</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of searchedInventory;">
<td>{{item.id}}</td>
<td>{{item.foodName}}</td>
<td>{{item.foodDescription}}</td>
<td>{{item.price}}</td>
<td>{{item.date}}</td>
<td>{{item.hotelName}}</td>
<td>{{item.hotelAddress}}</td>
<td><button class="btn btn-success">Select</button></td>
</tr>
</tbody>
</table>
</div>
Editted,
I tried to convert the object into an array,
this.values = Object.values(this.searchedInventory);
console.log(this.values);
}
and the html is,
<tr *ngFor="let item of values">
<td id="values">{{item}}</td>
<td><button class="btn btn-success">Select</button></td>
</tr>
now the the array is not printing in row but as a column.
data is coming as an object don't need to iterate it
<tr>
<td>{{searchedInventory.id}}</td>
<td>{{searchedInventory.foodName}}</td>
<td>{{searchedInventory.foodDescription}}</td>
<td>{{searchedInventory.price}}</td>
<td>{{searchedInventory.date}}</td>
<td>{{searchedInventory.hotelName}}</td>
<td>{{searchedInventory.hotelAddress}}</td>
<td><button class="btn btn-success">Select</button></td>
</tr>
I solved it on my own!!!
Actually the problem was in the backend Springboot,
#GetMapping(path="/admin/inventory/search/{food_name}")
public Inventory getFoodByName(#PathVariable String food_name) {
Inventory foodname = inventoryService.findByFoodName(food_name);
if(foodname == null) {
throw new TodoNotFoundException("foodname - " + food_name);
}
return foodname;
}
to,
#GetMapping(path="/admin/inventory/search/{food_name}")
public List<Inventory> getFoodByName(#PathVariable String food_name) {
Inventory foodname = inventoryService.findByFoodName(food_name);
if(foodname == null) {
throw new TodoNotFoundException("foodname - " + food_name);
}
List items = new ArrayList<>();
items.add(foodname);
return items;
}
so now my backend return a ArrayList, with which I can iterate using *ngFor and it is executing perfectly!!!
sometimes I need to look a deep into everything to figure out the solution. I was thinking the problem was with the *ngFor!

API_Multiple users

**I need to make a table with 6 random users using html. I only get one user data !
what should i do ? do i have to make a loop ?
this is the code i useed **
i tried to change the id but there was no output.
please let me know what approach i shall follow.
`
<body>
<h2>API</h2>
<table>
<thead>
<th>Full Name</th>
<th>Age</th>
<th>Gender</th>
<th>Location</th>
<th>Country</th>
</thead>
<tbody>
<tr>
<td id="fullname"></td>
<td id="age"></td>
<td id="gender"></td>
<td id="location"></td>
<td id="counrty"></td>
</tr>
<tr>
<td id="fullname"></td>
<td id="age"></td>
<td id="gender"></td>
<td id="location"></td>
<td id="counrty"></td>
</tr>
</table>
<script>
const api_url="https://randomuser.me/api/";
async function getUser() {
const response= await fetch(api_url);
const data= await response.json();
const user=data.results[0];
let{first, last} = user.name;
let{gender, email, phone} = user;
let age = user.dob.age;
let{city, state, country} = user.location;
let fullName = first + " " + last;
document.querySelector("#fullname").textContent = fullName;
document.querySelector("#age").textContent = age;
document.querySelector("#gender").textContent = gender;
document.querySelector("#location").textContent = city + " ," + state;
document.querySelector("#counrty").textContent= country;
}
getUser();
</script>
</body>
</html>
`
what shall i do to take more random users ?
shall i create more ids?
I advise you when using any other people's code (api/framework etc.) to go and look at the documentation.
As you can see from my code below, you can specify how many users you want as return from the api and do a simple forEach to insert them into the table.
const userNumber = 6;
const api_url = "https://randomuser.me/api/?results=" + userNumber; //change number base to your need.
async function getUser() {
const response = await fetch(api_url);
const data = await response.json();
data.results.forEach(user => {
let {
first,
last
} = user.name;
let {
gender,
email,
phone
} = user;
let age = user.dob.age;
let {
city,
state,
country
} = user.location;
let fullName = first + " " + last;
document.querySelector('tbody').innerHTML +=
`<tr>
<td data-fullname>${fullName}</td>
<td data-age>${age}</td>
<td data-gender>${gender}</td>
<td data-location>${city} , ${state}</td>
<td data-country>${country}</td>
</tr>`;
});
}
getUser();
<h2>API</h2>
<table border="1">
<thead>
<th>Full Name</th>
<th>Age</th>
<th>Gender</th>
<th>Location</th>
<th>Country</th>
</thead>
<tbody></tbody>
</table>
Remember instead that the ID of the elements must be unique

Knockout.js get property of an object for location.href

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.

How to concatenate strings from separate elements with jquery / javascript

I am wanting to concatenate strings from 2 separate elements and have them stored in a variable.
Currently my code is setting the variable equal to:
"Daily: 1070300, Weekly: 1070300, Monthly: 1070300"
My goal is to make the variable in the console equal to:
"Daily: 10, Weekly: 70, Monthly: 300"
$(document).ready(function() {
var str = '';
$('tbody > tr').each(function() {
$(this).find('.key').each(function() {
str += $(this).text() + ": " + $(this).parents().siblings('tr').find('.value').text() + ", ";
})
});
console.log(str);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<th class="key">Daily</th>
<th class="key">Weekly</th>
<th class="key">Monthly</th>
</tr>
<tr>
<td class="value">10</td>
<td class="value">70</td>
<td class="value">300</td>
</tr>
</tbody>
</table>
Thank you for your help all!
Each time through the key loop, you're grabbing the content of all three value cells (since $(this).parents().siblings('tr').find('.value') matches all three). There are many ways to fix this but one easy one I see is to use the index argument on the inner loop to select the value cell corresponding to the current key (using jQuery's eq function):
$(document).ready(function() {
var str = '';
$('tbody > tr').each(function() {
$(this).find('.key').each(function(index) {
str += $(this).text() + ": " + $(this).parents().siblings('tr').find('.value').eq(index).text() + ", ";
})
});
console.log(str);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<th class="key">Daily</th>
<th class="key">Weekly</th>
<th class="key">Monthly</th>
</tr>
<tr>
<td class="value">10</td>
<td class="value">70</td>
<td class="value">300</td>
</tr>
</tbody>
</table>
The code is very inefficient when you keep looking up stuff in the loop. So fixing it to read the index would work, it just causes the code to do more work than needed.
How can it be improved. Look up the two rows and one loop using the indexes.
var keys = $("table .key") //select the keys
var values = $("table .value") //select the values
var items = [] // place to store the pairs
keys.each(function(index, elem){ //loop over the keys
items.push(elem.textContent + " : " + values[index].textContent) // read the text and use the index to get the value
})
console.log(items.join(", ")) // build your final string by joing the array together
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<th class="key">Daily</th>
<th class="key">Weekly</th>
<th class="key">Monthly</th>
</tr>
<tr>
<td class="value">10</td>
<td class="value">70</td>
<td class="value">300</td>
</tr>
</tbody>
</table>
Collect the .key and .value classes into a NodeList convert the NodeList into arrays. Then merge the 2 arrays into key/value pairs stored in an Object Literal. Finally convert the object into a string so it can be displayed.
Demo
Details are commented in Demo
// Collect all th.key into a NodeList and turn it into an array
var keys = Array.from(document.querySelectorAll('.key'));
// As above with all td.value
var vals = Array.from(document.querySelectorAll('.value'));
function kvMerge(arr1, arr2) {
// Declare empty arrays and an object literal
var K = [];
var V = [];
var entries = {};
/* map the first array...
|| Extract text out of the arrays
|| Push text into a new array
|| Then assign each of the key/value pairs to the object
*/
arr1.map(function(n1, idx) {
var txt1 = n1.textContent;
var txt2 = arr2[idx].textContent;
K.push(txt1);
V.push(txt2);
entries[K[idx]] = V[idx];
});
return entries;
}
var result = kvMerge(keys, vals);
console.log(result);
// Reference the display area
var view = document.querySelector('.display');
// Change entries object into a string
var text = JSON.stringify(result);
// Clean up the text
var final = text.replace(/[{"}]{1,}/g, ``);
// Display the text
view.textContent = final
<table>
<tbody>
<tr>
<th class="key">Daily</th>
<th class="key">Weekly</th>
<th class="key">Monthly</th>
</tr>
<tr>
<td class="value">10</td>
<td class="value">70</td>
<td class="value">300</td>
</tr>
</tbody>
<tfoot>
<tr>
<td class='display' colspan='3'></td>
</tr>
</tfoot>
</table>
You can also solve that using unique ids, like that:
$(document).ready(function() {
var str = '';
$('tbody > tr').each(function() {
$(this).find('.key').each(function() {
var index = $(this).attr('id').slice(3)
str += $(this).text() + ": " + $('#value'+index).text() + ", ";
})
});
console.log(str);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<th class="key" id="key1">Daily</th>
<th class="key" id="key2">Weekly</th>
<th class="key" id="key3">Monthly</th>
</tr>
<tr>
<td class="value" id="value1">10</td>
<td class="value" id="value2">70</td>
<td class="value" id="value3">300</td>
</tr>
</tbody>
</table>

Angular translate and filtering over table

I have a simple table with search -
<table>
<thead>
<tr>
<th> Name </th>
<th> Lastname </th>
<th> Job Title </th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="data in ctrl.data | filter : searchQuery">
<td>{{data.name}}</td>
<td>{{data.lastname}}</td>
<td>{{data.jobtitle | translate}}</td>
</tr>
</tbody>
</table>
<input type="text" data-ng-model="searchQuery"/>
Since job title is translated - search works only with original value - so yes with "Developer" but no with any translation of this.
Is there is any possibility to make it work with translations too?
You can create a filter to handle the translation
Filter
app.filter('translateFilter', function($translate) {
return function(input, param) {
if (!param) {
return input;
}
var searchVal = param.toLowerCase();
var result = [];
angular.forEach(input, function(item) {
var translated = $translate.instant(item.jobtitle);
if (translated.toLowerCase().indexOf(searchVal) > -1) {
result.push(item);
}
});
return result;
};
});
And use it like this:
HTML
<tr data-ng-repeat="data in ctrl.data | translateFilter:searchQuery">

Categories