How to wait for subscribtion Iron Router with auto-publish? - javascript

I'm actually developping a WebApp with Meteor. Since i'm more focused on UI and specs than being ready for production and look at security issues.(would do that later, after I've done a good PoC).
I would like to keep the autopublish and insecure packages (damn that's cool to go quick).
I'm bumping into an annoying issue with Iron Router.
The problem is pretty simple, but I'cannot find a way to make it work properly. Sometime data return randomly undefined.
Router.route('/stuff/:_id', {
name: 'stuff',
template: 'stuff',
data: function(){
var stuff = Stuffs.findOne(this.params._id);
console.log("stuff: ", stuff);
return {headerTitle: stuff.name, stuffData: stuff};
},
action: function(){
this.render();
}
});
It's actually not that random, I guess something like this happen:
Working well when I hit this route from link from the App (I guess the data is ready)
But on page refresh, or on naviguate by the url, I could have this annoying undefined.
Everything is autopublish, so it's would be strange to use a waitOn into my route...
Does someone hit the same problem and got a solution?

Working well when I hit this route from link from the App (I guess the data is ready)
Data is already present on the client-side. In this case, Iron Router is doing client-side routing only (ie, no calls going to server).
But on page refresh, or on naviguate by the url, I could have this annoying undefined.
In this case you are actually requesting data from the server-side..via a HTTP GET request, for which you probably don't have any routes defined.
EDIT
I was wrong regarding the HTTP GET request. It seems that no matter which link we load from browser, Meteor server sends the whole build. After the whole build is loaded onto the client-side, Iron Router decides which template to load based on the URL path.
It seems on page load/refresh, the page gets rendered before receiving data from server-side. This is the most probable reason for getting undefined.
Attached this listener on the client-side database to check when it is updated by server:
if (Meteor.isClient) {
Stuffs.find().observe({
added: function(doc) {
console.log("Updated Stuffs : ", JSON.stringify(doc));
}
});
}

Get it!
Was simpler than I expected!
Related this issue:
https://github.com/iron-meteor/iron-router/issues/295
Obviously Meteor cursor are reactive, finally I just have to do:
Router.route('/stuff/:_id', {
name: 'stuff',
template: 'stuff',
data: function(){
var stuff = Stuffs.findOne(this.params._id);
if(stuff){
console.log("stuff: ", stuff);
return {headerTitle: stuff.name, stuffData: stuff};
}
},
action: function(){
this.render();
}
});
Testing if stuff is undefined before doing anything, so stuff is not used -> no errors. On update the hook will be rerun, done.
That's it. Pretty cool and simple, I don't even think about that!

Related

How do i save model data when creating a Backbone driven theme in Wordpress?

Ok so im messing around with Backbone for the first time. I think I've pretty much covered all the basics of frontend logic, but i have never really been any good at backend logic and coding.
I'm working with wordpress and creating a theme using backbone. My understanding is as long as i set up a template page that has the correct containers that my backbone code will render views in, the fact that it's a wordpress theme instead of it's own app shouldn't really change anything on the frontend side.
I'm at the stage where i want to save a model so that i can fetch it in my routes to link to my view to render.
I'm unsure about the whole process of saving data. I know i need to give the model attribute 'urlRoot' a string but i don't know what that string should be, and what happens after that.
Can someone explain the whole process, especially in terms of how to do it with Wordpress. (i did stumble upon the WP REST API plugin that i think helps, although i don't exactly know how.)
EDIT
OK so in the end i presume my problem was something to do with authentication when trying to access the database as the textResponse was just returning the entire HTML for the current page i was on, probably due to the fact it wasn't getting through to the database and being redirected back to the page.
After googling around for a while i came across this. Rather than reinventing the wheel I installed this plugin and followed the setup instructions and low and behold it worked pretty much out of the box. If your trying to build a Backbone theme i suggest using the WP-API Client JS plugin with the WP REST API plugin. Seems to cover everything.
How to expose a WordPress blog's content through an API?
WP REST API seems like a good way to start. There are a lot of options and it exposes everything you need.
Note that it is named WordPress REST API (Version 2) in the wordpress.org plugin directory.
You can test that the plugin works by navigating to:
http://www.example.com/wp-json/wp/v2/
It should output all the information on the blog as a big JSON dump.
You can also test that it works for other endpoints, like post:
http://www.example.com/wp-json/wp/v2/posts
There's a Backbone plugin for the WP REST API that works out of the box.
How to communicate with the API?
This is a simple example using Backbone without any plugin. If you want to know how to use the plugin, see the documentation for it.
Since it offers a lot of arguments that can be passed in the URL, I made a small collection and an example of how it could be used.
var API_ROOT = '/wp-json/wp/v2/',
DEFAULT_API_ARGS = ['context' /* etc. */ ];
var WordPressCollection = Backbone.Collection.extend({
constructor: function(models, options) {
options = options || {};
this.apiArgs = _.union(DEFAULT_API_ARGS, this.apiArgs, options.apiArgs);
this.args = _.extend({}, this.args, this.getApiArgs(options));
WordPressCollection.__super__.constructor.apply(this, arguments);
},
getApiArgs: function(obj) {
return _.pick(obj, this.apiArgs);
},
fetch: function(options) {
options = options || {};
options.data = _.extend({}, this.args, this.getApiArgs(options), options.data);
return WordPressCollection.__super__.fetch.call(this, options);
},
});
And to use it:
var CommentCollection = WordPressCollection.extend({
url: API_ROOT + 'comments',
// all the arguments to look for in the passed options
apiArgs: ['page', 'per_page', 'post' /* etc. */ ],
});
var myPostComments = new CommentCollection(null, {
post: 23 // id
});
console.log(myPostComments.url());
myPostComments.fetch({ page: 2 });
The fetch should make a GET request to:
/wp-json/wp/v2/comments?post=23&page=2
And from that point, the WP REST API plugin takes control. It returns a new JSON encoded array of comment objects in the body of the response.
It should looks something like this:
Backbone automatically parses the JSON received, so you don't need to worry about that and you just have to go on and use it:
myPostComments.each(function(comment) {
console.log(comment.get('author_name'));
});
Then, saving a new comment is a matter of calling:
// check the doc for the comment object details
myPostComments.create({
post: 23,
content: "my new comment",
/* etc. */
});
And this would make a POST request to /wp-json/wp/v2/comments.

Authentication with devise rails using backbone

I am new to backbone.js. I am developing a rails application using "backbone-on-rails" gem. I have included 3 models and rendering views uisng backbone. It worked fine. Now i want to add authentication to my app using devise, after the user has signed in only my app needs to be rendered otherwise i need to redirect to login page.
I have added devise gem for it.
Can someone please help me on how can i check whether user has signed in or not, if user hasn't logged in need to redirect to devise sign_in page using backbone?
Thanks in Advance
Backbone's a frontend-only framework, so it doesn't have a concept of authentication. All the source code is sent to the web browser, and all the network connections are plain to see, so a malicious user can trick your app into thinking it's logged in, even if it isn't. So you'll still need to check access permissions on the server.
What you can do, though, is have your Backbone app detect whether it thinks it's logged in, and change its display based on that. For instance, you could use Devise's user_signed_in? helper to add a data attribute on your body tag, and hook into that. Something like this in your app/views/layouts/application.html.erb:
<body data-user-signed-in="<%= user_signed_in? ? "true" : "false" %>">
And then, maybe your Backbone router is going to look something like this:
myApp.Routers.Router = Backbone.Router.extend({
routes: {"": "showFrontPage"},
isSignedIn: function() {
return $('body').data('user-signed-in') === "true";
},
showFrontPage: function() {
var view;
if (this.isSignedIn()) {
view = new myApp.Views.MainAppView();
} else {
view = new myApp.Views.SignInView();
}
view.render();
}
});
Alternatively, you could look directly for a session cookie. That seems a bit more brittle to me, though; if the name of your application changes, or Rails changes how it names its cookies, your app's going to break. But in that case, your isSignedIn() function is going to look more like this:
isSignedIn: function() {
return document.cookie.indexOf("_Railsappname_session") > -1;
}
If you want to check your user at various points of your app, you could easily write a controller method that returns the result of user_signed_in? as a JSON object. But it's better not to rely on this; rather than calling /user/is_signed_in and then /posts/create, far better to do one call to /posts/create and have that return a 401 Unauthorized if the user's not logged in.
As for logging in itself, you can adapt Devise to work via JS so you can login via AJAX, but it's not as straightforward as you might hope. There's a tutorial here.
I needed to implement a backbone login in rails with devise. Note that for my purposes I did not need user registration as well, since I wanted to have just one admin user, created manually in the terminal by me.
Basically as long as you make an AJAX post request to the right devise route, devise will handle the login for you (assuming of course you went through the devise setup process correctly).
In Backbone you can make this post request using a new model save.
This tutorial helped me set up my Backbone model and view (FYI: the tutorial also goes over what you need to do in order to add registration functionality).
The tutorial had some more advance backbone setup (for example it uses backbone.marionette and backbone.modelbinder) which although very useful, I did not want to get into. Below is my simplified version of the tutorial to the bare core of what you need.
Create a model with the urlRoot that matches your devise login route. For most people that go with the standard User model, the urlRoot route below should work. Note my code is written in coffeescript
class MyCoolBackboneApp.Models.UserSession extends Backbone.Model
urlRoot: '/users/sign_in.json'
defaults:
email: ""
password: ""
toJSON: ->
{ user: _.clone(#attributes) }
Note that devise expects the params to be wrapped inside 'user' which is why we had to overwrite the toJSON method
Then in your view, all you need to do is save the model together with the login credentials. Of course every person might have a different success and failure callback, but here is a very basic implementation:
events:
'submit form': 'login'
initialize: =>
#model = new MyCoolBackboneApp.Models.UserSession()
render: =>
$(#el).html( #template() )
#
credentials: ->
{
email: #$('#email').val(),
password: #$('#password').val(),
remember_me: 1
}
login: (event)->
event.preventDefault()
#model.save(#credentials(),
success: (userSession, response) =>
window.location.href = "/"
error: (userSession, response) =>
message = $.parseJSON(response.responseText).error
alert(message)
)
You should also read this tutorial about how to set up devise ajax authentication.
After you complete the above tutorial, you should be able to save your UserSession model with the right credentials (as I do in the view) and login successfully (assuming you have a saved existing user in your Database). You'll know you've logged in successfully when you get redirected to your success callback.
Then in the controllers in the rest of your app, you should be able to use the devise helpers: user_signed_in? or current_user etc etc.
(If you are logged in but get an undefined method error for these helpers, try to add: include Devise::Controllers::Helpers to your controllers).
Finally Alex P's response can then walk you through how to use the user_signed_in? boolean in your Backbone views.

How to redirect user after successful login?

Update:
After implementing the below suggestion by Rob Sedgwick, it has become apparent that the redirect only works when the user manually "F5" refreshers the browser (Chrome). What I need to achieve is that this happens automatically in the code so the redirect happens without the user having to hot refresh. Thanks for help with this last part.
At the moment ManageTodosView from what I understand is the first action after the user has been logged in. It prints a list of to do items set up by the user. A working example can be found here http://parseplatform.github.io/Todo/ and the code is https://github.com/ParsePlatform/Todo
I'm using to code to really get user logins to work, I'm not to worries about what the output of the rest of the code is because the long term plan will be to remove it, for the time being its helpful to keep in place to show that the app functioning correctly.
I'm using this code as a base to build a web app. At the moment, once the user is logged in, they are displayed data on the same page.
I want to be able to change this so that after they login, the user is redirected to a different page and the information is then displayed to them there.
The reason for this is that the index page is just a landing/login page and I want to redirect them to a more structured HTML page with menus, etc.
Within the JS code, do I just put in a redirect, something like:
self.location="top.htm";
to this area of the code?
// The main view for the app
var AppView = Parse.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#todoapp"),
initialize: function() {
this.render();
},
render: function() {
if (Parse.User.current()) {
new ManageTodosView();
} else {
new LogInView();
}
}
});
I have added the JS code to this JSFiddle
Update:
To address the issue of the page needing a manual fresh before the redirect works, insert
window.location.href="/someurl";
into the following code section within the todoe.js file and comment out the new ManageTodosView(); code.
Parse.User.logIn(username, password, {
success: function(user) {
window.location.href="user_home.html";
//new ManageTodosView();
self.undelegateEvents();
delete self;
},
Try this one also
window.open('url','_parent');
I would suggest a more robust template for integrating Parse Todo samples with real apps. 'Marionette' offers lots of value in real world.
If you take the time to look over the app's structure and then look at the 'loginSuccess' function (scroll to very bottom of link), its pretty straightforward to redirect. You can either use the router as shown OR you can use the Marionette aggregated events which would look like:
vent.trigger('header:loggedIn', _user);
somewhere else in any module within the app....
vent.on('header:loggedIn', function (user) {
that.setViewNew(user);
});
...
setViewNew : function (user) {
User = user;
var viewOptionsUser = {
collection : this.roleList,
events : {
'keypress #new-todo': 'createTaskOnEnter'},
parentUser : User};
this.headerRegion.show(new HeaderView(viewOptionsUser));
this.mainRegion.show(new RoleRoleListCompositeView(viewOptionsUser));
}
Try something like this to redirect your user
window.location = 'www.google.com'

Postponing search until template load

I want to make a request to a server to get a bunch of news articles based off of what the user clicks on (recent, trending, etc). I'd like to be able to load the page first and show a loading bar while I wait for the response from the API. What I have, and although it works and returns the JSON data I need, will wait until the response comes back from the server before loading anything. This is all it is so far:
What I want to achieve is the following: Load up an empty array of objects, and then make API calls to articles incrementally (let's say grab all the articles from this hour, then last hour, then the hour before, and so on) and whenever I retrive articles populate the view (I'm assuming I can just inject them into a controller somehow) however I'm getting lost on the Emberisms on how to add to that array of objects. I think I need to make an ArrayController and then create a model in said array, then call a function to add to it, but as I said I'm lost as to how to add items into that controller on the fly
App = Ember.Application.create();
App.Router.map(function() {
this.resource('today');
});
App.TodayRoute = Ember.Route.extend({
model: function() {
return $.getJSON('/today');
}
});
To elaborate a bit on the reasoning for my question - I'm confused on how to approach this with ember. I'm familiar on how to do something like this using jquery, however I'm trying to learn the framework and am having a little bit of trouble originally knowing what the division of labor is between the two. I know the actual AJAX requests should be jquery, but I want to do as much as possible in Ember. If this however is something that should be done by jquery though, then that is fine as well!
The getJSON method looks to have a callback function on success you could use, something like this maybe?
App.TodayRoute = Ember.Route.extend({
model: function() {
$.getJSON('/today', function(data) {
// hide loading here
/ data is the JSON returned
});
}
});
Check out this link http://api.jquery.com/jQuery.getJSON/
I found the solution - and like everything with ember, it was very simple. I used some blank fixture data, then defined my controller as below.
App.ArticleRoute = Ember.Route.extend({
model: function() {
return App.Article.find();
}
})
Then upon pageload I call a method focused around
App.Article.createRecord({
// Attributes, pulling from the returned JSON
})
Simple! :)

Emberjs: Loading data for other route

Resolved itself when updating to emberjs-rc.2. See answer.
In my application, I have 2 pages with wizard-ish functionality. On the first page, the user will provide some data, and on the next page the user will select an item in a list built based on the data on the first page. What I'm trying to do is to start loading the data for the second page as soon as the required data on the first page is valid, even if the user is still on the first page. This because building the list server will take some time, an I'd like to have the data ready when transitioning to the next page.
The two pages / routes are nested under the same resource.
My routes:
App.YayRoute = Ember.Route.extend({
events: {
dataValid: function() {
this.controllerFor('yaySecond').send('loadStuff');
}
}
});
App.YaySecondRoute = Ember.Route.extend({
events: {
loadStuff: function() {
this.controller.set('stuff', App.Stuff.find());
}
}
});
In YayFirstController, I'm observing relevant data and doing this.send('dataValid') when it is. The top route YayRoute picks this up ok, and triggers loadStuff on the second route. App.Stuff.find() looks like this
find: function() {
var result = Ember.A([]);
$.getJSON(url, function(data) {
// populate result
});
return result;
}
Everything is being run when intended, but my problem is that stuff on YaySecondController is not populated when called from loadStuff. If I add controller.set('stuff', App.Stuff.find()) contents of loadStuff to setupController on YaySecondRoute, the data will load OK. But then I lose the coolness with the data being loaded as soon as possible. Any ideas?
So, this resolved itself when updating to emberjs-rc.2 today. I'm still new to ember, so I have no idea what was going on. Anyway, is this the correct way to do this? It seems slighty cumbersome.

Categories

Resources