I wrote a simple application to get JSON data from the sever
C# code
public JsonResult Read()
{
var products = db.Products;
return Json(GetProducts(), JsonRequestBehavior.AllowGet);
}
public IEnumerable<Product> GetProducts()
{
var data = db.Products.ToList();
return (data);
}
In the view i wrote the following to bind view model data.
<div>
<table data-bind="with: products">
<thead><tr><th>From</th><th>To</th><th>Subject</th></tr></thead>
<tbody data-bind="foreach: Object">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
</table>
</div>
<script type="text/javascript">
function ProductsViewModel() {
var self = this;
self.products = ko.observable();
$.getJSON("http://localhost:50998/Home/Read", function (data) {
self.products = JSON.parse(data);
});
}
ko.applyBindings(new ProductsViewModel());
</script>
The Json data return from action is as follows
[{"ID":1,"Name":"Roger","Description":"Test1"},{"ID":2,"Name":"Roger2","Description":"Test2"}]
After i have parse the JSON, i can't use the parsed object to update the observerable.
Does anyone know why this happens?
If you want to set the value of self.products (or the value of any other observable) to the result of JSON.parse you need to call it like this self.products(JSON.parse(data)). Observables are like functions so you need to treat them like functions ;-)
Related
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.
My get method is responding with JSON data that i want to render in a table.
This is the output when i console.log the variable declared i my get-method:
This is my controller method:
$scope.getProdukt = function (searchText, typeVariable) {
var results = produktService.searchPakninger(searchText, typeVariable);
angular.forEach(results,
function (value, key) {
console.log(key);
console.log(value);
console.log(value.status.status + ' her er status.status');
$scope.searchValue = value;
});
};
I can't seem to reference the object correctley to get the info i want. Bellow is the table I would like to render the data in. {{searchValue}} returns the entire object in one table cell. I also need an ng-repeat since there can be more than one object.
<table class="table" id="mixTables">
<thead>
<tr>
<th>GTIN</th>
<th>EPDNR</th>
<th>NAVN</th>
<th>Handling</th>
</tr>
</thead>
<tbody ng-if="!searchEmpty">
<tr ng-repeat="obj in searchValue">
<td>GTIN: nummer: {{obj.GTIN}}</td>
<td>Kategori navn: {{obj.KategoriNavn}}</td>
<td>Kategori kode: {{obj.KategoriKode}}</td>
<td><button class="btn btn-green pull-right" type="button" ng-click="addProdukt()">Velg <span class="fa fa-plus-circle"></span></button></td>
</tr>
</tbody>
</table>
I have updated my table, but no data is obj-data i rendered in the table. I dont think obj.GTIN is the right way to reference the data in the json object. The solutions with promises did not work in the controller.
------------------------------------------------------UPDATE------------------------------------------------------------------
I am also trying with a promise in the controller. Here is what i have so far:
$scope.getProdukt = function (searchText, typeVariable) {
var results2 = produktService.searchPakninger(searchText, typeVariable)
.then(function (response) {
var object = JSON.stringify(this.response);
//$scope.searchValue = response.data;
//$scope.isEmpty = false;
console.log('async promise = ' + object);
});
}
The console log prints out undefined for the object variable. I have been trying JSON.parse() as well, but with no luck. response.length is 1 or more for valid searchText.
I assume that your produktService.searchPakninge returns a html promise, so you have to use the .then function. You can then assign the results to a variable, and loop through them with ng-repeat:
$scope.getProdukt = function (searchText, typeVariable) {
produktService.searchPakninger(searchText, typeVariable)
.then(function(response) {
$scope.result = response.data;
});
};
And the view:
<tbody>
<tr ng-repeat="item in result">
<td>{{item.GTIN}}</td>
<td>{{item.Egenskaper}}</td>
<td>{{item.Markedsnavn}}</td>
<td><button class="btn btn-green pull-right" type="button">Velg <span class="fa fa-plus-circle"></span></button></td>
</tr>
</tbody>
Since you get your data in one cell I assume searchPakninger does not return a promise, so this should be enough:
<tbody>
<tr ng-repeat="obj in searchValue">
<td>{{obj.GTIN}}</td>
<td>{{obj.Egenskaper}}</td>
<td>{{obj.Markedsnavn}}</td>
<td>{{obj.KategoriNavn}}</td>
<td>{{obj.KategoriKode}}</td>
<td><button class="btn btn-green pull-right" type="button">Velg <span class="fa fa-plus-circle"></span></button></td>
</tr>
</tbody>
In case it returns a promise though, you should also transform your function as:
$scope.getProdukt = function (searchText, typeVariable) {
produktService.searchPakninger(searchText, typeVariable)
.then(function(response) {
$scope.result = response.data;
});
};
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 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>
var baseUri = '#ViewBag.ApiUrl';
var viewmodel = function () {
var self = this;
self.VoucherDetails = ko.observableArray([]);
$.getJSON(baseUri, this.VoucherDetails);
alert(self.VoucherDetails);
};
<table>
<tbody data-bind='foreach: VoucherDetails'>
<tr>
<td data-bind="text : $data.empcode">
<span data-bind=" text : $data.empcode"></span> test
</td>
<td data-bind=" text : empcode">
<span data-bind=" text : empcode"></span>
</td>
</tr>
</tbody>
</table>
This is my partial view and there is no value coming in empcode or $data.empcode, I am new to knockoutjs. What is wrong in my code?
Your code looks incomplete:
$.getJson never sets the value of VoucherDetails (no callback is passed).
You never call ko.applyBindings
It should be:
var baseUri = '#ViewBag.ApiUrl';
var viewmodel = function () {
var self = this;
self.VoucherDetails = ko.observableArray([]);
$.getJSON(baseUri, function(data){
//create your VoucherDetails here based on the data (push each item to VoucherDetails using $.each
});
};
ko.applyBindings(viewModel);
http://knockoutjs.com/documentation/observableArrays.html