I'm struggling to create a user profile page, using Iron Router, which is located at localhost:3000/:username
. The profile page should have the following characteristics:
The public view and private view should exist at the same URL path. Depending on the client's credentials, they see one or the other without a redirect to a different page. The not found page should also not redirect, this way the user can still see the invalid URL in the browser URL bar if the enter an invalid username.
My router.js file:
this.route('profile', {
controller: 'ProfileController',
path: '/:username'
});
Within ProfileController
, I'm trying to scrape together the following:
onBeforeAction
- show loading screen; determine if username exists (aka if URL is valid)
waitOn
- wait for username
's data to be retrieved before removing loading screenonAfterAction
- remove loading screenThanks!
Luckyly, every characteristics you are looking for are available as baked in plugins so you won't even have to dive in defining your own hooks.
Notice that I'm using iron:[email protected]
, this is important to keep up with the latest stuff, there are just two minor quirks at the moment that I hope will get fixed soon.
Let's start with the user profile publication, which take the username as argument.
server/collections/users.js
Meteor.publish("userProfile",function(username){
// simulate network latency by sleeping 2s
Meteor._sleepForMs(2000);
// try to find the user by username
var user=Meteor.users.findOne({
username:username
});
// if we can't find it, mark the subscription as ready and quit
if(!user){
this.ready();
return;
}
// if the user we want to display the profile is the currently logged in user...
if(this.userId==user._id){
// then we return the corresponding full document via a cursor
return Meteor.users.find(this.userId);
}
else{
// if we are viewing only the public part, strip the "profile"
// property from the fetched document, you might want to
// set only a nested property of the profile as private
// instead of the whole property
return Meteor.users.find(user._id,{
fields:{
"profile":0
}
});
}
});
Let's continue with the profile template, nothing too fancy here, we'll display the username as public data, and if we are viewing the private profile, display the user real name that we assume is stored in profile.name
.
client/views/profile/profile.html
<template name="profile">
Username: {{username}}<br>
{{! with acts as an if : the following part won't be displayed
if the user document has no profile property}}
{{#with profile}}
Profile name : {{name}}
{{/with}}
</template>
Then we need to define a route for the profile view in the global router configuration :
lib/router.js
// define the (usually global) loading template
Router.configure({
loadingTemplate:"loading"
});
// add the dataNotFound plugin, which is responsible for
// rendering the dataNotFound template if your RouteController
// data function returns a falsy value
Router.plugin("dataNotFound",{
notFoundTemplate: "dataNotFound"
});
Router.route("/profile/:username",{
name:"profile",
controller:"ProfileController"
});
Note that iron:router
now requires that you define your routes and route controllers in the shared directory (usually this is the lib/
dir at the root of your project) available to both client and server.
Now for the trickiest part, the ProfileController
definition :
lib/controllers/profile.js
ProfileController=RouteController.extend({
template:"profile",
waitOn:function(){
return Meteor.subscribe("userProfile",this.params.username);
},
data:function(){
var username=Router.current().params.username;
return Meteor.users.findOne({
username:username
});
}
});
When iron:router
detects that you're using waitOn
in a RouteController
it will now automatically add the default loading
hook which is responsible for rendering the loadingTemplate
while the subscription is not yet ready.
I'll address now the two minor bugs I've talked about in the beggining of my answer.
First, the official iron:router
guide (which you should definitely read) http://eventedmind.github.io/iron-router/ mentions that the name of the option you should pass to the dataNotFound
plugin is dataNotFoundTemplate
but as of 28-09-2014 this won't work, you need to use the legacy name notFoundTemplate
, this is likely to get fixed in a matter of days.
The same goes for the code of my data
function in the controller : I've used the counter-intuitive syntax Router.current().params
to access the route parameters when normally this.params
would have been the appropriate regular syntax. This is another yet-to-be-addressed issue. https://github.com/EventedMind/iron-router/issues/857
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With