Knockout dependecy with observable - javascript

Based in the example in KnockoutJS site (http://knockoutjs.com/examples/cartEditor.html) I want to do something similar.
When you select the category I want to check if I have the values in the client, if not get from server. The example above has the products in the client side, but in my case I want to check in client, and if does not exist goes to server.
Anyone can show me an example, or any tip to do that?
Thanks in advance
Edited:
The code I've try (javascript):
function getJsonObject(value) {
return $.parseJSON(value.replace(/"/ig, '"'));
}
var sg = getJsonObject('#ViewBag.SchoolGroups');
var temp = {
schoolGroups: sg,
schoolsBySchoolGroup: undefined,
getSchools: function (schoolGroupId) {
alert(schoolGroupId);
if (this.schoolsBySchoolGroup === undefined) {
//get
}
else {
//verify if exist
//if not go to server
}
return "something...";
}
};
$(document).ready(function () {
var CartLine = function () {
var self = this;
self.schoolGroup = ko.observable(sg[0].Id);
self.school = ko.observable();
// Whenever the category changes, reset the product selection
self.schoolGroup.subscribe(function () {
self.school(undefined);
});
};
var Cart = function () {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
self.lines = ko.observableArray([new CartLine()]); // Put one line in by default
// Operations
self.addLine = function () { self.lines.push(new CartLine()); };
self.removeLine = function (line) { self.lines.remove(line) };
};
ko.applyBindings(new Cart());
});
HTML code:
<table>
<thead>
<tr>
<th>Data de inĂ­cio</th>
<th>Agrupamento</th>
<th>Escola</th>
<th></th>
</tr>
</thead>
<tbody data-bind='foreach: lines'>
<tr>
<td>
<input class='required datepicker' />
</td>
<td>
<select data-bind='options: temp.schoolGroups, optionsText: "Name", optionsValue: "Id", value: schoolGroup'></select>
</td>
<td data-bind="with: schoolGroup">
<select data-bind='options: temp.getSchools($parent.schoolGroup.Id), optionsText: "Name", optionsValue: "Id", optionsCaption: "Select...", value: $parent.school'></select>
</td>
<td>
</i>
</td>
</tr>
</tbody>
</table>
</i>
I try use $parent, $data with no sucess...

I wrote fiddle in which the server call is simulated by a time.
As you can see when a category is selected the sub categories are fetched from the server and strored into the category item.
So when a category is reselected the sub categories aren't fetched from the server.
var Cat = function (title) {
var self = this;
self.subs = ko.observableArray(null);
self.title = title;
};
var ViewModel = function (cats) {
var self = this;
self.selectedCat = ko.observable();
self.availableCats = cats;
self.selectedCat.subscribe(function (item) {
if (item.subs()) {
self.availableSubCats(item.subs());
} else {
serverCall(item.title, function (subCats) {
item.subs(subCats);
self.availableSubCats(subCats);
});
}
});
self.selectedSubCat = ko.observable();
self.availableSubCats = ko.observableArray();
}
var vm = new ViewModel([new Cat('Cat1'), new Cat('Cat2'), new Cat('Cat3')]);
ko.applyBindings(vm);
var serverCall = function (cat, callback) {
setTimeout(function () {
var ar = [];
for (var index = 0; index < 5 ; index++) {
ar[index] = cat + ' - ' + index;
}
alert('server Call')
callback(ar);
}, 1000)
};
I hope it helps.

You'd still do this in the subscribe handler. Here's a pseudo code example:
self.category.subscribe(function () {
if (values on client)
self.product(values on client);
else
// make ajax call to get values
});

Related

How do I check and alert for duplicates being inserted into my 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;
};

Get list from mongodb

I am totally new in javascript. I want to get list of object from mongodb and show them in the table.
This is my code:
OrderResource
#Path("order/{size}")
#GET
public Object showOrder(#PathParam("size") String size){
List<DBObject> OrderList = null;
DBCollection collection = datastore.getDB().getCollection("Order");
DBCursor OrderCursor = collection.find(QueryBuilder.start("size").is(size).get());
OrderList = OrderCursor.toArray();
if (OrderCursor == null) {
throw new WebApplicationException(404);
}
GenericEntity<List<DBObject>> entity = new GenericEntity<List<DBObject>>(OrderList) {};
return Response.ok(entity).build();
}
order.js
coffeeApp.factory('OrderFinder', function ($resource) {
return $resource('/service/coffeeshop/order/:size',
{size: '#size'}, {}
)});
coffeeApp.controller('FindOrderController', function ($scope, $window, OrderFinder) {
$scope.findOrderBySize = function () {
OrderFinder.query({size: "medium"}).$promise
.then(
function (response) {
$scope.orders = response.orderBySize;
});
};
$scope.findOrderBySize();});
order.html
<div ng-controller="FindOrderController" class="container">
<table>
<tr>
<th>Drinker</th>
</tr>
<tr ng-repeat="OrderBySize in orders">
<th>{{OrderBySize.drinker}}</th>
</tr>
</table>
But my table is still empty. What is wrong?

KnockoutJS observableArray: group data in foreach

The table with the knockout.js bindings currenlty looks lke this:
source total division
00234 4506 div1
30222 456 div2
63321 23 div2
40941 189 div1
The desired output would be something like below. The data needs to grouped by division.
source total
div1
00234 4506
40941 189
div2
30222 456
63321 23
Here's my ViewModel:
var ReportingViewModel;
ReportingViewModel = { Results: ko.observableArray(null) }
The ReportingViewModel gets populated via an ajax request:
ReportingViewModel.Results(data["Data"]["Report"]);
Q: How can I achieve the desired output?
EDIT:
Here's my View:
<table class="table table-condensed" id="reportData">
<thead>
<tr>
<th>source</th>
<th>total</th>
<th>division</th>
</tr>
</thead>
<tbody data-bind="foreach: Results">
<tr>
<td data-bind="text: source"></td>
<td data-bind="text: total"></td>
<td data-bind="text: division"></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function () {
ReportingViewModel.Results(null);
e.preventDefault();
var numbers = null;
if ($('#numbersdd').find("option:selected").length > 0) {
numbers = $('#numbersdd').find("option:selected");}
if (numbers != null) {
$.ajax({
url: '/Reporting/ReportData.aspx',
type: 'POST',
data: numbers,
dataType: 'json',
contentType: "application/json",
success: function (data) {
ReportingViewModel.Results(data["Data"]["Report"]);
},
error: function () {
alert('Error Running Report');
}
});
}
else { alert('No Data!'); }
});
var ReportingViewModel;
ReportingViewModel = {
Results: ko.observableArray(null),
}
ko.applyBindings(ReportingViewModel);
});
</script>
You may declare computed field like this:
GroupedResults: ko.computed(function() {
var result = {};
var original = ReportingViewModel.Results();
for (var i = 0; i < original.length; i++) {
var item = original[i];
result[item.division] = result[item.division] || [];
result[item.division].push(item);
}
return result;
})
This computed field will return object like this:
{
div1: [{source: 234, total: 4506, division: 'div1'}]
div2: [{source: 30222, total: 456, division: 'div2'}]
}
As you can see each property is a division and it contains array of records which are related to this division.
Then just bind your view to this new computed field.
If you want to create your computed as part of your ReportingViewModel declaration, do it like this:
var ReportingViewModel = function(data) {
var self = this;
self.Results = ko.observableArray(data);
self.GroupedResults = ko.computed(...)
}
Then your invocation of the object is similar to how you currently have it...but not.
var reportingViewModel = new ReportingViewModel(data["Data"]["Report"]);
ko.applyBindings(reportingViewModel);
Here is a reasonable fiddle on grouping data in Knockout 2.0 that should work for you.
http://jsfiddle.net/rniemeyer/mXVtN/
Most importantly, you should be transforming your data so you have your divisions as an element to loop through and each division has a computed child that returns the matching data. He happens to use an extension on the observableArray property itself to manage this...
ko.observableArray.fn.distinct = function(prop) {
var target = this;
target.index = {};
target.index[prop] = ko.observable({});
ko.computed(function() {
//rebuild index
var propIndex = {};
ko.utils.arrayForEach(target(), function(item) {
var key = ko.utils.unwrapObservable(item[prop]);
if (key) {
propIndex[key] = propIndex[key] || [];
propIndex[key].push(item);
}
});
target.index[prop](propIndex);
});
return target;
};
Then in your markup just data-bind to loop through your divisions.

