I'm new to knockout.js (and this is also my first stackoverflow post) and I'm now facing the following problem.
I'm not able to bind the data from web api to a ko.observablearray. Why is the length of this Announcements ko.observablearray always 0? The code works fine with client side data (by adding new announcements)..
Here's the JS-code:
var AnnouncementModel = function () {
var self = this;
self.AnnouncementText = ko.observable();
self.AllDepartmentsBool = ko.observable();
self.Editable = ko.observable(false);
self.Add = function () {
viewModel.Announcements.push(self);
viewModel.AnnouncementToEdit(new AnnouncementModel());
};
self.Delete = function () {
ajaxHelper(announcementsUri + self.ID, 'DELETE').done(
viewModel.Announcements.remove(self));
};
self.Edit = function () {
self.Editable(!self.Editable());
};
}
//The ViewModel
function AnnouncementsViewModel() {
var self = this;
self.InitialData = ko.observableArray();
self.Announcements = ko.observableArray();
self.AnnouncementToEdit = ko.observable(new AnnouncementModel());
self.error = ko.observable();
function getAllAnnouncements() {
ajaxHelper(announcementsUri, 'GET').done(function(data) {
self.InitialData(data);
});
};
getAllAnnouncements();
};
var viewModel = new AnnouncementsViewModel();
ko.applyBindings(viewModel, document.getElementById("announcements-container"));
function createAnnouncement(announcementDto) {
var announcement = new AnnouncementModel();
announcement.AnnouncementText = ko.observable(announcementDto.AnnouncementText);
announcement.AllDepartmentsBool = ko.observable(announcementDto.AllDepartmentsBool);
announcement.Editable = ko.observable(false);
return announcement;
}
var length = viewModel.InitialData.length;
for (i = 0; i < length; i++) {
var newAnnouncement = createAnnouncement(InitialData[i]);
viewModel.Announcements.push(newAnnouncement);
}
The HTML:
<div id="announcements-container" style="display: inline-block; float: right">
<ul id="announcements-list" class="newsticker" data-bind="foreach: Announcements">
<li>
<span data-bind="html: AnnouncementText"></span>
</li>
</ul>
#Html.Partial("_AnnouncementsModal")
</div>
The InitialData gets populated from the api as it should:
GOT IT WORKING! :
Thanks for the quick answers. I got the code working by iterating the data with .forEach(). Another problem was that the initialData didn't get populated in it's current scope so I edited the getAnnouncements function to work like this :
function getAllAnnouncements() {
ajaxHelper(announcementsUri, 'GET').done(function(data) {
data.forEach(function (entry) {
var newAnnouncement = createAnnouncement(entry);
self.Announcements.push(newAnnouncement);
});
});
};
This line is the likely culprit:
var length = viewModel.InitialData.length;
Remember that InitialData is a function. Functions have a length (it's their "arity", the number of formal arguments they have), but the observable function for an observable array's length isn't the array's length..
You probably wanted the length of the array inside it:
var length = viewModel.InitialData().length;
// -------------------------------^^
Your various calls to push on observable arrays work even though length doesn't because Knockout provides push (and several other things) on the observable array function, as James points out.
Similarly, this line:
var newAnnouncement = createAnnouncement(InitialData[i]);
probably wants to be using the array as well (and is missing viewModel. in front of InitialData).
So that whole section probably wants to be refactored a bit:
viewModel.InitialData().forEach(function(entry) {
var newAnnouncement = createAnnouncement(entry);
viewModel.Announcements.push(newAnnouncement);
});
or without forEach (but really, it's nearly 2016, and it's shimmable on obsolete browsers);
var data = viewModel.InitialData();
for (var i = 0; i < data.length; ++i) {
var newAnnouncement = createAnnouncement(data[i]);
viewModel.Announcements.push(newAnnouncement);
}
Side note: Your code (at least as it is in the question) was also falling prey to The Horror of Implicit Globals by not declaring the i that you use in that for loop. I've added a var above, but this is another reason for using forEach to loop through arrays.
You can also use EcmaScript 6 style enumeration as follows:
viewModel.InitialData().forEach(item => {
let newAnnouncement = createAnnouncement(item);
viewModel.Announcements.push(newAnnouncement);
});
Related
I want to return true if my property is a match in my search input. I managed to do that with my other properties from my JSON data, but in my nested data I cannot. Tried it with for loop and also just with if but without success.
this is how i get my json from DB:
db.query("One/docs").then(function(dataone){
var oness = [];
for(var i=0; i < dataone.rows.length; ++i) {
var x = new one();
x.fromJS(dataone.rows[i].value);
oness.push(x);
}
self.OneInfos(oness);
})
Maybe I'm forgetting something, I don't really know :P.
My Json data:
code 1 :
function one(){
var self = this;
self.AssemblyName = ko.observable();
self.Description = ko.observable();
self.Name = ko.observable();
self.Obsolete = ko.observable();
self.TypeName = ko.observable();
self.Properties = ko.observable();
self.Implements = ko.observable();
self.Implements.Interfaces = ko.observable();
self.IsInSearch = ko.pureComputed(function(){
var searchRegEx= App.instance.SearchRegEx();
if(!searchRegEx)
return true;
if(searchRegEx.test(self.TypeName())) {
return true;
}
if(searchRegEx.test(self.Name())) {
return true;
}
if(searchRegEx.test(self.Description())) {
return true;
}
//todo - look if there is a filter for interfaces
if (searchRegEx.test(self.Implements.Interfaces.TypeName())){
return true;
}
return false;
});
}
And here is my second solution with for loop (it's in the same line under "todo - comment"):
for(var i=0; i < Implements.length;++i) {
var Implement = Implements[i];
if (searchRegEx.test(self.Interfaces.TypeName)){
return true;
}
}
Notice: My implements are displayed with a select input
you don't need the if (self.Implements() && self.Implements().Interfaces).
var interfaces = self.Implements();
for(var i=0; i < interfaces.length;++i) {
var currentInterface = interfaces[i];
if (searchRegEx.test(currentInterface.TypeName)){
return true;
}
}
Modified the code from your answer above.
I think this works as it searches through your Implements and property TypeName.
Hope this helps
I'm not sure why your data mapping works, since fromJS doesn't seem to be in your one viewmodel. It is in ko.mapping however, so I'll just assume you're somehow accessing it correctly.
var x = new one();
x.fromJS(dataone.rows[i].value);
According to the knockout documentation, ko.mapping.fromJS works like so:
All properties of an object are converted into an observable. If an update would change the value, it will update the observable.
This means Implements is an observable. Implements.Interfaces would then be undefined, but Implements().Interfaces would return your array.
To search the TypeName values of this array, you'll have to use:
if (self.Implements() && self.Implements().Interfaces) {
var interfaces = self.Implements().Interfaces;
for(var i=0; i < interfaces.length;++i) {
var currentInterface = interfaces[i];
if (searchRegEx.test(currentInterface.TypeName)){
return true;
}
}
}
EDITS: Note that I've made some edits after our conversation in the comments. Seems that this question wasn't just about accessing data inside an observable, but about checking for undefined as well...
<html>
<body>
var el = document.getElementById("tab");
var tab = Table(el, data);
tab.showData();
tab.takeData();
var PieChart=drawPieChart(canvas);
</body>
</html>
<script>
function Table(el, data) {
...
...
return{
showData: function(){
...
...
}
takeData: function(){
var myData=new Array();
for (var i = 0; i < val2; i++) {
myData[i] = document.getElementById('polja' + i).value;
}
}
...
...
};
}
function drawPieChart(canvas){
...
...
return{
getmyData(
);
...
...
};
}
</script>
how can i get myData in function "drawPieChart" except making myData global variable? thx
i was thinking in html make somthing like this PieChart.getMyData(Table.takeData); or something like that
Based on your edit, you can do this:
function Table(el, data) {
var myData = new Array(); // Not global but accessible to every function that gets returned here
return{
// removed unnecessary code
takeData: function(){
for (var i = 0; i < val2; i++) {
myData[i] = document.getElementById('polja' + i).value;
}
},
function drawPieChart(canvas){
// Can access myData here
}
}
}
First, the problems with your code:
You have a block of JavaScript sitting inside <body>. This is not standard, not allowed, and will fail. Specifically, it will be displayed as text on the page, rather than being executed as JavaScript. All JavaScript must be located in one of the following places: an inline <script> tag, an external file that you load with a <script> tag, an event attribute such as onload="...", or an attribute that can run JavaScript, such as href="javascript:..." on <a> elements.
You have a syntax error in the object you're returning from Table(); there is no comma separator between the hash key/value pairs (showData() and takeData()). The comma is required.
You have some uninitialized variables, including val2, data, and canvas. You probably just excerpted your code to omit the relevant initialization, but you should try to present complete self-contained code samples when asking questions on Stack Overflow.
With regard to your question, the object tab you are returning from Table() looks an awful lot like an instance object of a class in any OO language. JavaScript supports the OO paradigm via the prototype pattern, so a sensible approach would be to make Table a full class by defining its prototype. If you do this, you can make tab a full instance of Table by creating it with the new operator, and then you can store the myData array as an attribute on tab. Here's a random demonstration based on your sample code:
http://jsfiddle.net/awytnngu/
HTML:
<div id="tab">
<input id="polja0" value="def1"/>
<input id="polja1" value="def2"/>
<input id="polja2" value="def3"/>
</div>
JS:
function Table(el,data) {
this.el = el;
return this;
}
Table.prototype.showData = function() {
// ...
};
Table.prototype.takeData = function() {
this.myData = new Array();
var val2 = this.el.children.length;
for (var i = 0; i < val2; ++i)
this.myData[i] = document.getElementById('polja'+i).value;
};
Table.prototype.drawPieChart = function(canvas) {
alert(this.myData);
};
var el = document.getElementById('tab');
data = 'whatever';
var tab = new Table(el,data);
tab.showData();
tab.takeData();
canvas = 'whatever';
var PieChart = tab.drawPieChart(canvas);
Just to throw in another possible approach, much simpler than the prototype solution, you can take an OUT parameter on takeData() and then pass it as an argument to drawPieChart():
http://jsfiddle.net/uv8bh6nj/
HTML:
<div id="tab">
<input id="polja0" value="def1"/>
<input id="polja1" value="def2"/>
<input id="polja2" value="def3"/>
</div>
JS:
function Table(el,data) {
return {
showData:function() {
// ...
},
takeData:function(OUT) {
OUT.myData = new Array();
var val2 = el.children.length; // closure
for (var i = 0; i < val2; ++i)
OUT.myData[i] = document.getElementById('polja'+i).value;
}
};
}
function drawPieChart(canvas,myData) {
alert(myData);
}
var el = document.getElementById('tab');
data = 'whatever';
var tab = new Table(el,data);
tab.showData();
var takeDataOUT = {};
tab.takeData(takeDataOUT);
canvas = 'whatever';
var PieChart = drawPieChart(canvas,takeDataOUT.myData);
I have a question similar to Bind text to property of child object and I am having difficulty properly creating the KO observable for a child object.
For example, I do an HTTP Get to return a JSON array of People, and the People array is inside a property called "payload". I can get the basic binding to work, and do a foreach on the payload property, displaying properties of each Person; however, what I need to do is add a "status" property to each Person, which is received from a different JSON, example
/api/people (firstname, lastname, DOB, etc.)
/api/people/1/status (bp, weight, height, etc)
I have tried binding to status.bp, and status().bp, but no luck.
The js example:
var TestModel = function (data) {
var len = data.payload.length;
for (var i = 0; i < len; i++) {
var elem = data.payload[i];
var statusdata = $.getJSON("http://localhost:1234/api/people/" + elem.id + "/status.json", function (statusdata) {
elem.status = statusdata;
data.payload[i] = elem;
});
}
ko.mapping.fromJS(data, {}, this);
};
var people;
var data = $.getJSON("http://localhost:1234/api/people.json", function (data) {
people= new TestModel(data);
ko.applyBindings(people);
});
2 important things I will need:
1) properly notify KO that "payload" is an array to key on ID property
2) make "status" an observable
Help!
[UPDATE] EDIT with working fix based on Dan's answer:
var TestModel = function(data) {
...
this.refresh = function () {
$.getJSON("http://localhost:1234/api/people", function (data) {
self.payload = ko.observableArray(); // this was the trick that did it.
var len = data.payload.length;
for (var i = 0; i < len; i++) {
var elem = data.payload[i];
$.getJSON("http://localhost:1234/api/people/" + elem.id + "/status", function (statusdata) {
// statusdata is a complex object
elem.status = ko.mapping.fromJS(statusdata);
self.payload.push(elem);
});
}
// apply the binding only once, because Refresh will be called with SetInterval
if (applyBinding) {
applyBinding = false;
ko.applyBindings(self);
}
}
I am still new to Knockout and improvements to the refresh function are most welcome. The mapping is still being reapplied each time.
You need to define an observable array and then push your data into it.
elem.status = ko.observableArray();
for (var i = 0; i < statusdata.length; i++) {
elem.status.push(statusdata[i]);
}
I can't tell what the full structure of the data is by the example. But if status is a complex object, you may what to give it its own model.
for (var i = 0; i < statusdata.length; i++) {
elem.status.push(new statusModel(statusdata[i]));
}
Here is the code:
http://jsfiddle.net/GKBfL/
I am trying to get collection.prototype.add to return a reference such that the final alert will display testing, testing, 123, testing. Is there a way to accomplish what I'm trying to do here?
HTML:
<span id="spantest">testing, testing, 123, testing</span>
JavaScript:
var collection = function () {
this.items = {};
}
collection.prototype.add = function(sElmtId) {
this.items[sElmtId] = {};
return this.items[sElmtId];
}
collection.prototype.bind = function() {
for (var sElmtId in this.items) {
this.items[sElmtId] = document.getElementById(sElmtId);
}
}
var col = new collection();
var obj = {};
obj = col.add('spantest');
col.bind();
alert(obj.innerHTML);
You problem is this line:
this.items[sElmtId] = document.getElementById(sElmtId);
This overwrites the object currently assigned to this.items[sElmtId] with the DOM node. Instead, you should assign the node to a property of that object:
this.items[sElmtId].node = document.getElementById(sElmtId);
That way, obj.node will always refer to the current node:
alert(obj.node.innerHTML);
DEMO
Side note: The problem with your fiddle is also that you execute the code when the DOM is not built yet (no wrap (head)), so it cannot find #spantest. You have to run the code once the DOM is ready, either no wrap (body), onDomRead or onLoad.
Creating a reference like you need is impossible in JavaScript. The closest thing you can get is either a nested or closed object, or just copying it over, like so:
var collection = function() {
this.items = {};
};
collection.prototype.add = function(sElmtId) {
return this.items[sElmtId] = {};
};
collection.prototype.bind = function() {
for(var sElmtId in this.items) {
var element = document.getElementById(sElmtId);
for(var x in element) {
this.items[sElmtId][x] = element[x];
}
}
};
var col = new collection();
var obj = {};
obj = col.add('spantest');
col.bind();
alert(obj.innerHTML);
But it won't be truly "bound". You'll have to use nested objects if you need that kind of functionality, and it will probably defeat the point of your syntactic sugar.
http://jsfiddle.net/GKBfL/7/
I'm trying to translate a PHP class into JavaScript. The only thing I'm having trouble with is getting an item out of an array variable. I've created a simple jsfiddle here. I cannot figure out why it won't work.
(EDIT: I updated this code to better reflect what I'm doing. Sorry for the previous mistake.)
function tattooEightBall() {
this.subjects = ['a bear', 'a tiger', 'a sailor'];
this.prediction = make_prediction();
var that = this;
function array_random_pick(somearray) {
//return array[array_rand(array)];
var length = somearray.length;
var random = somearray[Math.floor(Math.random()*somearray.length)];
return random;
}
function make_prediction() {
var prediction = array_random_pick(this.subjects);
return prediction;
}
}
var test = tattooEightBall();
document.write(test.prediction);
Works fine here, you are simple not calling
classname();
After you define the function.
Update
When you make a call to *make_prediction* , this will not be in scope. You are right on the money creating a that variable, use it on *make_prediction* :
var that = this;
this.prediction = make_prediction();
function make_prediction() {
var prediction = ''; //initialize it
prediction = prediction + array_random_pick(that.subjects);
return prediction;
}
You can see a working version here: http://jsfiddle.net/zKcpC/
This is actually pretty complex and I believe someone with more experience in Javascript may be able to clarify the situation.
Edit2: Douglas Crockfords explains it with these words:
By convention, we make a private that variable. This is used to make
the object available to the private methods. This is a workaround for
an error in the ECMAScript Language Specification which causes this to
be set incorrectly for inner functions.
To see the complete article head to: http://javascript.crockford.com/private.html
You never call classname. Seems to be working fine.
Works for me:
(function classname() {
this.list = [];
this.list[0] = "tiger";
this.list[1] = "lion";
this.list[2] = "bear";
function pickone(somearray) {
var length = somearray.length;
var random = somearray[Math.floor(Math.random()*length)];
return random;
}
var random_item = pickone(this.list);
document.write(random_item);
}());
Were you actually calling the classname function? Note I wrapped your code block in:
([your_code]());
I'm not sure what you're trying to accomplish exactly with the class structure you were using so I made some guesses, but this code works by creating a classname object that has instance data and a pickone method:
function classname() {
this.list = [];
this.list[0] = "tiger";
this.list[1] = "lion";
this.list[2] = "bear";
this.pickone = function() {
var length = this.list.length;
var random = this.list[Math.floor(Math.random()*length)];
return random;
}
}
var cls = new classname();
var random = cls.pickone();
You can play with it interactively here: http://jsfiddle.net/jfriend00/ReL2h/.
It's working fine for me: http://jsfiddle.net/YznSE/6/ You just didn't call classname(). If you don't call it, nothing will happen ;)
Make it into a self-executing function like this:
(function classname() {
this.list = [];
this.list[0] = "tiger";
this.list[1] = "lion";
this.list[2] = "bear";
function pickone(somearray) {
var length = somearray.length; //<---WHY ISN'T THIS DEFINED??
var random = somearray[Math.floor(Math.random() * length)];
return random;
}
var random_item = pickone(this.list);
document.write(random_item);
})();
var test = tattooEightBall();
document.write(test.prediction);
Should be:
var test = new tattooEightBall(); //forgot new keyword to create object
document.write(test.prediction()); // forgot parens to fire method
and:
this.prediction = make_prediction();
Should be:
this.prediction = make_prediction;