is it possible to pass child to ko computed function as parameter - javascript

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.

Related

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.

Queue timer in knockout.js

What would be the simplest way to implement knockout bindings for a customer queue timer? I am not that good with knockout and my search found only questions about countdown timers. I don't where exactly should I start with this. Custom binding? Manual refresh? Every advice is appreciated.
I have an observable array which contains my customer models, one of the properties of the customer model is EnqueueTime which contains the time, when the customer entered the queue. I want to display the time customer has spent in the queue (time span between the EnqueueTime and current time).
So far, I am just showing the value of EnqueueTime. This is just a snippet from my testing code I currently use, later it will be getting some reasonable form and data:
this.Customers = ko.observableArray();
var vm = {};
vm.CustomerNote = "Test";
vm.EnqueueTime = Date.now();
vm.Priority = true;
this.Customers.push(vm);
And the view:
<div id="customerQueue" class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Note</th>
<th>Time in queue</th>
</tr>
</thead>
<tbody data-bind="foreach: Customers">
<tr data-bind="css: {danger: Priority}">
<td>
<span data-bind="visible: Priority" class="glyphicon glyphicon-warning-sign"></span>
</td>
<td data-bind="text: CustomerNote"></td>
<td data-bind="text: EnqueueTime"></td>
</tr>
</tbody>
</table>
</div>
If you only have a few objects active at a time, a good quick solution is to use a computed observable. However since nothing else changes, you'll need an external observable that can be updated independently to refresh the displayed values.
Here's a quick example:
var vm = { models: ko.observableArray() };
var trigger = ko.observable(null).extend({ notify: 'always'});
class Model {
constructor(time) {
this.EnqueueTime = time;
this.Elapsed = ko.computed(() => {
trigger(); // read observable to be able to update when it updates
var date = new Date(null);
date.setTime(new Date() - this.EnqueueTime);
return date.toISOString().substr(11, 8);
});
}
}
vm.models.push(new Model(new Date()));
vm.models.push(new Model(new Date(2018, 1, 1)));
setInterval(() => trigger(null), 1000);
ko.applyBindings(vm);
And fiddle:
https://jsfiddle.net/AlexPaven/vnk92gt7/
Let me know if I can help further.

Knockout does not update UI when observableArray is a property of other model

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>

Trouble binding tables to Knockout collection

I can't understand why these Knockout table bindings aren't working:
Javascript:
$(function () {
var FileObject = function(id, name) {
this.id = id;
this.name = name;
};
var FilesModel = function() {
this.filesSelected = ko.observable(false);
this.myFiles = ko.observableArray([new FileObject(1, 'test_1')]);
this.myFiles.push(new FileObject(2, 'test_2'));
};
var filesModel = new FilesModel();
window.filesModel = filesModel;
ko.applyBindings(filesModel);
filesModel.myFiles().push(new FileObject(3, 'test_3')); // This never shows
alert(filesModel.myFiles().length); // Shows 3 items
});
HTML:
<h3>TABLE 1</h3>
<table>
<tbody data-bind="foreach: myFiles">
<tr>
<td>FILE:</td>
<td data-bind="text: name"></td>
</tr>
</tbody>
</table>
<h3>TABLE 2</h3>
<table>
<tbody data-bind="foreach: myFiles()">
<tr>
<td>FILE:</td>
<td data-bind="text: name"></td>
</tr>
</tbody>
</table>
In both of these tables, the first 2 files will show, but the 3rd file doesn't. What am I missing?
You're really close. Two main things to point out:
Status isn't an observable, and you're attempting to unwrap it with text: status().
You're pushing the new FileObject into an unwrapped array, meaning you're bypassing the observable altogether. Push new items directly into the observable array, you'll have better luck.
I've put together a jsbin example based on your original source.
Specifically, this:
filesModel.myFiles().push(new FileObject(3, 'test_3')); // This never shows
Should be:
filesModel.myFiles.push(new FileObject(3, 'test_3')); // Now it does
In your HTML, you were trying to data-bind status(), but status is not an observable. One approach is to make your FileObject members observables.
Also, your third FileObject was never showing because your syntax was wrong. Instead of filesModel.myFiles().push, it should be just filesModel.myFiles.push
See updated fiddle
$(function () {
var FileObject = function(id, name, size, status, progress) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.status = ko.observable(status);
};
var FilesModel = function() {
this.filesSelected = ko.observable(false);
this.myFiles = ko.observableArray([new FileObject(1, 'test_1')]);
this.myFiles.push(new FileObject(2, 'test_2', 3, 'status'));
};
var filesModel = new FilesModel();
window.filesModel = filesModel;
ko.applyBindings(filesModel);
filesModel.myFiles.push(new FileObject(3, 'test_3')); // This never shows
alert(filesModel.myFiles().length); // Shows 3 items
});

knockoutJS doesn't convert Json to observable

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

Categories