I am creating a multiplayer game over network so I have to react on network events.
I have this simple code, but the removePlayer method doesnt work. The addPlayer() works fine.
<table id="userlist2" class="tablesorter" cellspacing="0">
<thead>
<tr>
<th>Name</th>
<th>Queue</th>
<th>Points</th>
</tr>
</thead>
<tbody data-bind="foreach: players">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: queue"></td>
<td data-bind="text: score"></td>
</tr>
</tbody>
</table>
function PlayerViewModel() {
var self = this;
self.players = ko.observableArray();
self.addPlayer = function (Name, QueuePos, Score) {
self.players.push({
name: Name,
queue: QueuePos,
score: Score
});
}
self.removePlayer = function (Name) {
for (var i = 0; i < self.players().length; i++) {
if (self.players()[i].name == Name) console.log(i);
self.players().splice(i, 1);
}
}
}
players = new PlayerViewModel();
ko.applyBindings(players);
players.addPlayer('Player1', '0', '0');
players.addPlayer('Player2', '0', '0');
players.removePlayer('Player2');
Heres the http://jsfiddle.net/xseTc/
You have to use the remove function :
self.removePlayer = function (Name) {
self.players.remove(function(player) {
return player.name == Name;
});
}
See fiddle
html
<table id="userlist2" class="tablesorter" cellspacing="0">
<thead>
<tr>
<th>Name</th>
<th>Queue</th>
<th>Points</th>
</tr>
</thead>
<tbody data-bind="foreach: players">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: queue"></td>
<td data-bind="text: score"></td>
</tr>
</tbody>
</table>
javascript
function PlayerViewModel() {
var self = this;
self.players = ko.observableArray();
self.addPlayer = function (Name, QueuePos, Score) {
self.players.push({
name: Name,
queue: QueuePos,
score: Score
});
}
self.removePlayer = function (Name) {
self.players.remove(function(player) {
return player.name == Name;
});
}
}
players = new PlayerViewModel();
ko.applyBindings(players);
players.addPlayer('Player1', '0', '0');
players.addPlayer('Player2', '0', '0');
players.removePlayer('Player2');
Working remove function:
self.removePlayer = function (name) {
self.players.remove(function(player) {
return player.name == name
});
}
your code is fine except 2 error you have:
if (self.players()[i].name == Name) console.log(i);
self.players().splice(i, 1);
first here you are executing 2 lines so to execute them both you need to use {} so replace it with:
if (self.players()[i].name == Name) {
console.log(i);
self.players.splice(i, 1);
}
second to use splice you done need to call your observable array with () so you to use it as following :
self.players.splice(i, 1);
Working Demo
If you add view models to the array of players like this:
self.addPlayer = function (Name, QueuePos, Score) {
self.players.push(new PlayerVM(Name, QueuePos, Score)));
}
Then you can remove the player object like this:
self.removePlayer = function (player) {
self.players.remove(player);
}
Or from within the player vm:
parentVM.players.remove(self);
As per the best source from knockout site : https://knockoutjs.com/documentation/observableArrays.html
You can use remove method direct --
self.players.remove(self.players()[i]);
Related
I'm trying to get the sum of my table row (amount) with Angularjs which is connected to my sqlite database
I have managed to build CRUD operations perfectly but now I'm stuck on getting the sum of my amount field
Here's part of my html code:
<table id="tableSales" ng-init="listSales()">
<thead class="bz-tablecell">
<tr>
<th id="table-checkbox"><input type="checkbox" onclick="toggle(this);" id="chk-all"></th>
<th><b>Name</b></th>
<th><b>Amount</b></th>
<th><b>Quantity</b></th>
<th><b>Customer</b></th>
<th><b>Date</b></th>
<th class="export-ignore"><b>Status</b></th>
<th class="export-ignore"><b>Actions</b></th>
</tr>
</thead>
<tbody>
<tr class="bz-tablecell" dir-paginate="sale in sales|filter:search|itemsPerPage:20">
<td id="table-checkbox"><input type="checkbox"></td>
<td style="font-weight: 600">{{sale.name}}</td>
<td>{{sale.amount | currency: "TZS "}}</td>
<td>{{sale.quantity}}</td>
<td>{{sale.customer}}</td>
<td>{{sale.date}}</td>
<td class="export-ignore"><span class="approved" style="border-radius: 0 !important;">{{sale.status}}</span></td>
<td class="export-ignore"><a ng-click="delete(sale)">Manage</a></td>
</tr>
<tr ng-if="sales.length <= 0">
<td colspan="8" rowspan="4" class="center empty-state">
<b class="top">There are no sales yet!</b>
<br>Start by adding a new one.
</td>
</tr>
</tbody>
</table>
And here's is my angularjs code which do CRUD operations:
"USE STRICT";
app.controller("salesController", function ($scope, $location, dbService) {
$scope.sub = {
'title': 'Sales Orders'
}
$scope.listSales = function () {
//FETCH
dbService.runAsync("SELECT * FROM sales WHERE active = 1", function (data) {
$scope.sales = data;
});
}
$scope.save = function () {
if ($scope.sale.id) {
//EDIT
var id = $scope.sale.id;
delete $scope.sale.id;
delete $scope.sale.$$hashKey;
dbService.update('sales', $scope.sale, {
id: id
});
} else {
//SAVE
dbService.insert('sales', $scope.sale);
}
$scope.sale = {};
$scope.listSales();
}
$scope.delete = function (data) {
//DELETE
if (confirm("Are you sure you want to delete this sale?")) {
dbService.update('sales', {
active: 0
}, {
id: data.id
});
$scope.listSales();
}
}
});
What I need to do is get all sum of amount field, please help. Thank you
To compute a sum:
$scope.listSales = function () {
//FETCH
dbService.runAsync("SELECT * FROM sales WHERE active = 1", function (data) {
$scope.sales = data;
//COMPUTE Sum
$scope.sum = data.reduce( ( (_,sum) => sum + _.amount) , 0);
});
}
For more information, see
MDN JavaScript Reference - array.reduce
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>
For each make and model added via the "add" button, I need to check for a duplicate, set an alert if there is a duplicate and not let it add to the table. Cannot seem to find the solution...
Below is the entire code for the beginner project I am working on. My apologies ahead of time for this post, it is my first here... Thanks all.
<div>
<div>Make: <input type="text" ng-model="make"></div>
<div>Model:<input type="text" ng-model="model"></div>
<button ng-click="add()">Add</button>
<tr>
<th>Make</th>
<th>Model</th>
</tr>
<tr ng-repeat="car in cars" ng-click="rowClick(car)">
<td>{{car.make}}</td>
<td>{{car.model}}</td>
</tr>
<table class="table carsTable">
<tr>
<th>Make</th>
<th>Model</th>
</tr>
<tr ng-repeat="car in cars" ng-click="rowClick(car)">
<td>{{car.make}}</td>
<td>{{car.model}}</td>
</tr>
<script>
var carsApp = angular.module('carsApp', []);
carsApp.controller('carController', function ($scope){
$scope.cars = [];
$scope.add = function () {
$scope.cars.push({
make: $scope.make,
model: $scope.model
});
$scope.make = null;
$scope.model = null;
};
$scope.rowClick = function(car){
$scope.make= car.make;
$scope.model= car.model;
};
$scope.alert = function(){
alert('Already exists in table');
}
});
You can check for duplicates by checking each car in your array (comparing the make and model) - you can accomplish this with Array.some (returns a boolean if any of the elements in the array match the condition):
In your add function:
var hasDuplicates = $scope.cars.some(car => car.make == $scope.make && car.model == $scope.model);
if (hasDuplicates) {
alert("Car already exists");
} else {
$scope.cars.push({
make: $scope.make,
model: $scope.model
});
}
If you can't use arrow syntax:
var hasDuplicates = $scope.cars.some(function(car) {
return car.make == $scope.make && car.model == $scope.model;
});
$scope.add = function () {
let dataToAdd = {
make: $scope.make,
model: $scope.model
};
let alreadyAdded = $scope.cars.some((o) => angular.equals(o, dataToAdd ));
if (alreadyAdded) {
$scope.alert();
return false;
}
$scope.cars.push(dataToAdd);
$scope.make = null;
$scope.model = null;
};
I have 2 observableArray connected to each other. When a "feature" is clicked, I try to show "tasks" of it. However KO, does not update UI when I clicked a feature. On the console, I can track my viewModel, and I can see tasks successfully loaded on selectedFeature. However, UI does not update, even all arrays are defined as observable.
Here is a live demo on fiddle.
Please tell me where I am missing?
function GetFeatures() {
var url = "/Project/GetFeatures";
$.get(url, "", function (data) {
$.each(JSON.parse(data), function (i, item) {
projectVM.features.push(new featureViewModelCreator(item, projectVM.selectedFeature));
});
});
};
function GetTasks(selectedFeature) {
var url = "/Task/GetTaskList";
$.get(url, { "FeatureId": selectedFeature.FeatureId }, function (data) {
$.each(JSON.parse(data), function (i, item) {
selectedFeature.tasks.push(new taskViewModelCreator(item, selectedFeature.selectedTask));
});
});
};
function taskViewModelCreator(data, selected) {
var self = this;
self.TaskId = data.TaskId;
self.Title = data.Name;
self.Status = data.Status.Name;
self.CreatedDate = data.CreatedDate;
self.UserCreatedFullName = data.UserCreated.FullName;
this.IsSelected = ko.computed(function () {
return selected() === self;
});
}
function featureViewModelCreator(data, selected) {
var self = this;
self.FeatureId = data.FeatureId;
self.Name = data.Name;
self.Status = data.Status.Name;
self.CreatedDate = data.CreatedDate;
self.UserCreatedFullName = data.UserCreated.FullName;
self.tasks = ko.observableArray();
this.IsSelected = ko.computed(function () {
return selected() === self;
});
self.selectedTask = ko.observable();
self.taskClicked = function (clickedTask) {
var selection = ko.utils.arrayFilter(self.model.tasks(), function (item) {
return clickedTask === item;
})[0];
self.selectedTask(selection);
}
}
function projectViewModelCreator() {
var self = this;
self.ProjectId = 1;
self.features = ko.observableArray();
self.selectedFeature = ko.observable();
self.featureClicked = function (clickedFeature) {
self.selectedFeature(clickedFeature);
GetTasks(clickedFeature);
}
}
var projectVM = new projectViewModelCreator();
ko.applyBindings(projectVM, $('.taskmanTable')[0]);
GetFeatures();
On the UI
<div class="taskmanTable">
<table class="table table-hover featureList">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody data-bind="foreach: features">
<tr data-bind="click: $root.featureClicked, css: { active : IsSelected } ">
<td><span data-bind="text: Name"> </span></td>
</tr>
</tbody>
</table>
<table class="table table-hover taskList">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody data-bind="foreach: selectedFeature.tasks">
<tr>
<td><span data-bind="text:Title"></span></td>
</tr>
</tbody>
</table>
</div>
Here is the correct version with key notes: here. KO documentation is quite a detailed one.
You have mentioned an interesting note about UI code style: "As I know, we don't use () on UI". I did not put attention to this fact before.
We can really omit brackets for an observable: ko observable;
View contains an observable with no brackets:
<label>
<input type="checkbox" data-bind="checked: displayMessage" /> Display message
</label>
Source code:
ko.applyBindings({
displayMessage: ko.observable(false)
});
We can omit brackets for an observable array on UI: ko observable array
View contains: <ul data-bind="foreach: people">, while
View model has:
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
We can omit brackets on UI for 'leaf' observables or observables arrays. Here is your modified code sample. data-bind="if: selectedFeature" and data-bind="foreach: selectedFeature().tasks"> only leaf observable braces are omitted.
Finally, can we omit brackets for 'parent' observables? We can do it by adding another ko UI-statement (with instead of if, example 2).
The with binding will dynamically add or remove descendant elements
depending on whether the associated value is null/undefined or not
But, I believe, we can not omit brackets for parent nodes outside UI statement, because it is equal to a javascript statement: projectVM.selectedfeature().tasks. Othervise projectVM.selectedfeature.tasks will not work, because observables does not have such property tasks. Instead an observable contains an object with that property, which is retrieved by calling it via brackets (). There is, actually, an example on knockoutjs introduction page. <button data-bind="enable: myItems().length < 5">Add</button>
The code below uses the following fact (which can be found here, example 2):
It’s important to understand that the if binding really is vital to
make this code work properly. Without it, there would be an error when
trying to evaluate capital.cityName in the context of “Mercury” where
capital is null. In JavaScript, you’re not allowed to evaluate
subproperties of null or undefined values.
function GetFeatures() {
var data = {
Name: "Test Feature",
FeatureId: 1
}
projectVM.features.push(new featureViewModelCreator(data, projectVM.selectedFeature));
};
function GetTasks(selectedFeature) {
var data = {
Title: "Test Feature",
TaskId: 1
}
selectedFeature().tasks.push(new taskViewModelCreator(data, selectedFeature().selectedTask));
};
function taskViewModelCreator(data, selected) {
var self = this;
self.TaskId = data.TaskId;
self.Title = data.Title;
// Step 3: you can omit $root declaration, I have removed it
// just to show that the example will work without $root as well.
// But you can define the root prefix explicitly (declaring explicit
// scope may help you when you models become more complicated).
// Step 4: data-bind="if: selectedFeature() statement was added
// to hide the table when it is not defined, this statement also
// helps us to avoid 'undefined' error.
// Step 5: if the object is defined, we should referense
// the observable array via -> () as well. This is the KnockoutJS
// style we have to make several bugs of that kind in order
// to use such syntax automatically.
this.IsSelected = ko.computed(function() {
return selected() === self;
});
}
function featureViewModelCreator(data, selected) {
var self = this;
self.FeatureId = data.FeatureId;
self.Name = data.Name;
self.tasks = ko.observableArray();
this.IsSelected = ko.computed(function() {
return selected() === self;
});
self.selectedTask = ko.observable();
}
function projectViewModelCreator() {
var self = this;
self.ProjectId = 1;
self.features = ko.observableArray();
self.selectedFeature = ko.observable();
self.featureClicked = function(clickedFeature) {
self.selectedFeature(clickedFeature);
GetTasks(self.selectedFeature);
}
}
var projectVM = new projectViewModelCreator();
ko.applyBindings(projectVM, $('.taskmanTable')[0]);
GetFeatures();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="taskmanTable">
<table class="table table-hover featureList">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody data-bind="foreach: features">
<tr data-bind="click: $root.featureClicked, css: { active : IsSelected } ">
<td><span data-bind="text: Name"> </span></td>
</tr>
</tbody>
</table>
<hr/>
<table data-bind="if: selectedFeature()" class="table table-hover taskList">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody data-bind="foreach: selectedFeature().tasks()"><!-- $root -->
<tr>
<td><span data-bind="text: Title"></span></td>
</tr>
</tbody>
</table>
</div>
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.