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);
}
});
Related
I'm using a Web API to display values in HTML via Angular.
I have 4 attributes: Id, MovieName, Date and Cast. Cast is an array. I don't know how to handle the Cast attribute.
Controller.js
app.controller('MyController', function ($scope, MyService) {
$scope.getemploy = function () {
var promise = MyService.getMovies();
promise.then(function (pl) {
$scope.Movies= pl.data
},
function (error) {
$log.error('Some Prob', error);
});
}
GetMovies()--> will bring movie details(Cast attribute will have male, female lead names in the array).
HTML File :
<tr ng-repeat="mov in Movies">
<td>{{mov.Id}}</td>
<td>{{mov.MovieName}}</td>
<td>{{mov.Date}}</td>
<td>{{mov.Cast}}</td>
</tr>
But it's not working. I think I'll need some other way to handle Cast attributes, whether in html or angular.
My Json output for your reference:
[
{
"_movieId": 1,
"_moviename": "Olympus Has Fallen",
"_releaseDate": 2013,
"_cast": [
"Gerard Butler",
"Dylan McDermott",
"Aaron Eckhart",
"Angela Bassett"
]
}
Can anyone please help?
But its not working....
Because according to posted JSON your receive, HTML should be with different keys. Try this:
<tr ng-repeat="mov in Movies">
<td>{{mov._movieId}}</td>
<td>{{mov._moviename}}</td>
<td>{{mov._releaseDate}}</td>
<td>{{mov._cast.join(', ')}}</td>
</tr>
I used simple join method of array to render comma separated list of actors. If you need something more specific, you will need to make use of one more ngRepeat on mov._cast array (see Danny's answer).
I'm not sure what you mean by the Cast is not working, but I'm guessing it's just showing the JSON object in the HTML? You could show all the cast members in a list doing something like:
<tr ng-repeat="mov in Movies">
<td>{{mov.Id}}</td>
<td>{{mov.MovieName}}</td>
<td>{{mov.Date}}</td>
<td>
<ul>
<li ng-repeat="member in mov.Cast">{{ member }}</li>
</ul>
</td>
</tr>
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 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...
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.
I'm using Knockoutjs for the first time and I'm having trouble debugging because of my inability to log variables in console. I can see that my JS is loading properly in console, when I enter:
Home.TwitterFeedComponent I see an object returned. How do I use console.log in conjunction with knockout and subscribe?
var Home = Home || {};
var inheriting = inheriting || {};
Home.TwitterFeedComponent = function(attributes) {
if (arguments[0] === inheriting)
return;
Home.OnScreenComponent.call(this, attributes);
var component = this;
var recent_tweets = ko.observableArray();
var url = 'https://twitter.com/search.json?callback=?';
this.attributes.twitter_user_handle.subscribe(function(value) {
var twitter_parameters = {
include_entities: true,
include_rts: true,
from: value,
q: value,
count: '3'
}
result = function getTweets(){
$.getJSON(url,twitter_parameters,
function(json) {
console.log(json)
});
}
console.log(twitter_parameters);
});
};
Home.TwitterFeedComponent.prototype = new Home.OnScreenComponent(inheriting);
Home.TwitterFeedComponent.prototype.constructor = Home.TwitterFeedComponent;
I don't see the problem in your code, but if you want to log 'Observables', you have to log it as follows:
console.log(observableVar());
I'm a little unclear as to the exact scope of the question -- however, if like mine, this question is directed toward the use of console.log within your HTML.
Here is a little set of code that may help:
<div class="tab-content" data-bind="with: ClientSearch.selectedClient">
...
<table class="table table-striped table-condensed table-hover">
<thead></thead>
<tbody>
<!-- ko foreach: { data: _general, as: 'item' } -->
<tr>
<td data-bind="text: eval( 'console.log(\' le item \', item)' )"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
...
</div>
This code simply logs an item inside of a foreach to the console.
Hope this helps!