KnockoutJS observableArray: group data in foreach - javascript

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.

Related

Angular - ng-repeat not updating on update of nested array

I am rendering data using ng-repeat through GET request, which retrieves an array.
HTML
<div ng-controller="candidateCtrl" >
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>NO</th>
<th>NAMA</th>
<th>NIP</th>
<th>INSTANSI</th>
<th><span ng-show="animateCandidateAdmin" class="ion-load-a"></span></th>
</tr>
</thead>
<tbody ng-repeat="candidate in candidatesAdmin">
<tr class="well whel">
<td>{{$index + 1}}</td>
<td>{{candidate.candidate_name}}</td>
<td>{{candidate.candidate_nip}}</td>
<td>{{candidate.candidate_institusi}}</td>
<td>
<button class="btn btn-xs btn-success" ng-show="candidate.m_assesment_assesment_id == NULL" ng-click="addCandidate3(candidate.candidate_id)">
</td>
</tr>
</tbody>
</table>
</div><!-- OFF-MAINBAR -->
<div ng-repeat="item in percentage_penilaian" >
<div id="candidate_{{item.m_assesment_assesment_id}}" >
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>NO</th>
<th>NAMA</th>
<th>NIP</th>
<th>INSTANSI</th>
<th>BOBOT</th>
<th>SKOR</th>
<th>NILAI</th>
<th><span ng-show="animateCandidateManagerial" class="ion-load-a"></span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="candidate in candidates[item.m_assesment_assesment_id]" class="well whel">
<td>{{$index + 1}}</td>
<td>{{candidate.candidate_name}}</td>
<td>{{candidate.candidate_nip}}</td>
<td>{{candidate.candidate_institusi}}</td>
<td>{{candidate.percentage}}%</td>
<td ng-show="candidate.skor != NULL">
<button ng-click="$eval(arrAddCandidate[percentage_penilaian[$parent.$index+1].m_assesment_assesment_id])(candidate.candidate_id)"><i class="ion-arrow-right-a"></i> Mengikuti {{percentage_penilaian[$parent.$index+1].assesment_name}}</button>
</td>
</tr>
</tbody>
</table>
</div><!-- OFF-MAINBAR -->
</div>
</div>
</div>
</div>
JS
<script>
var SITEURL = "<?php echo site_url() ?>";
var selectionApp = angular.module("selectionApp", ["isteven-multi-select"]);
selectionApp.controller('candidateCtrl', function ($scope, $http) {
$scope.candidates = [];
$scope.arrAddCandidate = [];
$scope.getPercentagePenilaian = function () {
var url = SITEURL + 'xxx/xxx/' + 14;
$http.get(url).then(function (response) {
$scope.percentage_penilaian = response.data;
for(var i in response.data){
$scope.arrAddCandidate[response.data[i].m_assesment_assesment_id] = "addCandidate"+response.data[i].m_assesment_assesment_id;
}
})
};
$scope.getCandidateAdmin = function () {
var url = SITEURL + 'api/get_candidate_admin/' + 14;
$http.get(url).then(function (response) {
$scope.candidatesAdmin = response.data;
})
};
$scope.get_3 = function () {
var url = SITEURL + 'xxx/xxx/3__14';
$http.get(url).then(function (response) {
$scope.$apply(function () {
$scope.candidates[3] = response.data;
// $scope.candidates.push(response.data);
});
})
};
$scope.addCandidate3 = function (id) {
$scope.animateCandidateAdmin = true;
var postData = $.param({
candidate_id: id,
assesment_id: 3 });
$.ajax({
method: "POST",
url: SITEURL + "xx/xxx/xxxxx",
data: postData,
success: function (response) {
if(response=='sukses'){
$scope.animateCandidateAdmin = false;
$scope.getCandidateAdmin();
$scope.get_3();
}
}
});
};
$scope.get_5 = function () {
var url = SITEURL + 'xx/xxx/5__14';
$http.get(url).then(function (response) {
$scope.$applyAsync(function () {
$scope.candidates[5] = response.data;
// $scope.candidates.push(response.data);
});
})
};
$scope.addCandidate5 = function (id) {
$scope.animateCandidateAdmin = true;
var postData = $.param({
candidate_id: id,
assesment_id: 5 });
$.ajax({
method: "POST",
url: SITEURL + "xx/xxx/xxxxx",
data: postData,
success: function (response) {
if(response=='sukses'){
$scope.animateCandidateAdmin = false;
$scope.getCandidateAdmin();
$scope.get_5();
}
}
});
};
angular.element(document).ready(function () {
$scope.getPercentagePenilaian();
$scope.get_3;
$scope.get_5;
});
});
</script>
Response from $scope.getCandidateAdmin
[{"candidate_id":"24","candidate_name":"contoh","candidate_nip":"12345","candidate_institusi":"Institusi A","selection_selection_id":"14"}]
Response $scope.getPercentagePenilaian
[{"id":"14","m_assesment_assesment_id":"3","percentage":"50"},
{"id":"15","m_assesment_assesment_id":"5","percentage":"10"}]
Response from $scope.get_3
[{"id":"43","selection_selection_id":"14","m_assesment_assesment_id":"3"
,"candidate_id":"24","m_candidate_id" :"1","candidate_name":"contoh","candidate_nip":"12345","candidate_institusi":"Institusi A","competency_skor":null}]
After I adding candidate, I believe that the $scope.candidates array is updated correctly, however the table in my view does not change. I don't know what i'm doing wrong.
Potential Issue
I think 'm_assesment_assesment_id' is stored as a String and not a Integer, which is the root of your problem.
[
{ "id":"14",
"m_assesment_assesment_id":"3", // Note: the numbers are wrapped in quotes
"percentage":"50"
},
{ "id":"15",
"m_assesment_assesment_id":"5", // JSON parsers will interpret these as Strings, NOT numbers
"percentage":"10"}
]
Background to Understanding Problem
Since the ng-repeat is using the item.m_assesment_assesment_id property, Angular resolves these as Strings. When this String is passed into the Array, the Array is interpreted as a JavaScript Object and not an array. For example:
// Both the following evaluate to the same thing
// Using dot accessor
item.m_assesment_assesment_id
// Using square brackets to access data through Object Notation
item["m_assesment_assesment_id"]
This is why JavaScript interprets your Array as an Object, using item.m_assesment_assesment_id as a key and not and index.
<!-- item.m_assesment_assesment_id will evaluate as a String as the JSON data specifies the String format, triggering Object evaluation, not Array evaluation -->
<tr ng-repeat="candidate in candidates[item.m_assesment_assesment_id]" class="well whel">
Here is some good overview of Array Evaluation:
https://www.w3schools.com/js/js_arrays.asp
Associative Arrays Many programming languages support arrays with
named indexes.
Arrays with named indexes are called associative arrays (or hashes).
JavaScript does not support arrays with named indexes.
In JavaScript, arrays always use numbered indexes.
Solutions
There are a couple of solutions. If you have access to manipulating the JSON formatting, I would recommend correcting the problem there:
{
"id":"14",
"m_assesment_assesment_id": 3, // No quote == Integer
"percentage":"50"
}
But this would require you to have access to the server code constructing the response and there may be architectural reasons for using a string.
Alternatively, you could change your ng-repeat:
<tr ng-repeat="candidate in candidates[parseInt(item.m_assesment_assesment_id)]" class="well whel">
However there is a performance loss as this requires more processing during Angular's $digest() event. This will not be significant for small datasets, but it is worth noting.
If you don't have access to changing the JSON formatting on the server and you don't want to put extra load on the $digest event, your control could process the response data from the AJAX calls. This is often the approach recommended by Angular documentation.
angular.module("selectionApp", ["isteven-multi-select"])
.controller('candidateCtrl', function ($scope, $http) {
$scope.candidates = [];
$scope.arrAddCandidate = [];
$scope.getPercentagePenilaian = function () {
var url = SITEURL + 'xxx/xxx/' + 14;
$http.get(url).then(function (response) {
// USE OUR CONVENIENCE METHOD HERE TO CONVERT STRINGS TO INTEGERS
$scope.percentage_penilaian = processPercentagePenilaian(response.data);
for(var i in response.data){
$scope.arrAddCandidate[response.data[i].m_assesment_assesment_id] = "addCandidate"+response.data[i].m_assesment_assesment_id;
}
})
};
// *** MORE of your code exists between these functions
// ALSO YOU HAVE TO CALL THIS IN THE CALLBACK OF THE APPROPRIATE AJAX CALLBACK
var processPercentagePenilaian = function(items) {
// Loop through the Items
angular.forEach(items, function(item, key) {
// For Every Item, convert the m_assesment_assesment_id to an Integer
item.m_assesment_assesment_id = parseInt(item.m_assesment_assesment_id);
});
// End Controller
});

Knockout, collection property becomes null when I try and remove one item from it if it has more than one item in it?

Really strange knockout error I am having. It's a pretty complex scenario so please see this fiddle:
http://jsfiddle.net/yx8dkLnc/
Essentially I have a double nested collection, the first collection FishMeasurements contains a collection of objects which have the species information associated with it, and a collection of Measurements which hold all measurements associated with that species.
Now when I try and remove items from the nested collection in this HTML:
<!-- ko foreach: FishMeasurements() -->
<h3><span data-bind="text: SpeciesName"></span><span data-bind="text: SpeciesId" style="display: none;"></span></h3>
<table class="table table-striped">
<thead>
<tr>
<th>Length</th>
<th>Count</th>
<th>Weight</th>
<th>Fish Id</th>
<th> </th>
</tr>
</thead>
<tbody data-bind="foreach: Measurements()">
<tr>
<td><span data-bind="text: LengthInMillimeters"></span></td>
<td><span data-bind="text: Count"></span></td>
<td><span data-bind="text: WeightInPounds"></span></td>
<td><span data-bind="text: FishCode"></span></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<!-- /ko -->
The remove measurement function doesn't work when the Measurements collection has more than one object. I click the remove link, and it throws an error that says:
VM617:163 Uncaught TypeError: Cannot read property 'Measurements' of null(…)
The strange thing about this is, if I only add one item to the Measurements collection, the delete button work fine, but as soon as I add multiple measurements, if I click remove on any item in the table but the first row, this error is generated. However, if I click the first item in the table per species, there is no error and all records are removed!
Something tells me its treating Measurements like one object instead of a collection, because it only works on index 0. But I'm not sure because in console I am able to type:
mappedModel.FishMeasurements()[0].Measurements()[1]
And get a full ko_mapping object returned, so it's not null. But for some reason when I click the remove, it is null. As long as there is only one measurement per species, clicking remove works fine, as soon as there are more it breaks.
What am I doing wrong?
When you addMeasurement for the first time speciesId, speciesName are getting defined because fishMeasurementBySpecies === undefined and therefore when you remove the first item you have a valid measurement.SpeciesId() as a parameter inside removeMeasurement function but for the second time and more since fishMeasurementBySpecies is not undefined anymore then speciesId, speciesName never get set and then whenremoveMeasurementis called,measurement.SpeciesId() is null.
In order to make your model works, you need to apply below changes.
define var speciesId = mappedModel.SelectedSpecies(); before your if statment
var speciesId = mappedModel.SelectedSpecies();
if (fishMeasurementBySpecies === undefined || fishMeasurementBySpecies === null) {
Put () for Measurements inside removeMeasurement function where you want to get the length
if(fishMeasurementBySpecies.Measurements().length === 0)
Below I provide you an example of what you want to do by using manual view model instead of using mapping plugin.
Example: https://jsfiddle.net/kyr6w2x3/118/
Your example :http://jsfiddle.net/yx8dkLnc/1/
VM:
var data = {
"AvailableSpecies":
[
{"Id":"f57830b8-0766-4374-b481-82c04087415e","Name":"Alabama Shad"},
{"Id":"3787ce10-e61c-4f03-88a5-ff648bb55480","Name":"Alewife"},{"Id":"e923214f-4974-4663-9158-d6979ce637f1","Name":"All Sunfish Spp Ex Bass And Crappie"} ],
"SelectedSpecies": null, "CountToAdd":0,"LengthToAdd":0,"WeightToAdd":0,"GenerateFishCode":false,"FishMeasurements":[]
};
function AppViewModel(){
var self = this;
self.AvailableSpecies = ko.observableArray(data.AvailableSpecies);
self.SelectedSpecies = ko.observable();
self.CountToAdd = ko.observable();
self.LengthToAdd = ko.observable();
self.WeightToAdd = ko.observable();
self.FishCode = ko.observable();
self.FishMeasurements = ko.observableArray([]);
self.addMeasurement = function(item) {
var SpeciesExists = false;
ko.utils.arrayForEach(self.FishMeasurements(), function (item) {
if(item.SpeciesId() == self.SelectedSpecies().Id) {
var len = item.Measurements().length;
// you may have a better way to generate a unique Id if an item is removed
while(item.Measurements().findIndex(x => x.Id() === len) > 0){
len++;
}
item.Measurements.push(new MeasurementsViewModel({LengthInMillimeters:self.LengthToAdd(),
Count:self.CountToAdd(),
WeightInPounds:self.WeightToAdd(),
FishCode:self.FishCode(),
Id:len ++,
ParentId:self.SelectedSpecies().Id
})
);
SpeciesExists = true;
}
});
if(!SpeciesExists){
self.FishMeasurements.push(new FishMeasurementsViewModel({SpeciesName:self.SelectedSpecies().Name,
SpeciesId:self.SelectedSpecies().Id,
Measurements:[{LengthInMillimeters:self.LengthToAdd(),
Count:self.CountToAdd(),
WeightInPounds:self.WeightToAdd(),
FishCode:self.FishCode(),
Id:1}]
})
);
}
}
self.removeMeasurement = function(data){
ko.utils.arrayForEach(self.FishMeasurements(), function (item) {
if(item && item.SpeciesId() == data.ParentId()) {
ko.utils.arrayForEach(item.Measurements(), function (subItem) {
if(subItem && subItem.Id() == data.Id()) {
item.Measurements.remove(subItem);
}
});
}
if(item && item.Measurements().length == 0){
self.FishMeasurements.remove(item);
}
});
}
}
var FishMeasurementsViewModel = function(data){
var self = this;
self.SpeciesName = ko.observable(data.SpeciesName);
self.SpeciesId = ko.observable(data.SpeciesId);
self.Measurements = ko.observableArray($.map(data.Measurements, function (item) {
return new MeasurementsViewModel(item,self.SpeciesId());
}));
}
var MeasurementsViewModel = function(data,parentId){
var self = this;
self.LengthInMillimeters = ko.observable(data.LengthInMillimeters);
self.Count = ko.observable(data.Count);
self.WeightInPounds = ko.observable(data.WeightInPounds);
self.FishCode = ko.observable(data.FishCode);
self.Id = ko.observable(data.Id);
self.ParentId = ko.observable(parentId ? parentId : data.ParentId);
}
var viewModel = new AppViewModel();
ko.applyBindings(viewModel);

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);

Knockout dependecy with observable

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
});

