Set href using $root value within foreach loop - javascript

The link in the first td column spazzes out when trying to bind to $root.rootBaseUrl.
In the second td column, the same rootBaseUrl observable prints perfectly.
The difference is that in the first td column, I am trying to set the value within attr:.
Also, please note that there is a foreach loop happening at the tbody level. Hence the use of $root prefix.
<tbody data-bind="foreach: siteList">
<tr>
<td><h3><a data-bind="text: SiteName, attr: {href: $root.rootBaseUrl + SiteID}"></a></h3></td>
<td><h3><span data-bind="text: $root.rootBaseUrl"></span></h3></td>
</tr>
</tbody>
var rootBaseUrl = ko.observable("");
var index = window.location.toString().indexOf("RiskOrder");
var baseURL = window.location.toString().substring(0, index);
this.rootBaseUrl(baseURL);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Basically, I am getting the current browser URL in the JS, stripping it to the base root url, then trying to add this static URL to the href binding and concatenating with a dynamic SiteID value.
Is this possible?

Replace attr with text and get a glimplse of your problem:
function Vm(){
var self = this;
self.SiteID = ko.observable("AX123");
}
function RootVm(){
var self = this;
var index = window.location.toString().indexOf("RiskOrder");
var baseURL = window.location.toString().substring(0, index);
self.rootBaseUrl = ko.observable("");
self.SiteName = ko.observable("My Site");
self.rootBaseUrl(baseURL);
self.SiteList = ko.observableArray([new Vm()]);
}
ko.applyBindings(new RootVm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table>
<tbody data-bind="foreach: SiteList">
<tr>
<td>
<h3><a data-bind="text: $root.rootBaseUrl + SiteID"></a></h3>
</td>
<td>
<h3><span data-bind="text: $root.rootBaseUrl"></span></h3>
</td>
</tr>
</tbody>
</table>
It renders function....., so a textual representation of a function. This is because rootBaseUrl is a function. If you want to use it in an expression, you have to use parentheses:
<h3><a data-bind="text: $root.rootBaseUrl()+ SiteID()"></a></h3>
If you use it as the only thing in a binding, the parentheses are optional:
<h3><span data-bind="text: $root.rootBaseUrl()"></span></h3>
So your fix would be:
function Vm(){
var self = this;
self.SiteID = ko.observable("AX123");
self.SiteName = ko.observable("My Site");
}
function RootVm(){
var self = this;
var index = window.location.toString().indexOf("RiskOrder");
var baseURL = window.location.toString().substring(0, index);
// On SO Snippets window.location works differently so we hack it:
baseURL = "https://example.com/my-website/url/";
self.rootBaseUrl = ko.observable("");
self.rootBaseUrl(baseURL);
self.SiteList = ko.observableArray([new Vm()]);
}
ko.applyBindings(new RootVm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table>
<tbody data-bind="foreach: SiteList">
<tr>
<td>
<h3><a data-bind="text: SiteName, attr: { href: $root.rootBaseUrl() + SiteID() }"></a></h3>
</td>
<td>
Second column
</td>
</tr>
</tbody>
</table>
Or use a computed so you can (a) unit test the logic and (b) make the parentheses optional again:
function Vm(urlBaseVm){
var self = this;
self.SiteID = ko.observable("AX123");
self.SiteName = ko.observable("My Site");
self.hrf = ko.computed(function() {
return urlBaseVm.rootBaseUrl() + self.SiteID();
});
}
function RootVm(){
var self = this;
var index = window.location.toString().indexOf("RiskOrder");
var baseURL = window.location.toString().substring(0, index);
// On SO Snippets window.location works differently so we hack it:
baseURL = "https://example.com/my-website/url/";
self.rootBaseUrl = ko.observable("");
self.rootBaseUrl(baseURL);
self.SiteList = ko.observableArray([new Vm(self)]);
}
ko.applyBindings(new RootVm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table>
<tbody data-bind="foreach: SiteList">
<tr>
<td>
<h3><a data-bind="text: SiteName, attr: { href: hrf }"></a></h3>
</td>
<td>
Second column
</td>
</tr>
</tbody>
</table>

Related

KnockoutJS - show accumulate value for each item

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>

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>

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.js click binding accessing model values

When alerting the property values inside the object I get a really weird alert message.
The alert call is located at where it saids ISSUE IS HERE *.
The alert I am receiving is https://flic.kr/p/m1CYkD
function c(){if0<arguments.length)returnc.equalityComparer&&c.equalityComparer(d,arguments[0])} ......
JS
$(function(){
ko.applyBindings(new ViewModelBooks());
});
function Book(title, checked_out_to, id)
{
this.title = ko.observable(title);
this.checked_out_to = ko.observable(checked_out_to);
this.id = ko.observable(id);
}
function ViewModelBooks(){
var self = this;
self.library = ko.observableArray();
self.checkOutBook = function(obj){
alert(obj.title); <--- ISSUE IS HERE ********************
};
// Query all books
var url = 'http://portal.internal.urs.org/tools_services/training_library/_vti_bin/listdata.svc/Book?$expand=CheckedOutTo';
$.getJSON(url, function(data){
for (var i = 0; i < data.d.results.length; i++){
var book = data.d.results[i];
var checked_out_to = null;
if (book.CheckedOutTo != null){
checked_out_to = book.CheckedOutTo.Name;
}
self.library.push(new Book(book.Title, checked_out_to, book.Id));
}
});
}
HTML
<tbody data-bind="foreach: library">
<tr><td data-bind="text: title"></td>
<td>
<span data-bind="ifnot: checked_out_to">
<button class="btn_checkout" type="button"
data-bind="click: $parent.checkOutBook">Check Out</button>
</span>
<span data-bind="if: checked_out_to">
<span data-bind="text: checked_out_to"> </span>
</span>
</td>
</tr>
</tbody>
Thanks
As obj.title is an observable (a function), you have to invoke it if you need its value.
Your code should be :
alert(obj.title()); // getter
obj.title('new value'); // setter
See doc

No value is coming in my partial view throught knockout js

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

Categories