For example I have the following server routes set up for my user entity:
GET /users/ // gets collection of users
GET /users/:id // gets user :id
GET /users/me // gets the current user
At the beginning of my app I want to get the current user from the server and store it... Something along the lines of:
App.addInitializer(function () {
$.get('/users/me')
.done(function processCurrentUser (userJson) {
App.user = new User(userJson);
});
});
My question is where this API call should actually reside. Would it be better to have something along the lines of:
App.addInitializer(function () {
App.user = new User();
App.user.fetchMe(); // performs the api call above
});
Or should I be doing something inside of a controller?
Thanks for the help!
When doing a fetch, I always worry about how its asyn behavior is going to affect the components that depend on that data. If there are no downriver components that will need the data before it can be reasonably expected to return, then there's technically nothing wrong with your approach.
There is, however, another possible way of loading your globals. What I often do (and for a user's list, too, it so happens) is bootstrap the data to the initial load page. I generally load it on the window variable. So for your example, in your backend template,
<script>
window.globals = {};
window.globals.currentUser = #Html.Raw(Json.Encode(ViewBag.User))
</script>
Of course, you can replace #Html.Raw(Json.Encode(ViewBag.User)) (we use C#) with your favorite backend model.
Then in your app start you're guaranteed to have the models:
App.addInitializer(function () {
App.user = new User(window.globals.currentUser);
});
Related
I have a model "User" calling an API with an ID returning information such as desired pagination, language, role etc.
When I perform a fetch on the model's instance, it works fine. I know I need to wait for the fetch to be finished, but how do I keep the variables I'd potentially define on success callback visible to the rest of application?
var user = new App.Models.User();
user.fetch().done(function() {
//when I define fruit_list here, it's not visible to the outside world
});
// fruit_list view uses the information retrieved from the user model (pagination), which falls into the default because fetch wasn't performed yet
var fruit_list = new App.Views.FruitList();
Any thoughts? What is the best practice for storing global user information?
Many thanks!
If visibility to outside world is your concern, this can be one option.
You already have a global variable App declared for your application. You can create a separate namespace for model and view instances as well. For e.g.
App.models = {
user : new App.Models.User()
};
App.views = {
fruit_list : new App.Views.FruitList()
};
App.models.user.fetch().done(function() {
// do your stuff with 'user' model and 'fruit_list' view
// App.views.fruit_list
});
First off, some background
My client has a kind of a "split-view", meaning- a side-panel displaying a list of objects and a main view displaying the selected object's details. Every time the user clicks on an Object in the list, a Backbone's route is called to navigate to the id which updates a "selected" property on the Session, what causes the main view to update- pretty standard stuff.
The problem
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
My goal is that every time an Object is created, the list and the main view will be instantly updated to reflect the newly added Object. To achieve this I created a Meteor.method, create(), that uses Collection.insert and returns the id so I can use it with my Route. The method is shared across the client and server and is being called from within a template's event handler.
My first try was to store the returned id in a variable in the event handler and update the Route in the next line; For some reason, that didn't work because the method returned an undefined value. So I tried a different approach, instead of returning the id, I used it within the method to update the Route directly (if Meteor.isClient of course). That didn't work either because the id returned by Collection.insert in the client's version of the method was different from the one in the server's version.
First approach
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = Meteor.call('create');
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});
Second approach
Meteor.methods({
create: function () {
var ObjectId = Objects.insert({name:'test'});
if(Meteor.isClient){
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
}
});
If anyone knows what's going on and can give me some directions that would be great.
Any different approaches to the problem or suggestions would be much appreciated as well.
Thanks
Update
So I tried #Pent's suggestion and I got the same result as with my second approach. For some odd reason Meteor decides to ignore my id (created with Random.id()) and inserts the object with a different one.
So I tried another approach, I used just a simple string value instead of Random.id() and voila - it worked. Riddle me that.
Answer updated:
This will be both a client and server method:
Meteor.methods({
create: function () {
var id = Random.id();
Objects.insert({_id: id, name:'test'});
if(this.isSimulation) {
appRouter.navigate("object/id/" + id, {trigger:true});
}
}
});
You can view a similar pattern from Meteor's party example: https://github.com/meteor/meteor/blob/b28c81724101f84547c6c6b9c203353f2e05fbb7/examples/parties/model.js#L56
Your problem is coused by the fact that remote methods, i.e. those which will be called on the server, don't simply return any value. Instead, they accept a callback that will be used to process the returned value (see docs). So in your first example you should probably do something like this:
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
Meteor.call('create', function (error, result) {
if (!error)
appRouter.navigate("object/id/" + result, {trigger:true});
});
}
});
You also said:
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
I think that in this case you should definitely wait for server response. Note, that there is no chance you get the correct object id unless this is given to you by the server.
One possible way to get around this issue is to create a local (client-side) collection:
// only on client
var temporary = new Meteor.Collection(null); // null name
in which you could store your "temporary" newly created objects, and then save them to the "real" collection after the user clicks the save button. You could implement your router to respond to urls like object/new/* to get access to these objects before they're saved to your database.
The correct answer for this question is defining a client side method that's responsible for creating the unique id (preferably using Random.id() ) and calling the Meteor.methods' create(). That way, you can have the id available immediately without waiting for the server to generate one. The trick here is to generate the id outside of the Meteor.method so that the id generation happens only once for both the stub and the actual server method.
create = function(){
var id = Random.id();
Meteor.call('create', id);
return id;
}
Meteor.methods({
create: function (id) {
Objects.insert({_id: id, name:'test'});
//more code...
}
});
//and in the Template...
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = create();
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});
ya'll I have a bit of a structural/procedural question for ya.
So I have a pretty simple ember app, trying to use ember-data and I'm just not sure if I'm 'doing it right'. So the user hits my index template, I grab their location coordinates and encode a hash of it (that part works). Then on my server I have a db that stores 'tiles' named after there hash'd coords (if i hit my #/tiles/H1A2S3H4E5D route I get back properly formatted JSON).
What I would like to happen next, if to display each of the returned tiles to the user on the bottom of the first page (like in a partial maybe? if handlebars does that).
I have a DS.Model for the tiles, if I hard code the Hash'd cords into a App.find(H1A2S3H4E5D); I can see my server properly responding to the query. However, I cannot seem to be able to figure out how to access the returned JSON object, or how to display it to the user.
I did watch a few tutorial videos but they all seem to be outdated with the old router.
Mainly I would like to know:
1. Where does the information returned by App.find(); live & how to access it?
2. what is the 'correct' way to structure my templates/views to handle this?
3. how should I pass that id (the hash'd coords) to App.find? as a global variable? or is there a better way?
the biggest problem(to me) seems to be that the id I search by doesn't exist until the user hit the page tho first time. (since its dynamically generated) so I can't just grab it when the page loads.
I can post a fiddle if required, but I'm looking for more of a conceptual/instructional answer rather then some one to just write my code for me
I'm still learning a lot with Ember as well, but this is my understanding. When you follow the guides and the tutorials out there, you'll have something like this:
App.TileController = Ember.ObjectController.extend();
App.TileRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Tile.find(MYHASH));
}
});
What it does is set the special content object to the result. So since we're declaring an object controller, and calling find with a parameter, it knows that a single result is expected. So a view & template that follow the naming convention of Tile will be loaded. And in there you can access properties on the Tile object:
<p>{{lat}}</p><p>{{lng}}</p>
I have to admit that this feels a bit mystical at times. The core to it is all in the naming convention. You need to be pretty specific in how you name all your various controllers, routes, etc. Once that's nailed down, it's a matter of binding what data you want to the controller's content.
1) Aside from the generic answer of "in memory", the .find() calls live where ever you return it to. Generally speaking, this is meant to be set on a 'content' property of a controller.
2) I more or less answered this, but generally speaking you take the name of your route, and base it off that. So for a route TileRoute, you have:
TileController = Ember.ObjectController.extend
Tile = DS.Model.extend
TileView = Ember.View.extend
tile.handlebars
I generally store all my handlebars files in a templates/ folder. If you nest them deeper, just specify the path in your view object:
App.TileView = Ember.View.extend({
templateName: "tiles/show"
});
3) This really depends on your app. Generally speaking its better for the id to be either obtained from the URL, or constructed locally in a function. Since you are encoding a hash, i imagine you're doing this in a function, and then calling find. I do something a bit similar for an Array controller.
I don't know at what point you are generating a hash, so let's say it's onload. You should be able to generate the hash just in the setupController function.
App.TileRoute = Ember.Route.extend({
generateHashBasedOnCoords: function() {
// ...
},
setupController: function(controller) {
var MYHASH = this.generateHashBasedOnCoords();
controller.set('content', App.Tile.find(MYHASH));
}
});
I hope that helps.
I believe that you can make use of the data binding in ember and basically have an array controller for tiles and set the content initially to an empty array. Then we you get back your response do a App.find() and set the content of the tiles controller with the data that is returned. This should update the view through the data binding. (Very high level response)
The data itself is stored in a store that is setup with ember data. You access it with the same method you are using the model methods App.Tile.find() ect. It checks to see if the data that is needed is in the store if so it returns the data otherwise it makes a call to the api to get the data.
I'm working on a large AngularJS app in which I am trying to encapsulate all my Ajax code into various services which the controllers get data from. The problem revolves around needing to know the status of any ajax calls and displaying the correct information to the user. There could be no data found, data currently loading, or an error that has occurred preventing data from being loaded. The user needs to be shown a loading message, a "no data found" message, or an error message.
Let's say I have a ProjectService. Ideally if there was a method called getAllProjects it would return an array of projects. But that way I have no idea what is happening with the server communication.
So how to I let the controller know if data is loaded, loading, or an error has occurred? The best way I can come up with is using callbacks like in the pseudo code below. Is there any better way to accomplish such a thing or anything I may be overlooking?
Thanks.
app.controller( "ProjectController", function( $scope, ProjectService ){
// Set the initial / default status
$scope.loadStatus = "loading";
// Return an empty array initially that will be filled with
// any data that is returned from the server
// The callback function will be executed when the ajax call is finished
$scope.projects = ProjectService.getProjects(function( status ){
// Alert the controller of a status change
setStatus( status );
});
function setStatus( ){
$scope.loadStatus = status;
// ... update the view or whatever is needed when the status changes....
}
});
app.service( "ProjectService", function( $resource ){
return {
getAllProjects: function(){
// ... load and return the data from the server ...
}
};
});
In our codebase we've just been doing
$scope.flags.loading = true;
$http(...).success(function(){
$scope.flags.loading = false;
});
Yes, this is sort of simplistic, but not all queries require a loading overlay (such as during pagination or refreshing). This is why we have opted not to simply use a decorator.
However, lets say you want to, I can think of a few ways of doing this. Lets say you're like us and keep your flags together in an object. Then you can use associations to your advantage:
MyService.flags = $scope.flags
... (inside the service) ...
this.flags.loading = true/false;
By establishing a reference as a property of the service, you can do all the state toggling from within the service, and avoid cluttering your controller. Again though, this might create the possible drawback of having 2 or more close-together queries conflicting (first query finishes and removes the loading state before the second one completes).
For this reason we have been find with setting the flag. We don't really check for 'loaded' we just check for data or use success callbacks.
I have a collection that can potentially contain thousands of models. I have a view that displays a table with 50 rows for each page.
Now I want to be able to cache my data so that when a user loads page 1 of the table and then clicks page 2, the data for page 1 (rows #01-50) will be cached so that when the user clicks page 1 again, backbone won't have to fetch it again.
Also, I want my collection to be able to refresh updated data from the server without performing a RESET, since RESET will delete all the models in a collection, including references of existing model that may exist in my app. Is it possible to fetch data from the server and only update or add new models if necessary by comparing the existing data and the new arriving data?
In my app, I addressed the reset question by adding a new method called fetchNew:
app.Collection = Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
// added this conditional block
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) {
collection.trigger('reset', collection, options);
}
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is pretty much identical to the standard fetch() method, except for the conditional statement checking for item existence, and using add() by default, rather than reset. Unlike simply passing {add: true} in the options argument, it allows you to retrieve sets of models that may overlap with what you already have loaded - using {add: true} will throw an error if you try to add the same model twice.
This should solve your caching problem, assuming your collection is set up so that you can pass some kind of page parameter in options to tell the server what page of options to send back. You'll probably want to add some sort of data structure within your collection to track which pages you've loaded, to avoid doing unnecessary requests, e.g.:
app.BigCollection = app.Collection.extend({
initialize: function() {
this.loadedPages = {};
},
loadPage: function(pageNumber) {
if (!this.loadedPages[pageNumber]) {
this.fetchNew({
page: pageNumber,
success: function(collection) {
collection.loadedPages[pageNumber] = true;
}
})
}
}
});
Backbone.Collection.fetch has an option {add:true} which will add models into a collection instead of replacing the contents.
myCollection.fetch({add:true})
So, in your first scenario, the items from page2 will get added to the collection.
As far as your 2nd scenario, there's currently no built in way to do that.
According to Jeremy that's something you're supposed to do in your App, and not part of Backbone.
Backbone seems to have a number of issues when being used for collaborative apps where another user might be updating models which you have client side. I get the feeling that Jeremy seems to focus on single-user applications, and the above ticket discussion exemplifies that.
In your case, the simplest way to handle your second scenario is to iterate over your collection and call fetch() on each model. But, that's not very good for performance.
For a better way to do it, I think you're going to have to override collection._add, and go down the line dalyons did on this pull request.
I managed to get update in Backbone 0.9.9 core. Check it out as it's exactly what you need http://backbonejs.org/#Collection-update.