Knockout foreach nesting issue

I'm trying to generate a table using properties of two viewmodels, which are sub viewmodels of the main viewmodel, which ko.applyBindings() is called using.
The idea is to generate a row for each element in SubVM1 where the first cell is the element's name. Then for every element in SubVM2, an additional cell is added to the row.
The rows generate correctly and the first cell shows the SubVM1 name, but it is only followed by one cell, instead of however many elements are in SubVM2.
Also, the function in the data-bind doesn't work either. I've tried declaring the Value function as a prototype of SubV2 but it errors out as undefined.
Regardless, something I'm not sure about is clearly happening with the binding context, and help would be appreciated.
<tbody data-bind="foreach: {data: SubViewModel1, as: 'SubVM1'}">
<tr>
<td data-bind="text: SubVM1.Name" />
<!-- ko foreach: {data: $root.SubViewModel2, as: 'SubVM2'} -->
<td data-bind="text: Value(SubVM1.Type)"></td>
<!-- /ko -->
</tr>
</tbody>
Edit: Partially done jsfiddle: http://jsfiddle.net/jgr71o8t/1
There are a couple of things that I could see.
The first one is that the <td data-bind='' />.
Knockout does not generally like self closed tags. Always use the closing tag, in this case <td data-bind=''></td>
The second is that anything you want updated on the screen should be an ko.observable or ko.observableArray. any changes to properties after ko.applyBindings will not be reflected on the screen
HTML
<table border="1">
<tbody data-bind="foreach: {data: subViewModel1, as: 'SubVM1'}">
<tr>
<td data-bind="text: name"></td>
<!-- ko foreach: {data: $root.subViewModel2, as: 'SubVM2'} -->
<td data-bind="text: SubVM2.Value(SubVM1.Type)"></td>
<!-- /ko -->
</tr>
</tbody>
</table>
JS Fiddle Demo with knockout bindings on all properties
function MasterViewModel() {
var self = this;
self.subViewModel1 = ko.observableArray([]);
self.subViewModel2 = ko.observableArray([]);
}
function SubVM1ViewModel() {
var self = this;
self.name = ko.observable("Sub View Model 1");
self.otherProperty = ko.observable(43);
}
function SubVM2ViewModel() {
var self = this;
self.title = ko.observable('Sub View Model 2');
self.subVM1List = ko.observableArray([]);
}
SubVM2ViewModel.prototype.Value = function (type) {
for (var i = 0; i < this.subVM1List().length; i++) {
if (this.subVM1List()[i].Type === type) {
return this.subVM1List()[i].name();
}
}
};
var masterVM = new MasterViewModel();
var subVM2 = new SubVM2ViewModel();
subVM2.subVM1List.push(new SubVM1ViewModel());
masterVM.subViewModel1.push(new SubVM1ViewModel());
masterVM.subViewModel2.push(subVM2);
ko.applyBindings(masterVM);
JS Fiddle Demo with straight javascript properties
function MasterViewModel() {
var self = this;
self.subViewModel1 = [];
self.subViewModel2 = [];
}
function SubVM1ViewModel() {
var self = this;
self.name = "Sub View Model 1";
self.otherProperty =43;
}
function SubVM2ViewModel() {
var self = this;
self.title = 'Sub View Model 2';
self.subVM1List = [];
}
SubVM2ViewModel.prototype.Value = function (type) {
for (var i = 0; i < this.subVM1List.length; i++) {
if (this.subVM1List[i].Type === type) {
return this.subVM1List[i].name;
}
}
};
var masterVM = new MasterViewModel();
var subVM2 = new SubVM2ViewModel();
subVM2.subVM1List.push(new SubVM1ViewModel());
masterVM.subViewModel1.push(new SubVM1ViewModel());
masterVM.subViewModel2.push(subVM2);
ko.applyBindings(masterVM);

