Imagine a component that renders an HTML table. The data from the table comes from remote JSON.
Another part of the component relies on the HTML table being fully rendered (with the JSON data).
On the component's init event, I retrieve the JSON and set the data that the component will use to render the table.
I can't use an afterRender hook to further process the table, because when the afterRender is fired, the table exists but without the JSON data.
I noticed that an afterRender hook outside the component works (the table is fully rendered), but then I'd break the encapsulation by running code that belongs inside the component.
I could maybe get the JSON synchronously, or perhaps a promise within a promise? How would I do the latter? What I mean is on the component's init hook, how do I create a promise which returns only when the promise inside it is returned?
Or how can I approach this the Ember way?
You can definitely chain the hell out of your promises.
var items = [];
this.set('items', items);
$.getJSON('/colors').then(function(results){
results.forEach(function(item){
item.color +=" is pretty";
});
return results;
}).then(function(prettyResults){
prettyResults.forEach(function(item){
items.pushObject(item);
});
});
http://emberjs.jsbin.com/OxIDiVU/724/edit
Super deep convoluted promises
new Ember.RSVP.Promise(function(resolve){
resolve($.getJSON('/colors'));
}).then(function(results){ // this isn't hit til the json is returned
results.forEach(function(item){
item.color +=" is pretty";
});
return new Ember.RSVP.Promise(function(resolve){
Ember.run.later(function(){
resolve(results);
}, 4000);
});
}).then(function(prettyResults){ // this isn't hit til the 4 second resolve is done
prettyResults.forEach(function(item){
items.pushObject(item);
});
});
http://emberjs.jsbin.com/OxIDiVU/725/edit
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.
I have a BackboneJS App where I fetch a bunch of collections. Now I want to apply some sort of loader to indicate that the collection is loading and the user gets to know that something is happening. So I want to use the .ajaxStart() and .ajaxStop()-method. So I was thinking about something like this:
this.artistsCollection.fetch(
$(document).ajaxStart(function () {
console.log('ajax start');
$('.someDiv').addClass('TEST');
}),
$(document).ajaxStop(function () {
console.log('ajax stop');
// stop doing stuff
})
);
Issue is that first time I trigger the .fetch() my console says ajax stop and the class is not applied!?!? Second time I trigger the .fetch() it works like it should and the class gets applied. Does anyone know whats the issue?
Please help anyone?
You're passing the returned result of adding the two event handlers with jQuery as parameters to the Collection fetch method. The Backbone Collection fetch method receives an options object which can include a success callback (see documentation).
I think if you move the listeners out of the method call it should work as you expect:
// Global AJAX listeners
$(document).ajaxStart(function () {
console.log('ajax start');
// do stuff
});
$(document).ajaxStop(function () {
console.log('ajax stop');
// stop doing stuff
});
this.artistsCollection.fetch();
I have a preloaded object graph of Backbone collections and models. To initialize my UI I need to make sure the collections are loaded, and then pull some item from them by ID using get(). I want to have a method that accepts a callback which is either called immediately if the collection is loaded, or gets delayed until the collection is loaded.
So far I have the following abomination of a mixin:
window.BackboneReady =
onReady: (cb)->
if #loaded_
console.log "Calling onReady immediately"
cb(#)
else
console.log "Scheduling onReady for later"
#once 'sync', =>
console.log "onReady fired in callback"
#loaded_ = true
cb(#)
however, it only works sometimes (I see the message "Scheduling onReady for later" but my event handler is never executed). Rant: it looks Backbone doesn't even have a basic signal variable to tell me whether the object is synced or not, which seems completely absurd.
What would be the sane way to accomplish this? I don't want to call fetch() every time I want to get() an object from the collection for my UI since this defeats the purpose of holding a preloaded object graph in the first place.
You can try this in your collection to use promises and solve your challenge
initialize: function(){
this.on("request", function(collection, xhr, options){
this.ready = xhr;
});
}
Then you can do
$.when(myCollection.ready).done(function(){
// do things to the collection that is ready
console.log(myCollection.get(5));
});
Or in your collection:
getIfLoaded: function(id){
if(this.ready.state === "resolved"){
return this.get(id);
}
else{
return null;
}
}
For more info on deferred and promises, take a look at http://davidsulc.com/blog/2013/04/01/using-jquery-promises-to-render-backbone-views-after-fetching-data/ and http://davidsulc.com/blog/2013/04/02/rendering-a-view-after-multiple-async-functions-return-using-promises/
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.
I'm using jQuery UI. I'm loading some content in a dialog box over AJAX. After inserting the content from the server, I need to make modifications to the document. I am using the .live() function on my link; I thought this would enable me to use Js after loading the content over ajax, but it's like the content I just loaded isn't a part of the document. Any help very much appreciated.
Are you adding the bindings (lives) in the success function of the ajax call?
If so I had the same issue, I'll try to explain what I figured out:
$.post('callURL', function(data){
// Let's say data returned from server is an ID of a div I have to hide
// by clicking on some_link
$('#some_link').live('click',function(){
$('#'+data).hide();
});
});
This won't work because the code inside the 'live' function is executed on click and at that time the 'data' value is gone.
To make it work I made a global variable 'ID' which I set in the success function and then called in the 'live' function again like this:
var ID;
$.post('callURL', function(data){
// Let's say data returned from server is an ID of a div I have to hide
// by clicking on some_link
ID = data
$('#some_link').live('click',function(){
$('#'+ID).hide();
});
});