Meteor: User Profile Page with Iron Router
Asked Answered
C

1

9

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:

  • Public view - anyone can see basic information about the user
  • Private view - if the client visits his or her own profile page while signed-in, his or her sensitive user data is shown and they have edit capabilities
  • Loading view - while the user profile data is being fetched, show a loading screen
  • Not found view - if invalid username is entered into the URL, return a not-found page.

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)
    • Either show not found view, private profile, or public profile
  • waitOn - wait for username's data to be retrieved before removing loading screen
  • onAfterAction - remove loading screen

Thanks!

Coercion answered 28/9, 2014 at 11:29 Comment(0)
O
12

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

Orbit answered 28/9, 2014 at 15:22 Comment(3)
Thank you very much for your help! That Meteor.publish() function worked great for only publishing select data about the user in question.Coercion
This is a great write-up thank you. Worth noting for future readers this code won't work with the audit-argument-checks package installed.Biodynamics
If you have a profile page template, how do you get the values of fields of the user of that specific profile page on the js side?Saeger

© 2022 - 2024 — McMap. All rights reserved.