Struggling with a bug that knockout.js me - javascript

I fear this is something as embarrassing as a typo, but since I´m stuck on this and quite desperate I´m willing to pay with pride. ;)
This is my case:
Task = function (data) {
var self = this;
self.TaskId = data.TaskId;
self.TaskName = ko.observable(data.TaskName);
}
ViewModel = function () {
var self = this;
self.Tasks = ko.observableArray();
self.SelectedTask = ko.observable();
}
$.getJSON("/myService/GetAllTasks",
function (tData) {
var mappedTasks = $.map(tData, function (item) {
return new Task(item)
});
self.Tasks(mappedTasks); // Populate Tasks-array...
});
self.newTaskItem = function () {
var newitem = new Task({
TaskId: -1,
TaskName: "enter taskname here"
});
self.Tasks.push(newitem); // THIS ONE CRASH
self.Tasks().push(newitem); // BUT SUBSTITUTED WITH THIS ONE IT RUNS ON...
self.editTaskItem(newitem);
};
self.editTaskItem = function (item) {
self.SelectedTask(item); // UNTIL TIL LINE WHERE IT CRASHES FOR GOOD...
self.showEditor(true); // makes Task-edior visible in HTML
};
I also hava an "self.SelectedTask.subscription" in my file, but leaving it out of the code makes no difference.
I also should mention that my database table is empty, so the getJSON returns no data to the mappedTasks, leaving self.Tasks() = [ ] (according to Firebug)
I have fixed the incorrectly closed tags in my code.
Part 2:
Decided after a while to redo my code from the starting point. It got me one step further.
The code now stops on the second of these lines (in "self.newTaskItem"):
self.Tasks.push(newitem);
self.SelectedTask(newitem); // Here it fails.
These two observables are connected in my HTML like this:
<select data-bind="options: Tasks, optionsText: '$root.TaskName', value: SelectedTask"</select>

It looks like your ViewModel() function never gets closed. Add a closing } to wherever you want that function declaration to end. It looks to me (based on your formatting) that you want this:
ViewModel = function () {
var self = this;
self.Tasks = ko.observableArray();
self.SelectedTask = ko.observable();
}
Additionally, you need to close your$.getJson call with a );:
$.getJSON("/myService/GetAllTasks",
function (tData) {
var mappedTasks = $.map(tData, function (item) {
return new Task(item)
});
self.Tasks(mappedTasks); // Populate Tasks-array...
});

I am not 100% sure what your problem is or what error you are getting but this is what I would do - change your Task = function to function Task -
function Task(data) {
var self = this;
self.TaskId = data.TaskId;
}
By saying Task = function without using a var in front of it you are registering Task in the global namespace, not a good idea. Same thing with your view model... Fix it if you can still...
self.newTaskItem = function () {
var newitem = new Task({
// Your Task is looking for a TaskId, not a TextBatchId
TaskId: 1
});
self.Tasks.push(newitem);
self.editTaskItem(newitem);
};
Also, you are creating a TextBatchId where I think your Task object is looking for a TaskId. Fix that, or if you are doing it on purpose for some reason please show your view code and give a better explanation of what is going wrong and what errors you see.