KnockoutJS - extending the shopping cart example

I'm currently trying to extend the KnockoutJS shopping cart example to preload existing rows from a JSON collection.
Say, I have an object like this:
var existingRows = [{
"Category":Classic Cars,
"Product":2002 Chevy Corvette,
"Quantity":1,
}, {
"Category":Ships,
"Product":Pont Yacht,
"Quantity":2,
}];
I am trying to modify the example so that on load it populates the grid with two rows, with the comboboxes pre-set to the items in the JSON object.
I can't seem to get this object playing nicely with JSFiddle, but I've got as far as modifying the Cart and CartLine functions, and ApplyBindings call as follows:
var CartLine = function(category, product) {
var self = this;
self.category = ko.observable(category);
self.product = ko.observable(product);
// other code
}
var Cart = function(data) {
var self = this;
self.lines = ko.observableArray(ko.utils.arrayMap(data, function(row) { return new CartLine(row.Category, row.Product);}))
// other code
}
ko.applyBindings(new Cart(existingRows));
This correctly inserts two rows on load, but does not set the drop down lists. Any help would be much appreciated :)
The problem is that the values of the category and product observables in the CartLine object are not simple strings. They are actual objects, e.g. category refers to a specific category from the sample data that's provided in that example, same with product.
But you're just setting them to strings.
(Another problem is that your JS object existingRows is not valid javascript because of quotes missing around the string)
To get that example working with your existingRows object you could extract the relevant category and product from the sample data:
var Cart = function(data) {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
self.lines = ko.observableArray(ko.utils.arrayMap(data, function(row) {
var rowCategory = ko.utils.arrayFirst(sampleProductCategories, function(category) {
return category.name == row.Category;
});
var rowProduct = ko.utils.arrayFirst(rowCategory.products, function(product) {
return product.name == row.Product;
});
return new CartLine(rowCategory, rowProduct, row.Quantity);
}));
// other code
}
Updated fiddle: http://jsfiddle.net/antishok/adNuR/664/
<h1> Online shopping</h1>
<button id="btnAdd" data-bind='click: addLine'>Add product</button><br /><br />
<table width='100%'>
<thead>
<tr>
<th width='25%'>Product</th>
<th class='price' width='15%'>Price</th>
<th class='quantity' width='10%'>Quantity</th>
<th class='price' width='15%'>Subtotal (in rupees)</th>
<th width='10%'> </th>
</tr>
</thead>
<tbody data-bind='foreach: items'>
<tr>
<td>
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: product'> </select>
</td>
<td class='price' data-bind='with: product'>
<span data-bind='text: (price)'> </span>
</td>
<td class='quantity'>
<input data-bind='visible:product, value: quantity, valueUpdate: "afterkeydown"' />
</td>
<td class='price'>
<span data-bind='visible: product, text: subtotal()' > </span>
</td>
<td>
<a href='#' data-bind='click: $parent.removeLine'>Remove</a>
</td>
</tr>
</tbody>
</table>
<h2>
Total value: <span data-bind='text: grandTotal()'></span> rupees
</h2>
$(document).ready(function () {
$("#btnAdd").button();
ko.applyBindings(new OnlineShopping());
});
function formatCurrency(value) {
return "$" + value.toFixed(2);
}
var Item = function () {
var self = this;
self.product = ko.observable();
self.quantity = ko.observable(1);
self.subtotal = ko.computed(function () {
var result = self.product() ? self.product().price * parseInt("0"+self.quantity(), 10) : 0;
return result;
});
};
var OnlineShopping = function () {
var self = this;
// List of items
self.items = ko.observableArray([new Item()]);
// Compute total prize.
self.grandTotal = ko.computed(function () {
var total = 0;
$.each(self.items(), function () { total += this.subtotal() })
return total;
});
// Add item
self.addLine = function () {
self.items.push(new Item())
};
// Remove item
self.removeLine = function () {
self.items.remove(this)
};
};
// Item collection
var products = [{ name: "IPhone", price: "45000" }, { name: "Galaxy Y", price: "7448" }, { name: "IPad", price: "25000" }, { name: "Laptop", price: "35000" }, { name: "Calci", price: "750"}];

Categories