I'm having a problem with Knockout. It is telling me that it can't parse a specific binding. If I don't include the binding in my HTML, the page compiles and I can run the event handler manually (in the console). Here is what the binding looks like:
<!-- ko foreach: problems -->
<li data-bind="css: {active: $root.current.number() == $data.number() }">
<a data-bind="click: $root.setCurrent( $data.number ), text: $data.number">
<!-- /ko -->
The error I get is the very unhelpful
"Unable to parse bindings.
Bindings value: click: $root.setCurrent( $data.number ), text: $data.number
Message: undefined is not a function"
Of course, $data is very clearly not undefined. If I remove the call to setCurrent, the bindings compile file, and I can run setCurrent in the console (and it works).
The setCurrent handler is defined in my view model as:
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
}
$('document').ready( function () {
$.getJSON("#{PracticalWriteR}", function (data) {
viewModel = new ViewModel(data);
pager.extendWithPage(viewModel);
ko.applyBindings(viewModel);
pager.start();
viewModel.setCurrent = function (num) {
viewModel.problems()[viewModel.current.number() - 1].response(viewModel.current.problem.response());
viewModel.problems()[viewModel.current.number() - 1].marked(viewModel.current.problem.marked());
var neu = viewModel.problems()[num - 1];
viewModel.current.number(num);
viewModel.current.problem.answers(neu.answers());
viewModel.current.problem.response(neu.response());
viewModel.current.problem.practical.key(neu.practical.key());
viewModel.current.problem.practical.value.question(neu.practical.value.question());
viewModel.current.problem.practical.value.solution(neu.practical.value.solution());
viewModel.current.problem.marked(neu.marked());
viewModel.current.problem.number(neu.number());
};
});
});
The idea is that setCurrent takes an int, which is a "question number" and changes first updates the current-current question, and then changes the current question to a new question (whose number is the original argument). Sorry for the noise here.
When you bind a function to an event in knockout you generally call the function without parenthesis (similar to how you would do using addEventListener).
Knockout automatically provides the current model object as the first parameter to the function, and then the actual DOM event that activated the binding as the second parameter.
Your code should work if you just change it to:
<a data-bind="click: $root.setCurrent, text: $data.number">
Then change your setCurrent function to accept the Object wrapping the number as its parameter.
You can modify this behavior by using an anonymous function if you want to alter the order or way in which parameters are passed... for your case you could do something like
<a data-bind="click: function(data, event) { $root.setCurrent($data.number) }, text: $data.number">
There is also yet another method, using the myFunction.bind(param1, param2, ...) syntax if you don't like the anonymous function.
See the knockout docs on click binding for more info: http://knockoutjs.com/documentation/click-binding.html
Related
I'm seeing the success callback but in the debugger Chrome Dev Tools, I'm still seeing the model when I type in this.model. I know that it's destroyed on the server side, but can you explain why it's still attached to the view? How do I get rid of it?
delete: function () {
this.model.destroy({success: function () {console.log("success in destroy");}});
debugger;
}
What you are seeing is correct. Looking at the documentation on model.destroy (or looking to the code) we can see it basically does two things:
HTTP DELETEs the server representation of the model
Removes the model from any containing collections
Note that nothing happens to the model itself or to any objects the model may be attached to.
We can see this behavior with a simple example:
var foo = new Backbone.Model({foo: 'bar'});
var foos = new Backbone.Collection([foo]);
var fooView = new Backbone.View();
fooView.model = foo;
foo.destroy({success: function () {
console.log('success');
}});
console.log(foo, foos, fooView);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
Note that nothing happens to foo or fooView.model after executing this code, although foos no longer contains an instance of foo.
Removing view.model
If you want to remove the model from the view you can leverage the success callback. Just change your view's delete method (from your question) to something like the following:
delete: function () {
this.model.destroy({success: function () {
delete this.model;
}.bind(this)});
}
Alternatively, since we know from the docs that the model will fire a "destroy" event, we can also listen for that event and fire a callback that deletes our model.
I have a big project written in ASP.NET I need to modify. It uses knockout.js to bind values:
$(document).ready(function () {
var model = new MyViewModel();
ko.applyBindings(model);
});
Afterwards, the bindig is done:
<span data-bind="text: myText"></span>
I now want to access all values that were bound. Unfortunately, I'm not very experienced with those frameworks.
With
console.log(model);
I get a huge output (extract):
MyViewModel{settings: [...]}
TypeSeriesSubscription: c()
barClick: (e, bar)
cancelSetting: ()
chartClick: (value)
chartUser: c()
charts: c()
[...]
With
model.myText
I get just undifined.
How can I access myText in order to just put its text out in a console?
You're scoping model to only exist inside of the ready function. Try this:
var model = new MyViewModel();
$(document).ready(function () {
ko.applyBindings(model);
});
Then you should be able to:
console.log(model.myText());
Since KO relies on an observable pattern, you need to use () after any property to see its current value.
Using backbone.js I'm trying to fetch a model from my server and based on that, render an underscore template. I first tried it without using the result of the api call using the following render function:
render: function(options) {
this.ticketId = options.ticketId;
var that = this;
var ticketModel = new TicketModel({'id': that.ticketId});
$.when(ticketModel.fetch()).done(function(){
console.log(ticketModel); // this outputs the correct model
});
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
this.$el.html(template);
},
This works perfectly well. So I tried using the result of the api call to render the template:
render: function(options) {
this.ticketId = options.ticketId;
var that = this;
var ticketModel = new TicketModel({'id': this.ticketId});
$.when(ticketModel.fetch()).done(function(){
console.log(ticketModel);
console.log($('#tab-content-template').html());
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
this.$el.html(template);
});
},
but unfortunately, this results in an error saying
Uncaugt TypeError: Cannot read property 'html' of undefined.
The weird thing is that it outputs the html in the console correctly resulting from console.log($('#tab-content-template').html());. The error I get is on the line which reads this.$el.html(template);
How can it be that it first is able to get the html(), and after that it says it can't find the property html? I'm totally stuck here.. :S
All tips are welcome!
Your issue is this no longer refers to what you think it refers to. in your code you have placed
var that = this;
this is a common pattern/trick to allow the closure to retain the context of "this" when being executed. Inside your closure "that" would now refer to what you think "this" should refer to.
Try changing "this" to "that"
$.when(ticketModel.fetch()).done(function(){
console.log(ticketModel);
console.log($('#tab-content-template').html());
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
that.$el.html(template);
});
I usally make use of jQuery's proxy function that ensures when a function is executed you can be confident of the context in which it is running
$.when(ticketModel.fetch()).done($.proxy(function(){
console.log(ticketModel);
console.log($('#tab-content-template').html());
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
this.$el.html(template);
},this));
oh your other question about why is $('#tab-content-template').html() working, this is because you are using JQuery directly which is in the global namespace so there for accessible where as $el is a property of your view so if you can't access your view you can't access the property.
See http://backbonejs.org/#Model-fetch - in options argument it accepts success and error callbacks:
ticketModel.fetch({
success: function(model, response, options) {
// render the template
}
});
Also if you need to use a context of current view object in this callback you can use Underscore/Lo-Dash _.bind() function to pass the context:
ticketModel.fetch({
success: _.bind(function(model, response, options) {
// Render the template using some method of the view:
this.renderMyTemplate(model);
}, this)
});
or just pass the method itself:
ticketModel.fetch({
success: this.renderMyTemplate,
error: this.handleFetchError
});
You don't need a $.when here, backbone now returns a promise for fetch call. Below code should work for you. Also consider compiling template outside render function. Compiling template is bit heavy task, should be cached once done.
var _this = this;
ticketModel.fetch().done(function () {
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
_this.$el.html(template);
});
I have the following ViewModel.
function PageSectionVM(pageSection) {
var self = this;
self.SectionName = ko.observable();
self.Markup = ko.observable();
self.update(pageSection);
}
and I have also created the update method as called in the above constructor function.
PageSectionVM.prototype.update = function (pageSection) {
var self = this;
pageSection = pageSection || {};
self.SectionName(pageSection.SectionName);
self.Markup(pageSection.Markup);
};
This is bundled off in its own file and I would like to reuse this VM in several pages. On one particular page I would like to 'extend' this viewmodel to include a new function. I have tried do this by adding a new function to PageSectionVM's prototype, like so.
PageSectionVM.prototype.tabName = function () {
var self = this;
return "#tab-" + self.SectionName();
};
If I then add this as a knockout binding statement, it returns the text of the function as opposed to the function result. I get the feeling I am missing something. If I add tabName as a computedObservable in the original viewmodel it works, but that means I am putting specific code for a single purpose in my 'general' viewmodel's code (something I'd like to avoid).
The knockout binding statement I am using is
<a data-bind="attr:{href: tabName}, text:SectionName"></a>
This sits inside a foreach binding on an observableArray of PageSectionVMs. The text property is fine, but the href ends up containing the literal text of the function, not its result.
Any help greatly appreciated.
If I then add this as a knockout binding statement, it returns the text of the function as opposed to the function result.
Of course it does. Knockout bindings work like this:
check if the bound value is an observable
if yes, unwrap it (i.e. "execute it")
convert to string what remains and use it in your view
NB: All observables are functions, but not all functions are observables.
That means you will get the function text if your binding looks like text: SectionName when SectionName() is a plain old function.
Because of the way observables work (w/r/t dependency tracking & this handling) you cannot use them in a prototype, they have to live in the instance.
That means:
either you put them in the instance (like #WayneEllery suggests)
or you call them yourself in the view (text: SectionName())
or you use ko.utils.extend() to extend pre-existing PageSectionVM instances with extra observables/computeds.
I don't understand why you have a problem using a computed. If you want to use prototype you can do it as follows:
I've also added url encoding and initilized the model.
function PageSectionVM(pageSection) {
var self = this;
self.SectionName = ko.observable(pageSection.SectionName);
self.Markup = ko.observable(pageSection.Markup);
self.TabName = ko.computed(this.getTabName, self);
};
PageSectionVM.prototype.getTabName = function () {
var self = this;
return "#tab-" + encodeURIComponent(self.SectionName());
};
http://jsfiddle.net/5vUhe/
Let's say I have a jquery plugin that has an onSelect attribute. The user sets it to a function, and when that function is called, this refers to the object the plugin is applied to. All is good.
If I want to write a plugin that wraps this plugin, in order to inject some code into the onSelect, I do something like this:
// Get whatever the user put in
var extOnSelect = options['onSelect'];
// delete the onSelect attribute
delete options['onSelect'];
// re-add onSelect as an anonymous function that calls my method and the user's
var options = $.extend({
'onSelect': function() { onSelect(); extOnSelect(); }
}, options);
// call the plugin that I am wrapping / injecting extra onSelect code into
$(this).externalPlugin(options);
Then it will pass in my own onSelect code, while preserving what the user entered.
The only problem is, within each of those two functions this no longer refers to the object, it now refers to I think the generic inline function.
What's the best solution to fix this?
Use apply and the arguments object:
extOnSelect.apply(this, arguments);
to call extOnSelect exactly like the current function.
You could use call, but then you would need to pass the event object (and other possibe arguments) explicitly.
I think you need to use call method of javascript functions.
var options = $.extend({
'onSelect': function() { onSelect.call(this); extOnSelect.call(this); }
}, options);