(assuming the unclosed stuff isn't present in your real code)
In Task, TaskId isn't an observable, so when you set SelectedTask to a particular task your editor fields won't properly update (it's a fairly common mistake to assume that the elements of an observableArray are themselves observable, but they aren't unless you explicitly make them so).

Related

Knockout pureComputed Not Working

I'm having a ridiculous time trying to handle addresses using Knockout. My structure is something like:
viewModel.buildings()[0].Address()...
Where Address is:
var Address = function () {
var self = this;
self.cAddr1 = ko.observable("");
self.cCity = ko.observable("");
...
self.cDisplay = ko.pureComputed(function () {
return self.cAddr1() + '<br>' + self.cCity() + ...;
}
self.AddressActions = new AddressActions();
}
Everything seems to work ok. Each building has an address and the observables are updated properly. The cDisplay also works correctly.
I am wanting to add another computed/observable/whatever that will call a function that is part of AddressActions when the address changes. I tried this, but the console.log never even gets hit which doesn't make any sense to me:
var Address = function () {
// Same as above...
...
self.triggerAddressVerify = ko.pureComputed(function () {
console.log('here');
self.cAddr1(); self.cAddr2(); self.cCity(); self.cState(); self.cZip();
self.AddressActions.VerifyAddress(self);
}
}
Any ideas why this isn't working?
Result
So I'm still new to knockout.js (obviously) but it works a little bit differently than I thought. I basically used the accepted answer but wrapped everything into a pureComputed. Here's what I ended up adding:
var Address = function () {
...
self.addressChangeEvent = ko.pureComputed(function () {
return self.cAddr1() + self.cAddr2() + self.cCity() self.cState() + self.cZip();
}
self.addressChangeEvent.subscribe(function () {
self.AddressActions.VerifyAddress(self);
}
}
it looks like you're looking for subscribe rather than computed
self.cAddr1.subscribe(function(){
self.AddressActions.VerifyAddress(self);
});
you can add a subscription for each variable you need an event for
The Knockout documentation for pure computed specifically says to not use it when you intend to perform some action (side effects).
You should not use the pure feature for a computed observable that is meant to perform an action when its dependencies change.
You can use a regular computed instead.
self.triggerAddressVerify = ko.computed(function () {
console.log('here');
self.cAddr1(); self.cAddr2(); self.cCity(); self.cState(); self.cZip();
self.AddressActions.VerifyAddress(self);
});
But note that this will run once initially as well as on future changes. If you only want to perform an action on future changes, your approach of subscribing to a pure computed is better.

Custom JQuery Plugin Method error

I've been working on writing a custom jquery plugin for one of my web applications but I've been running into a strange error, I think it's due to my unfamiliarity with object-oriented programming.
The bug that I've been running into comes when I try to run the $(".list-group").updateList('template', 'some template') twice, the first time it works just fine, but the second time I run the same command, I get an object is not a function error. Here's the plugin code:
(function($){
defaultOptions = {
defaultId: 'selective_update_',
listSelector: 'li'
};
function UpdateList(item, options) {
this.options = $.extend(defaultOptions, options);
this.item = $(item);
this.init();
console.log(this.options);
}
UpdateList.prototype = {
init: function() {
console.log('initiation');
},
template: function(template) {
// this line is where the errors come
this.template = template;
},
update: function(newArray) {
//update code is here
// I can run this multiple times in a row without it breaking
}
}
// jQuery plugin interface
$.fn.updateList = function(opt) {
// slice arguments to leave only arguments after function name
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var item = $(this), instance = item.data('UpdateList');
if(!instance) {
// create plugin instance and save it in data
item.data('UpdateList', new UpdateList(this, opt));
} else {
// if instance already created call method
if(typeof opt === 'string') {
instance[opt](args);
}
}
});
}
}(jQuery));
One thing I did notice when I went to access this.template - It was in an array so I had to call this.template[0] to get the string...I don't know why it's doing that, but I suspect it has to do with the error I'm getting. Maybe it can assign the string the first time, but not the next? Any help would be appreciated!
Thanks :)
this.template = template
Is in fact your problem, as you are overwriting the function that is set on the instance. You end up overwriting it to your args array as you pass that as your argument to the initial template function. It basically will do this:
this.template = ["some template"];
Thus the next time instance[opt](args) runs it will try to execute that array as if it were a function and hence get the not a function error.
JSFiddle

Wiring up Knockout with SignalR (Object doesn't support property or method)

I'm getting the following js error (on the line of code marked ###) when starting my app:
JavaScript runtime error: Object doesn't support property or method 'messages'
I've previously had this working without knockout, but am trying to add in knockout as I feel this will be easier to maintain in the long run. I suspect that as this is my first venture into both SignalR and Knockout, I've done something blindingly stupid, so any pointers please let me know. The mappedMessages data is fully populated, it's just when it tries to set self.messages it has the issue. Knockout 3.0.0, SignalR 1.1.3.
The full javascript code is below:
$(function () {
var messageHub = $.connection.messageHubMini;
function init() { }
function Message(data) {
this.Operator = ko.observable(data.Operator);
this.text = ko.observable(data.Text);
}
function MessagesViewModel() {
// Data
var self = this;
self.messages = ko.observableArray([]); //### message on this line
}
// Add a client-side hub method that the server will call
messageHub.client.updateMessages = function (data) {
var mappedMessages = $.map(data, function (item) { return new Message(item) });
self.messages(mappedMessages);
}
// Start the connection
$.connection.hub.start().done(init);
ko.applyBindings(new MessagesViewModel());
});
Thanks :)
You should use the viewModel object in the SignalR client methods. Currently, you're trying to use a variable called self, but that variable isn't available in that SignalR client method scope. I have updated your code into a version which I believe should solve your problem, doing as few changes as possible.
$(function () {
var messageHub = $.connection.messageHubMini;
function init() { }
function Message(data) {
this.Operator = ko.observable(data.Operator);
this.text = ko.observable(data.Text);
}
function MessagesViewModel() {
// Data
var self = this;
self.messages = ko.observableArray([]); //### message on this line
}
var viewModel = new MessagesViewModel();
// Add a client-side hub method that the server will call
messageHub.client.updateMessages = function (data) {
var mappedMessages = $.map(data, function (item) { return new Message(item) });
viewModel.messages(mappedMessages);
}
// Start the connection
$.connection.hub.start().done(init);
ko.applyBindings(viewModel);
});

Cannot add item to observableArray

This should be a pretty trivial question, but it's getting me crazy!
I't like step 1 of the tutorial, but I can't it to work.... so this is my code:
/* Model definition */
var PrivateSalesMenuModel = function () {
var self = this;
this.privateSales = ko.observableArray();
this.addPrivateSale = function (privateSale) {
var newPs = new PrivateSaleMenuItem(privateSale);
self.privateSales.push(newPs);
};
};
var PrivateSaleMenuItem = function (ps) {
this.title = ps.Description;
this.hasActual = ps.HasActual;
this.hasSale = ps.HasSale;
this.listaId = ps.ListaId;
this.isSelected = false;
};
/* end model definition*/
var privateSalesMenuModel = new PrivateSalesMenuModel();
ko.applyBindings(privateSalesMenuModel);
Pretty simple... I have an object that represent my model, that is a collection of others objects, called PrivateSaleMenuItem.
Problem is that addPrivateSale didn't work as expected. Somewhere in the code I do
privateSalesMenuModel.addPrivateSale(ps);
where ps is an object created by other JavaScript functions... anyway is exactaly the object I need in the constructor of PrivateSaleMenuItem, so it's consistency is not the problem.
The problem seems o be that self.privateSales.push(newPs); doesn't work... after that inocation, the number of privateSalesMenuModel.privateSales is still 0.
Why is that?
Edited
I put toghether an example in jsFiddle with this same exact code, and it works fine, so I suspect something in my page make the push method of observableArray stop working... how can I find out what it is?
ops... the link of jsfiddle http://jsfiddle.net/YBHf5/
It looks like you are mixing this and self in your code here:
var PrivateSalesMenuModel = function () {
var self = this;
this.privateSales = ko.observableArray();
this.addPrivateSale = function (privateSale) {
var newPs = new PrivateSaleMenuItem(privateSale);
self.privateSales.push(newPs);
};
};
Changing them to all use self fixes it like this:
var PrivateSalesMenuModel = function () {
var self = this;
self.privateSales = ko.observableArray();
self.addPrivateSale = function (privateSale) {
var newPs = new PrivateSaleMenuItem(privateSale);
self.privateSales.push(newPs);
};
};
Please see working fiddle here:
http://jsfiddle.net/YBHf5/1/
Problem solved.... it was a trivial problem indeeed, with a trivial solution: I just needed to push the external script declaration at the end of the page.
I had the script (in which the code provided on the question is present) at the top of the page, inside the body, but above anything else.
Just putting the script reference at the end of the body and everything works as expected again.
Sorry for wasting your time... but this little issue is very hard to find out, 'cause the reason is not clear at all while debugging

Variable scope in Javascript Object

I'm discovering the concept of "objects" in JavaScript. I'm making an RSS Parser, and I have an error (commented).
function MyParser (feed_url) { // Construct
"use strict";
this.feedUrl = feed_url;
this.pubArray = [];
if (typeof (this.init_ok) == 'undefined') {
MyParser.prototype.parse = function () {
"use strict";
var thisObj = this;
$.get(this.feedUrl, function (data, textStatus, jqXHR) {
if (textStatus == 'success') {
var xml = jqXHR.responseXML,
//lastBuildDate = new Date($(xml).find('lastBuildDate').text());
items = $(xml).find('item');
items.each(function () {
var pubSingle = thisObj.makeObj($(this).find('pubDate').text(),
$(this).find('link').text(),
$(this).find('title').text(),
$(this).find('description').text(),
$(this).find('encoded').text(),
$(this).find('commentRss').text(),
$(this).find('comments').last().text());
thisObj.pubArray.push(pubSingle);
});
console.log(thisObj.pubArray); // OK
}
}, 'xml');
console.log(this.pubArray); // Empty
return (this.pubArray);
};
MyParser.prototype.makeObj = function (pubDate, pubLink, pubTitle, pubDesc, pubContent, pubComCount, pubComLink) {
"use strict";
var pubSingle = {};
pubSingle.pubDate = new Date(pubDate);
pubSingle.pubLink = pubLink;
pubSingle.pubTitle = pubTitle;
pubSingle.pubDesc = pubDesc;
pubSingle.pubContent = pubContent;
pubSingle.pubComCount = pubComCount;
pubSingle.pubComLink = pubComLink;
return (pubSingle);
};
}
this.init_ok = true;
}
If you look at the console.log(), you'll see that the line // OK is outputting my array correctly.
But later, when returning from $.get, my array is empty.
Does anybody have an idea why, and how to correct that please?
This is not a problem with variable-scope. The problem here is that you're working with asynchronous flow and you're not thinking correctly the flow.
Let me explain:
When you do your .get, you fire a parallel asynchronous process that will request information from the browser, but your main program's flow keeps going, so when you get to your "return" statement, your array has not been filled yet with the response from your get method.
You should use your array from inside the get callback and not outside of it, since you can't guarantee that the array will have the information you need.
Does it make any sense?
Let me know!
Further explanation
According to your comments, you're still doing something like this:
var results = MyParser(feed_url);
//code that uses results.pubArray
And you cannot do that. Even though you're setting your "pubArray" inside your .get callback, you're trying to use pubArray right after you called MyParser and that's before the .get callback is called.
What you have to do, is call your next step on your program's logic from within the .get callback... that's the only way you can be sure that the pubArray is filled with proper data.
I hope that makes it clearer.
This is because your line
console.log(this.pubArray); // Empty
is being called directly after you issue your Ajax request; it hasn't had time to fetch the data yet. The line
console.log(thisObj.pubArray); // OK
is being called inside the Ajax callback, by which time the data has been fetched.
Thank you all, and particulary #Deleteman .
Here is what I did:
$.get(this.feedUrl, 'xml').success(function () {
thisObj.handleAjax(arguments[0], arguments[1], arguments[2]);
$(document).trigger('MyParserDone');
}).error(function () {
$(document).trigger('MyParserFailed');
});
Then, when i enter "HandleAjax", i'm back in my object context, so "this" refers to my object and the right properties. The only "problem" is that I have to set a listener (MyParserDone) to make sure the parsing is finished.

Categories

Resources