Traversing Table with jQuery

I have a table with an HTML attribute on the TR element titled "data-order" which simply holds an integer indicating the order in which to sort the table (descending). Right now the code only checks the row ahead of the TR clicked - what I'm attempting to do is to get it to scan all rows ahead of its position in the table and once it finds a number greater than (not greater than or equal to) then call the swaprow function...
Here is the javascript used to move the row up.
function adjustRank(id, e) {
var url = "/ajax/moveup/" + aid;
var row = $(e).closest("tr").get(0);
var prevRow = $(row).prev().get(0);
var moveUp = false;
var prevRowOrder = parseInt($(prevRow).attr("data-order"));
var rowOrder = parseInt($(row).attr("data-order"));
$.ajax({
type: "POST",
url: url,
data: {aid: aid},
dataType: "json",
success: function ()
{
if(rowOrder + 1 > prevRowOrder) // think this is where I need to traverse the table
swapRows(row, prevRow);
},
failure: function () { alert("Error processing request."); }
});
}
and here are a couple of items in the table for example:
<table id="listings" style="min-height:150px; width:100%;">
<tr id="1" data-order="11"><td>1</td><td align="left"><span onclick="adjustRank('ace93485-cea5-4243-8294-9f3d009aba3d', this)" style="cursor:pointer;">Lindsey Vonn</span></td><td></td></tr>
<tr id="2" data-order="6"><td>2</td><td align="left"><span onclick="adjustRank('9f83aed6-b99a-4674-a8b7-9f3d009aba38', this)" style="cursor:pointer;">Al Horford</span></td><td></td></tr>
<tr id="3" data-order="5"><td>3</td><td align="left"><span onclick="adjustRank('d48a52bd-17e9-4631-9a2e-9f3d009aba39', this)" style="cursor:pointer;">Derek Jeter</span></td><td></td></tr>
</table>
You may use recursion to solve that problem. Please, see the code.
window.adjustRank = function(id, el) {
var orderDiff = 1;
var row = $(el).closest("tr");
var order = parseInt(row.attr("data-order")) + orderDiff;
row.attr("data-order", order);
var prevRow = row.prev();
if(prevRow.get(0)){
moveUp(order, row, prevRow);
}
}
window.moveUp = function(order, row, prevRow){
if(order > parseInt(prevRow.attr("data-order"))){
var prevPrevRow = prevRow.prev();
if(prevPrevRow.get(0)){
moveUp(order, row, prevPrevRow);
} else {
prevRow.before(row);
}
} else {
prevRow.after(row);
}
}
If you get orderDiff via AJAX, then place the code into your AJAX call success function. Please, see this demo

Categories