I want to pass an extra variable (the userid) with before rendering my backbone view. I am getting the the extra variable with a ajax request but because is asynchronous I think my page is being rendered before I get the variable. For simplicity lets say I have this in my backbone view :
PostsApp.Views.Post = Backbone.View.extend({
template: _.template($('#post-template').html()),
render: function(){
var newObject = this.model.toJSON();
$.ajax({
url:"/user",
success:function(result){
newObject.twittername = result.name; ;
}
});
this.$el.html(this.template(newObject));
}
});
I guess I can put this.$el.html(this.template(newObject)); in the callback but than 'this'refers to something else.. Can anyone think of a work around for this?
Or is it completely very bad to send such a request in the render function..
You are correct in your assumption. It will not render correctly.
The simple fix
Just like the more general case, you can perform the actual rendering inside the success callback.
render: function(){
var newObject = this.model.toJSON();
var that = this; // to fix that `this` refers to in the callback
$.ajax({
url:"/user",
success:function(result){
newObject.twittername = result.name; ;
that.$el.html(that.template(newObject));
}
});
}
The better fix
What I'd do is:
Have the twitter name as a part of the model
fetch it from the model (maybe even with a restful .fetch
listen to change events in the view, and call render on such events.
This is because the view should not be responsible for changing model data in Backbone. It's mixing "business logic" with presentation and it can get ugly pretty fast.
[I think this example 2 from Addy Osmani's "Backbone Fundamentals" should give you a general idea on how this sort of structure is laid out.
Related
I'm trying to experiment here. I want to build a component that auto populates some data from an ajax request after mounting. Something like this:
var AjaxComponent = React.createClass({
getInitialState: function() {
return {
data: {}
};
},
render: function() {
return (
<div>
{this.state.data.text}
</div>
);
},
componentDidMount: function() {
makeAjaxResquest(this.props.url).then(function(response){
this.setState({
data: response.body // or something
});
}.bind(this));
}
});
With that example component, I'd use <AjaxComponent url="/url/to/fetch" /> to display the content.
Now, what if I'd like to access different bits of data from children elements? Can I do something like this?
<AjaxComponent url="/url/to/fetch">
<div>
<header>{RESPONSE.title}</header>
<div>
{RESPONSE.text}
</div>
</div>
</AjaxComponent>
No problem if it doesn't render anything before the ajax request ends. The thing is how could I pass the data for children to render, not as props. Is it possible?
I had a similar scenario where I had similar Components that would query data from different APIs. Assuming you know the expected response from a given API, you could do it the same way perhaps.
Essentially make a generic Component where it props functions as an "API" of sorts, then define different types of sub components and their associated render function.
For example:
In widget, you then do something like this, where widgets is just a plain javascript file with a bunch of functions:
componentDidMount: widgets[type].componentDidMount(),
render: widgets[type].render().
In widgets, it would be like this:
var widgets = {
widget1: {
componentDidMount: function () {
//Ajax call..
},
render: function() {
//How should I draw?
}
},
widget2: //Same format, different functions
Then in some parent component you simply go
< Widget type="widget1" \>
or whatever.
There are a couple weird things about this that probably don't sit right with React. First off, you should take state all the way up to the top-level component, so I wouldn't do my ajax calls in componentDidMount...I'd more likely get the data I want for the widgets I want to render at a higher level, then pass that in as a prop too if it won't change until I make another API call (thinking Flux style flow here). Then, just pass in the data as a prop as well and just specify the render functions:
< Widget data={this.state.data[0]} type=widget1 />
The "gotcha" here is that you are making an assumption that whatever is in this data prop will match what you need in the widget type. I would pass in an object, and then validate it all in the render function etc.
That's one way. Not sure if it's valid, I'm sure someone who knows more could pick it apart but it suited my use case and I now have a library of similar components that I can selectively render by passing in data and a type, then looking up the appropriate render function and checking to make sure the data object contains everything I need to render.
When I load a Partial View with some unobtrusive AJAX I also need the URL to change accordingly.
It would be okay if I had to do the work in the jQuery's done() callback.
But it's not possible right, wihtout loading something new. So the only choise I have is to load a View?
$.ajax({
//some jquery ajax call properties
success: function(){
location.hash = 'your_unique_value';
}
})
or use history api window.history.pushState('page2', 'Title', '/page2.php');
Docs
I'm trying to use Backbone with REST API:
Here the code
My model:
var PagesModel = Backbone.Model.extend({
idAttribute: 'Guid',
initialize: function () {
this.on('remove', this.destroy);
},
urlRoot: '/api/pages'
});
Collection:
var PagesCollection = Backbone.Collection.extend({
model: PagesModel,
url: '/api/pages'
});
View:
var PagesView = Backbone.View.extend({
el: '#pages',
events: {
'click .removePage': 'remove',
},
initialize: function (collection) {
this.collection = collection;
this.collection.fetch();
this.template = $('#pages-template').html();
this.collection.bind('change reset', this.render, this);
},
render: function () {
var template = _.template(this.template);
$(this.el).html(template({ pages: this.collection.toJSON() }));
return this;
},
remove: function (e) {
e.preventDefault();
var id = $(e.currentTarget).closest('ul').data("id");
var item = this.collection.get(id);
this.collection.remove(item);
$(e.currentTarget).closest('ul').fadeOut(300, function () {
$(this).remove();
});
}
});
And here I'm starting up application:
$(function () {
var pagesCollection = new PagesCollection();
var pagesView = new PagesView(pagesCollection);
});
I'm clicking or Remove and in Network inspector see this link
http://localhost:54286/backbone/function%20()%20%7B%20%20%20%20%20%20var%20base%20=%20getValue(this,%20'urlRoot')%20%7C%7C%20getValue(this.collection,%20'url')%20%7C%7C%20urlError();%20%20%20%20%20%20if%20(this.isNew())%20return%20base;%20%20%20%20%20%20return%20base%20+%20(base.charAt(base.length%20-%201)%20==%20'/'%20?%20''%20:%20'/')%20+%20encodeURIComponent(this.id);%20%20%20%20}
instead of /api/pages/{guid}.
What I'm doing wrong?
I still haven't figured fully why, but you can make it work by destroying your model after the end of its removal (Backbone does one last thing after triggering the remove event: destroy the collection's reference in the model).
But what's even better, is using directly the destroy function on the model, it will remove it from the collection automatically (use {wait: true} if needed).
Edit:
Finally managed to locate the source of the problem. It's rather simple in fact. To override the model's url (calculated with urlRoot but that doesn't matter), you can pass Model#destroy a url option when calling Backbone.sync (or something that'll call it).
Now you're thinking "but I don't!". But you do. The listener (Model#destroy in your case) is given 3 arguments. Model#destroy will take the first one (the model itself) as options.
And here's the fail (I think Backbone needs a patch to this): giving an url option to Backbone.sync is the only time _.result in not used to calculate the url. So you find yourself having as url the url property of your model, which is the function you see in your call.
Now, for a quickfix:
this.on('remove', this.destroy.bind(this, {}));
This will ensure the first argument of your Model#destroy call is {} (as well as binding the context).
Bear with me a little longer.
Now, if you're still willing to call Collection#remove before destroying your model, here's a little hack: because (as I stated above) the remove event is triggered before Backbone makes sure to remove the collection's reference in your model, you don't need the urlRoot property in your model. Indeed, the model won't be in the collection anymore, but Backbone will still take the collection's url into account to get the model's url (as the reference is still there).
Not a definitive answer, but just going by the code in your question and the backbone.js documentation, the problem may be that you named your method remove and this is getting in the way of the remove method in Backbone.View.
http://backbonejs.org/#View-remove
Update:
It also looks like the output you see in the network inspector is that the definition of the Backbone.Model.url function is being appended. Meaning url is not being properly called (Maybe the () is missing by the caller?). Are you overriding Backbone.sync anywhere in your application?
Currently playing about with KnockoutJS. Just trying to update an observable array from a ajax/json feed (using twitter) in this example.
It seems to lose scope of what "this" is when trying to update my observable array (currentTweets). I've tried adding bind to various places but no such luck.
The error I get is: Uncaught TypeError: Cannot call method 'push' of undefined
I'm sure I am doing something stupid, here it is in action (not much to look at!)
http://jsbin.com/oyuteb
I've read a lot about Knockout mapping but don't feel confident enough to take that on yet!
So, any help or guidance would be fab.
Thanks
The simplest way to solve this is to proxy your viewmodel's "this" into another variable so it is available inside handlers. When jquery ajax calls the success handler, the context is different so this refers to something else.
So you would have
function twitterViewModel() {
var self = this;
this.currentTweets = ko.observableArray([]);
...
this.getTweets = function(){
$.ajax({
dataType: 'jsonp',
url: 'http://search.twitter.com/search.json?callback=?&q='
+ this.searchTerm() + '&rpp=50',
success: function (data) {
$.each(data.results, function(i,tweet){
self.currentTweets.push({'keywords' : 'bugger'});
});
}
});
}
}
You can eliminate the bind calls and use self instead.
Hope this helps.
Using MVC2 I have created a form using the Ajax helper in a view. The form posts to a controller which binds to a model object. A PartialViewResult is returned by the controller and the HTML gets updated into a div. So far, so good.
I now need to submit the same form and return the results in a generated file for the user to download. Obviously I don't want the file contents going into my div.
Is there an elegant way to handle this situation without having to hack it to bits? I'm fairly new to MVC / AJAX and it's still a point of confusion for me.
You may not use ajax call to download files. Following links may help you to do what you are trying to do
JQuery Ajax call for PDF file download
http://forums.asp.net/t/1683990.aspx/1
OK, so I couldn't find any simple solutions anywhere so I came up with my own. I remove the Ajax event handlers from the form when I want the download, put them back when I want the Ajax. I'm guessing there's a more elegant way to do this, as this feels like a 'clever trick'. I'm open to better suggestions but so far this is my preferred method.
Reference ToggleAjax.js on my page:
var ToggleAjax = function ($, form) {
var onclick = form.onclick,
onsubmit = form.onsubmit;
$('input[class*="ajax-enabled"]').click(function () {
form.onclick = onclick;
form.onsubmit = onsubmit;
});
$('input[class*="ajax-disabled"]').click(function () {
form.onclick = function () { };
form.onsubmit = function () { };
});
};
Then I call ToggleAjax on my page and pass in the form:
$(function () {
ToggleAjax($, $('form')[0]);
});
And of course I add the class ajax-enabled or ajax-disabled to the input controls.