I am working on a table for a web application using MVC and knockout.js. I have experience doing web development but this is my first time using knockout. I currently have a table with 3 columns. The 2nd and 3rd are populated using a knockout function that displays the data. I tried to set up the first column the same way except using an image instead of text. I keep getting a broken image icon and an error in the brower's console.
The error the browser is giving me:
GET http://hostinfo/Sponsor/~PracticeAppImagesGWC.png 404 (Not Found)
This is my table:
<table id="sponsorTable">
<thead><tr>
<th></th><th id="sponsor">Sponsor</th><th id="description">Description</th>
</tr></thead>
<!-- Todo: Generate table body -->
<tbody data-bind="foreach: Sponsors">
<tr>
<td><img data-bind="attr: {src: Image}" /></td>
<td class="sTableInfo" data-bind="text: Name"></td>
<td class="sTableInfo" data-bind="text: Description"></td>
</tr>
</tbody>
</table>
This is my function for filling the table. (columns 2 and 3 fill properly)
function pageModel() {
var self = this;
self.Sponsors = ko.observableArray([]);
}
function Sponsor( _image, _name, _descrip)
{
var self = this;
self.Image = ko.observable(_image);
self.Name = ko.observable(_name);
self.Description = ko.observable(_descrip);
}
var viewModel = new pageModel();
viewModel.Sponsors().push(new Sponsor("~/Images/GWC.png", "name1", "info1"));
viewModel.Sponsors().push(new Sponsor("second image would go here", "name2", "info2"));
$(function () {
ko.applyBindings(viewModel);
})
I think something is escaping the slashes in the img source link, but I'm not sure and I can't figure out why.
UPDATE: my compiler is telling me this for the image tag: "Validation (HTML5): Element 'img' is missing required attribute 'src'."
The pushmust be on the observableArray itself, you need to remove the parenthesis:
var viewModel = new pageModel();
viewModel.Sponsors.push(new Sponsor("~/Images/GWC.png", "name1", "info1"));
viewModel.Sponsors.push(new Sponsor("second image would go here", "name2", "info2"));
Pass just image name from viewmodel, and static folder path can be append like this.
// in viewmodel
viewModel.Sponsors().push(new Sponsor("GWC.png", "name1", "info1"));
// inside table
<td><img data-bind="attr: {src:'/Image/'+ Image}" /></td>
Hope this helps...
Related
I have a table, which shows invoices, then a nested table that shows the individual checks made for those invoices. I'm using knockout and typescript to render these tables. I am able to get the invoices to show, however the checks table doesn't show the data. Here's the code so far:
<tbody class="nohighlight" data-bind="foreach: parent.bankDrafts">
<tr>
<td><span data-bind="text: CheckID"></span></td>
<td><span data-bind="text: CheckRunID"></span></td>
<td><span data-bind="text: VendorName"></span></td>
<td><span data-bind="text: CheckDate"></span></td>
<td><span data-bind="text: FormatCurrency(CheckAmount)"></span></td>
<td><span data-bind="text: Globalize.formatCheckRunApproveStatus(ApprovalStatusID)"></span></td>
</tr>
</tbody>
Here's the typescript:
namespace CheckRunApproval {
declare let searchParameter: string;
class SearchCheckRunModel {
public searchParameter = ko.observable<string>(searchParameter || null);
public checkRuns = ko.observableArray<CheckRunModel>(null);
public bankDrafts = ko.observableArray<BankDraftInfoModel>();
}
var model = new SearchCheckRunModel();
export function GetBankDrafts(data: CheckRunModel): void {
CheckRunServiceMethods.GetBankDrafts(data.CheckRunID())
.done(bankDrafts => ko.mapping.fromJS(bankDrafts, null, model.bankDrafts));
}
}
And here's the service call:
public static GetBankDrafts(checkrunID: number): JQueryPromise<BankDraftInfo[]> {
return CommonMethods.doAjax<BankDraftInfo[]>(
"/Corp/Checks/CheckRunApprovalWS.asmx/getBankDrafts",
JSON.stringify({ checkrunID }),
"GetBankDrafts"
);
}
Now the server call does reach the server side code, passing in the correct parameters and returning the list of checks I'm trying to show as part of the invoice. However, the table itself does not have any data.
My thinking is that it has something to do with the way I'm mapping the model to the view model. It could also be the way I've setup the table itself, with the correct knockout attributes, etc. Any help would be greatly appreciated.
Edit: changing parent.bankDrafts to $parent.bankDrafts() it fixed the issue.
You have a typo in your code. Use $parent.bankDrafts instead of parent.bankDrafts in a foreach binding.
I have, what I thought was a fairly straightforward knockout situation. I have a model that comes in from WebApi that has an array of things with a Success element. I need the value of success to determine what of the properties render. I've validated that all the data is coming down from WebApi ok but nothing but the table shell renders. There are no errors in the dev console.
The HTML
<div id="model1Wrapper">
<table class = "table">
<thead >
<tr >
<th >Stuff</th><th>Things</th>
</tr>
</thead>
<tbody data-bind = "foreach: $data.historyArray" >
<!--ko if: Success -->
<tr class = "success" >
<td data-bind = "text: $data.ThingA" > </td>
<td data-bind = "text: ThingB" > </td>
</tr>
<!-- /ko -->
<!--ko ifnot: Success -->
<tr class = "danger" >
<td colspan="3" data-bind = "text: ThingC" > </td>
</tr>
<!-- /ko -->
</tbody>
</table>
</div>
Example Model Data
[{
"ThingA": "A",
"ThingB": "B",
"ThingC": "C",
"Success": false
}, {
"ThingA": "A",
"ThingB": "B",
"ThingC": "C",
"Success": true
}]
This is monitoring a process that has feeds from several endpoints so I have multiple ViewModels on the page. So I framed up a rough example of how that is working elsewhere on the page.
That business
<script>
var sampleModelData = [{
"ThingA": "A",
"ThingB": "B",
"ThingC": "C",
"Success": false
}, {
"ThingA": "A",
"ThingB": "B",
"ThingC": "C",
"Success": true
}]
var viewModel1 = {
historyArray: ko.observableArray()
};
function onNewHistory(data) {
viewModel1.historyArray(data);
}
$(document).ready(function(){
ko.applyBindings(viewModel1, document.getElementById("model1Wrapper"));
onNewHistory(sampleModelData);
})
</script>
I had to mask of some of the speciffics but the gist is, the ajax call returns an array in the example. There is a function that is called to update the new data into the observable and I would expect the table to rerender, it does not.
Other deets
Sometimes there is no model data in the table so I load it and wait
for an update. All the other Viewmodels are loaded like this but this
is the only one with an array and the only one I'm having trouble
with.
I have tried taking out the if/ifnot business and that does not work.
Fiddler hates me and I have not been able to set up a clean version of this to try.
I leafed though some of the related questions and nothing seems to fit my issue. Or the example is much more complicated to apply.
Thanks!
The problem is in this code:
var viewModel1 = {
historyArray = ko.observableArray();
}
You're mixing the syntax for declaring objects with the syntax for code inside functions. When declaring an object, don't use = and ;. Instead use : and ,.
If you change the declaration to something like below, it should work.
var viewModel1 = {
historyArray: ko.observableArray()
}
Just adding another answer to this question in case someone comes across it in future. I had this issue and it was a result of initialising my observable array within the method. I didn't mean to do this (copy paste error) and it didn't produce any errors in the console so was difficult to trace.
For example:
LoadJSArrayIntoObservable(results) {
vm.validationResults = ko.observableArray(); <---- THIS IS INVALID.
vm.validationResults([]); <---- THIS IS WHAT I MEANT TO DO!!
$.each(results, function () {
try {
vm.validationResults.push(new ValidationResult(this));
}
catch (err) {
alert(err.message);
}
});
I'm in the process of replacing one hell of a lot of javascript/jquery code with knockoutjs and I'm trying to figure out the best way forward. I have no time to replace everything at the same time so I will have to integrate the knockout logic with the existing javascript...
Is there a way to populate a knockout view model from javascript which is not called from a data-bind attribute? Any help would be nice since I've not been able to find this anywhere else (at least not anything that worked).
I know what I'm mentioning here isn't the "correct" way of doing things, but I'm trying to migrate parts of the javascript code... Doing it all in one go isn't an option at the moment.
(using knockout 3.2)
Edit:
Typically the existing javascript does something like:
$('#productlist').append(productItemHtmlCode);
And I would rather have it do something like:
ViewModel.productList.push(productItemObject);
If I understand correctly, currently you have something like this:
<div id='myDiv'>
current status is: <span id='statusSpan'>Active</span>
</div>
with some corresponding javascript that might be something like:
function toggleStatus() {
var s= document.getElementById('statusSpan');
s.innerHTML = s.innerHTML == 'Active' ? 'Inactive' : 'Active';
}
And you want to change it so that the javascript is updating the viewmodel rather than manipulating the DOM?
var app = (function() {
var vm = {
statusText: ko.observable('Active'),
toggleStatus: toggleStatus
}
return vm
function toggleStatus() {
vm.statusText = vm.statusText == 'Active' ? 'Inactive' : 'Active';
}
}) ();
ko.applyBindings(app,document.getElementById('myDiv'));
And then the html would be
<div id='myDiv'>
current status is: <span id='statusSpan' data-bind="text: statusText"></span>
</div>
If that's what you're talking about, that's what Knockout is designed for. The javascript updates the viewmodel, knockout manipulates the DOM.
The example you give is easy to represent in Knockout.
the HTML:
<div>
<table data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: category"></td>
</tr>
</table>
</div>
and in the viewmodel:
vm = {
products: ko.observableArray(), // empty array to start
addProduct: addProduct
}
return vm;
function addProduct(id, name, category) {
products.push({id: id, name: name, category:category});
}
etc.
I'm new to the world of Knockout and JavaScript so this question might be a bit on the loose side..
I'm having trouble sending my binded data from my view to the controlling JavaScript function.
The HTML:
<tbody data-bind="foreach: deals">
<tr>
<td><span data-bind="text: Name"></span></td>
<td><span data-bind="text: TotalDeals"></span></td>
<td><span data-bind="text: TotalOpenDeals"></span></td>
<td><span data-bind="text: TotalYellowDeals"></span></td>
<td><span data-bind="text: TotalRedDeals"></span></td>
<td><span data-bind="text: TotalClosedTermDeals"></span></td>
<td> </td>
<td><span data-bind="text: BusinessToInstLegal"></span></td>
<td><span data-bind="text: LawyerTAT"></span></td>
<td> <a href="#" data-bind='click: $parent.CountryRep'>View Country Report</a></td>
</tr>
</tbody>
Once the data is binded, I want the user to be able to click the "View Country Rep" and the data to be sent to this method in the corresponding js file:
self.CountryRep = function (obj) {
// Fetching deals data
//var locate = "/data/getCountryDeals"
//datacontext.GetJson(locate, obj.Name,
data[0].Name = obj.Name;
data[0].TotalDeals = obj.TotalDeals;
data[0].TotalOpenDeals = obj.TotalOpenDeals;
data[0].TotalClosedTermDeals = obj.TotalClosedTermDeals;
data[0].TotalYellowDeals = obj.TotalYellowDeals;
data[0].TotalRedDeals = obj.TotalRedDeals;
data[0].BusinessToInstLegal = obj.BusinessToInstLegal;
data[0].LawyerTAT = obj.LawyerTAT;
PlotCountry(data);
}
However I am receiving this runtime error:
JavaScript runtime error: Object doesn't support property or method 'CountryRep'
I've tried a few different things, like declaring the function without the 'var' and as a normal function, using a different call in the html (View Country Report ) to no avail.
Any suggestions?
Update: So I've changed the everything as Raheen instructed below and now it does not give the error, however it doesnt seem like its going into countryRep as I've put a breakpoint there and when I click it just loads the home page. Any thoughts on why this is happening? The html is in a file called report.regional.html in the views folder and countryRep is in a js file called report.regional.js in the viewmodels folder.
You should change your function like this. It is already enough and no need to get separate data
var CountryRep = function (obj) {
self.PlotCountry(obj);
}
Also where is CountryRep function. I assume it should be $parent.CountryRep as it is in foreach
Call it like this
data-bind='click: $parent.CountryRep'
Edit
Your view model should be something like this
function test(){
var self = this
self.deals = ko.observableArray([])
self.CountryRep = function (obj) {
self.PlotCountry(obj);
}
}
EDIT
It is going on home page because you dont have any href attribute set
<a href="countryrep.html" data-bind='click: $parent.CountryRep'>View Country Report</a>
And
self.CountryRep = function (obj) {
self.PlotCountry(obj);
return true
}
I have a list of attachments on a page which is generated using a jQuery $.ajax call and Knockout JS.
My HTML looks like (this is stripped back):
<tbody data-bind="foreach: attachments">
<tr>
<td data-bind="text: Filename" />
</tr>
</tbody>
I have a function that gets the list of attachments which is returned as a JSON response:
$(function () {
getFormAttachments();
});
function getAttachments() {
var request = $.ajax({
type: "GET",
datatype: "json",
url: "/Attachment/GetAttachments"
});
request.done(function (response) {
ko.applyBindings(new vm(response));
});
}
My view model looks like:
function vm(response) {
this.attachments = ko.observableArray(response);
};
There is a refresh button that the use can click to refresh this list because over time attachments may have been added/removed:
$(function () {
$("#refresh").on("click", getAttachments);
});
The initial rendering of the list of attachments is fine, however when I call getAttachments again via the refresh button click the list is added to (in fact each item is duplicated several times).
I've created a jsFiddle to demonstrate this problem here:
http://jsfiddle.net/CpdbJ/137
What am I doing wrong?
Here is a fiddle that fixes your sample. Your biggest issue was that you were calling 'applyBindings' multiple times. In general you will call applyBindings on page load and then the page will interact with the View Model to cause Knockout to refresh portions of your page.
http://jsfiddle.net/CpdbJ/136
html
<table>
<thead>
<tr><th>File Name</th></tr>
</thead>
<tbody data-bind="foreach: attachments">
<tr><td data-bind="text: Filename" /></tr>
</tbody>
</table>
<button data-bind="click: refresh">Refresh</button>
javascript
$(function () {
var ViewModel = function() {
var self = this;
self.count = 0;
self.getAttachments = function() {
var data = [{ Filename: "f"+(self.count*2+1)+".doc" },
{ Filename: "f"+(self.count*2+2)+".doc"}];
self.count = self.count + 1;
return data;
}
self.attachments = ko.observableArray(self.getAttachments());
self.refresh = function() {
self.attachments(self.getAttachments());
}
};
ko.applyBindings(new ViewModel());
});
--
You may also want to look at the mapping plugin - http://knockoutjs.com/documentation/plugins-mapping.html. It can help you transform JSON into View Models. Additionally it is able to assign a property to be the "key" for an object... this will be used to determine old vs new objects on subsequent mappings.
Here is a fiddle I wrote a while back to demonstrate a similar idea:
http://jsfiddle.net/wgZ59/276
NOTE: I use 'update' as part of my mapping rules, but ONLY so I can log to the console. You would only need to add this if you wanted to customize how the mapping plugin updated objects.