Iron Router / User details issue in Meteor - meteor

I am relatively new to Meteor and have been stuck on an issue for awhile. I have a /users/:_id route that is supposed to display details specific to that user id. However, whenever I hit that route, it displays information for the currently logged in user, NOT of the user whose details I want to view.
Here's my route:
Router.route('/users/:_id', {name: 'Users', controller: 'usersDetailController'});
Here's my usersDetailController:
usersDetailController = RouteController.extend({
waitOn: function () {
Meteor.subscribe('userProfileExtended', this.params._id);
},
onBeforeAction: function () {
var currUserId = Meteor.userId();
var currUser = Meteor.users.findOne({_id: currUserId});
console.log('admin? ' + currUser.isAdmin);
if (!currUser.isAdmin) {
this.render('accessDenied');
} else {
this.next();
}
},
action: function() {
this.render('Users');
}
});
And here's my server/publish:
Meteor.publish('userProfileExtended', function() {
return Meteor.users.find({_id: this.userId});
});
User Details template:
<template name="Users">
<form>
{{#with user}}
<div class="panel panel-default">
<div class="panel-heading">{{profile.companyName}} Details</div>
<div class="row">
<div class="col-md-4">
<div class="panel-body">
<p><label>Company: </label><input id="Company" type="text" value={{profile.companyName}}></p>
<p><label>Email: </label><input id="Email" type="text" value={{emails.address}}></p>
<p><label>Phone: </label><input id="Phone" type="text" value={{profile.phoneNum}}></p>
<p><label>Tire Markup: </label><input id = "tireMarkup" type="text" value={{profile.tireMarkup}}></p>
<p><button class="saveUserDetails">Save</button></p>
<p><button class="deleteUser">Delete User</button></p>
</div>
</div>
</div>
</div>
{{/with}}
Here's my Template Helper:
Template.Users.helpers({
user: function() {
return Meteor.users.findOne();
}
});
Can someone help? I think the issue is the way i reference "this.userId"...
Thank you!!

You need to change your publish function to use the userId parameter you specify when subscribing :
Meteor.publish('userProfileExtended', function(userId) {
return Meteor.users.find(userId,{
fields:{
'username':1,
'profile.firstName':1,
'profile.lastName'
}
});
});
In the publish function, userId will equal whatever value you call Meteor.subscribe with, in this case it will hold this.params._id.
Beware of using the proper syntax for route parameters, if you declare a path of /users/:_id, you need to reference the param using this.params._id.
Also note that it's insecure to publish the whole user document to the client if you only need to show specific fields in the interface, that's why you want to use the fields option of Collection.find to only publish a subset of user documents.
EDIT :
I would recommend using the route data function to specify the data context you want to apply when rendering your template, something like this :
data: function(){
return {
user: Meteor.users.findOne(this.params._id)
};
}

Related

Calling Meteor methods in React components

Currently I'm working on a project based on Meteor as back end and React as front end. I really enjoyed simplicity untill I removed insecure package and have to deal with Meteor methods. Right now I need to perform a basic insert operation and I'm just stucked!
I have a form as component (in case eventually I'd like to use this form not only for inserting items but for editing those items as well) and here's my code for this form:
AddItemForm = React.createClass({
propTypes: {
submitAction: React.PropTypes.func.isRequired
},
getDefaultProps() {
return {
submitButtonLabel: "Add Item"
};
},
render() {
return (
<div className="row">
<form onSubmit={this.submitAction} className="col s12">
<div className="row">
<div className="input-field col s6">
<input
id="name"
placeholder="What"
type="text"
/>
</div>
<div className="input-field col s6">
<input
placeholder="Amount"
id="amount"
type="text"
/>
</div>
</div>
<div className="row">
<div className="input-field col s12">
<textarea
placeholder="Description"
id="description"
className="materialize-textarea">
</textarea>
</div>
</div>
<div className="row center">
<button className="btn waves-effect waves-light" type="submit">{this.props.submitButtonLabel}</button>
</div>
</form>
</div>
);
}
});
This chunk of code is used as a form component, I have a prop submitAction which I use in let's say add view:
AddItem = React.createClass({
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Items.insert(
{
name: name,
range: range,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
},
function(error) {
if (error) {
console.log("error");
} else {
FlowRouter.go('items');
};
}
);
},
render() {
return (
<div className="row">
<h1 className="center">Add Item</h1>
<AddItemForm
submitButtonLabel="Add Event"
submitAction={this.handleSubmit}
/>
</div>
);
}
});
As you can see I directly grab values by IDs then perform insert operation which works absolutely correct, I can even get this data displayed.
So now I have to remove insecure package and rebuild the whole operation stack using methods, where I actually stucked.
As I understand all I should do is to grab same data and after that perform Meteor.call, but I don't know how to pass this data correctly into current method call. I tried considering this data right in the method's body which doesn't work (I used the same const set as in AddItem view). Correct me if I'm wrong, but I don't think this method knows something about where I took the data (or may be I don't really get Meteor's method workflow), so by this moment I ended up with this code as my insert method:
Meteor.methods({
addItem() {
Items.insert({
name: name,
amount: amount,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
});
}
});
and this is how I changed my handleSubmit function:
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Meteor.call('addItem');
},
Also I tried declaring method like this:
'addItem': function() {
Items.insert({
// same code
});
}
but it also didn't work for me.
Again, as I understand the problem isn't about data itself, as I wrote before it works just right with insecure package, the problem is how the heck should I get this data on the server first and right after that pass this to the client using methods (also console gives no even warnings and right after I submit the form, the page reloads)?
I've already seen some tutorials and articles in the web and didn't find desicion, hope to get help here.
You can add your data as parameters in your Meteor call function. You can also add a callback function to check on the success of the call.
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Meteor.call('addItem', name, amount, description, function(err, res) {
if (err){
console.log(JSON.stringify(err,null,2))
}else{
console.log(res, "success!")
}
});
},
In your Meteor methods:
Meteor.methods({
addItem(name, amount, description) {
var Added = Items.insert({
name: name,
amount: amount,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
});
return Added
}
});

Meteor, publish:composite. how to access joined data in the template?

So I used publishComposite to do a collection join in Meteor. I have a parent collection (Subscriptions) with a user_id foreign key. I look up the user name in the Meteor.users collection to get the actual username, but how do I actually print this in the html template. My subscription data is there but how do I actually refer to the username?
Here is the publish code:
//publish subscriptions course view
Meteor.publishComposite('adminCourseSubscriptions', function(courseId){
return {
//get the subs for the selected course
find: function(){
return Subscriptions.find(
{course_id: courseId}
);
},
children:
[
{
//get the subscriber details for the course
find: function(sub){
return Meteor.users.find({_id:sub.user_id});
}
}
]
};
});
here are the template subdcriptions:
Template.adminCourseDetail.helpers({
courseDetail: function(id){
var id = FlowRouter.getParam('id');
return Courses.findOne({ _id: id });
},
courseSubscriptions: function(){
var id = FlowRouter.getParam('id');
return Subscriptions.find({course_id:id})
},
users: function(){
return Meteor.users.find();
}
});
and the template (which is garbage) ps the course details come from a separate collection. It was easier and I think more performant to get the details separately and this works fine. It's just the username that I cannot display correctly:
<template name="adminCourseDetail">
<h1>Course Details</h1>
<p>Title: {{courseDetail.title}}</p>
<p>Description: {{courseDetail.description}}</p>
<p>Start Date: {{courseDetail.startDate}}</p>
<p>Number of sessions: {{courseDetail.sessions}}</p>
<p>Duration: {{courseDetail.duration}}</p>
<p>Price: {{courseDetail.price}}</p>
<p>{{userTest}}</p>
edit
delete
<h2>Course Subscriptions</h2>
{{#each courseSubscriptions}}
<div class="row">
<div class="col-md-3">{{username}}</div>
<div class="col-md-3">{{sub_date}}</div>
</div>
{{/each}}
</template>
Thanks in advance for any suggestions!
As far as I understand your question, documents of the Subscriptions collection contain only the attribute user_id, referencing the corresponding user document in the Meteor.users collection. If this is the case, then you need to add an additional template helper which returns the username:
Template.adminCourseDetail.helpers({
// ...
getUsername: function() {
if (this.user_id) {
let user = Meteor.users.find({
_id: this.user_id
});
return user && user.username;
}
return "Anonymous";
}
// ...
});
After that, just replace {{username}} with {{getUsername}}:
<template name="adminCourseDetail">
<!-- ... -->
<h2>Course Subscriptions</h2>
{{#each courseSubscriptions}}
<div class="row">
<div class="col-md-3">{{getUsername}}</div>
<div class="col-md-3">{{sub_date}}</div>
</div>
{{/each}}
<!-- ... -->
</template>
Probably you misunderstood the concept of the reywood:publish-composite package. Using Meteor.publishComposite(...) will just publish a reactive join, but it will not return a new set of joined data.
For anyone else having a similar issue and looking at my specfic example. In my case the following code worked. Based on Matthias' answer:
In the template helper:
getUsername: function() {
let user = Meteor.users.findOne({
_id: this.user_id
});
return user;
}
and then in the template:
{{getUsername.username}}
My each block is looping through the cursor returned from the subscriptions collection rather than the course collection which is why it is simpler than the code Matthias provided.

html element innerHtml change with session and server code

This webApp has a header which displays a short message based on user interaction by using session in order to make it reactive. I would like to give the server the priority to display its own message it it has one.
Like if server.message is "" then use client session else use server message.
Should I use global helper? or How can go about it? Thanks.
Template.header.helpers({
headerLabel: function(){
return Session.get('taskSelected');
}
});
<template name="header">
<h1>
<button class="col-xs-2 mainMenu" type="button">☰</button>
</h1>
<h3>
<label class="col-xs-8 text-center">
{{#if headerLabel}}
{{headerLabel}}
{{else}}
Select an item
{{/if}}
</label>
</h3>
<h1>
<button class="col-xs-2" type="button">⋮</button>
</h1>
</template>
Template.mainMenu.events({
'click .menuItem': function (event) {
Session.set('taskSelected', this.menuItem);
});
I think you could do this with reactive variables:
Template.header.onCreated( function() {
this.message = new ReactiveVar( "" );
Meteor.call('serverMessageMethod', function(error, results) {
if( error || !results ) {
Template.instance().message.set(Session.get("yourVariable");
} else {
Template.instance().message.set(results);
}
});
});
Template.header.helpers({
message() {
return Template.instance().message.get();
}
});
You'll have to create the server method to see if there's a message, but other than that this should work.

How to display Meteor.loginWithPassword callbak error message on the same page

I have created a custom login page and used the Meteor.loginWithPassword(user, password, [callback]) function to login to the app.
Following is the login template:
<template name ="Login">
<form class="login-form form-horizontal">
<div class="control-group">
<input class="email" type="text" placeholder="Email">
</div>
<div class="control-group m-inputwrapper">
<input class="password" type="password" placeholder="Password">
</div>
<div class="control-group">
<button type="submit" class="submit t-btn-login" >Login</button>
</div>
</form>
<div class="alert-container">
<div class="alert-placeholder"></div>
</div>
</template>
Template.Login.events({
'submit .login-form': function(e, t) {
e.preventDefault();
// retrieve the input field values
var email = t.find('.email').value,
password = t.find('.password').value;
Meteor.loginWithPassword(email, password, function(err) {
if (err) {
$(".alert-placeholder").html('<div></div><div class="alert"><span><i class="icon-sign"></i>'+err.message+'</span></div>')
}
});
return false;
}
});
While i debugging i can see the error message displayed and added to the dom. but it will get refresh and message will disappear.
Is meteor re render the page after Meteor.loginWithPassword() ? How can i overcome this?
When using meteor, if you find yourself manually injecting html elements with jQuery, you are probably doing it wrong. I don't know the blaze internals well enough to give you an exact answer to why your elements are not being preserved, but here is a more meteor-like solution:
In your alert container, conditionally render an error message:
<div class="alert-container">
{{#if errorMessage}}
<div class="alert">
<span><i class="icon-sign"></i>{{errorMessage}}</span>
</div>
{{/if}}
</div>
In your login callback, Set the errorMessage session variable if err exists:
Meteor.loginWithPassword(email, password, function(err) {
if (err) {
Session.set('errorMessage', err.message);
}
});
Finally, add a template helper to access the errorMessage session variable:
Template.Login.helpers({
errorMessage: function() {
return Session.get('errorMessage');
}
});
You can use Bert for showing error message in each page. I use it in login page like this :
Meteor.loginWithPassword(emailVar, passwordVar, function(error) {
if (error) {
Bert.alert(error.reason, 'danger', 'growl-top-right');
} else {
Router.go('/dashboard');
}
});

How to create users client side?

I'm working on a intern document-sharing project for a small company. I want to do this with meteor. I'm very common with html/javascript but not with databases.
My problem is to handle the users. Because of my researches here I'm not sure if it's already possible to create users on client side. The official documentation shows some methodes how to deal with users but no examples.
I tried to create a list on server side like this:
Users = new Meteor.Collection("users");
Then I want to insert a user on startup like this:
//on Client side
if (Meteor.isClient) {
var username = "My Name";
Meteor.call("create_user", username, function(error, user_id) {
Session.set("user_id", user_id);
});
}
//on Server side
if(Meteor.is_server) {
Meteor.methods({
create_user: function(username) {
console.log("CREATING USER");
var USER_id = Users.insert({name: username});
return user_id;
},
});
}
But reading the username in the html template doesn't work...
Are there any good examples with a register and login?
Cheers
Adding the accounts functionality is very easy in Meteor.. may it be simple email, password, or by using facebook connect/twitter etc..
do the following to get a simple meteor app with user accounts set up..
meteor create simpleapp
cd simpleapp
add the accounts-ui and accounts-password packages
meteor add accounts-ui
meteor add accounts-password
you simply add other accounts related packages for implementing facebook/twitter/github/google login etc
to list other available meteor packages use this command
meteor list
now edit your simpleapp.html file to this to add login buttons etc..
<head>
<title>simpleapp</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
{{loginButtons}}
</template>
I simply added {{loginButtons}} to the default html file to add the default login buttons..
now run the meteor app and go to localhost:3000
meteor
you implemented the login functionality without doing much work. 4-5 lines of code, it even takes care of things like forgot password, registering new user etc
next thing is you need to display a particular html when the user is signed in.
you do this using the {{currentUser}} global
you implement it accordingly
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
{{loginButtons}}
{{#if currentUser}}
{{> loggedInTemplate}}
{{else}}
{{> loggedOutTemplate}}
{{/if}}
</template>
<template name="loggedInTemplate">
<!-- user is logged in -->
</template>
<template name="loggedOutTemplate">
<!-- user is logged out -->
</template>
You don't need to create a user system manually. Just use the accounts package:
The Meteor Accounts system builds on top of the userId support in publish and methods. The core packages add the concept of user documents stored in the database, and additional packages add secure password authentication, integration with third party login services, and a pre-built user interface.
Your code should work, though. But you're not exposing the username property to the client, so maybe that's why you can't see it in your template.
Ok thank you, that's easy. But so I can not modify the loggedInTemplate or the loggedOutTemplate.
I show you what I've got:
//the html
<head>
<title>myApp | documents</title>
</head>
<body>
<div id="headerBox">
{{> header}}
</div>
<div id="sideBox">
{{> side}}
</div>
<div id="contentsBox">
{{> contents}}
</div>
</body>
<template name="header">
<h1>Company name</h1>
</template>
<template name="contents">
{{#if currentUser}}
<p>Welcome to documents</p>
{{else}}
<h3>Hello! Please log in!</h3>
<p><input type="text" id="username" placeholder="Username"><input type="text" id="password" placeholder="Password"><input type="button" value="log me in!"></p>
{{/if}}
</template>
<template name="side">
<p>Hello {{ activeUser }}</p>
<p><input type="button" value="Create New Document" onclick="createPage()"></p>
<h3>Documents:</h3>
</template>
and
//client.js
Pages = new Meteor.Collection("pages");
Meteor.startup(function() {
Session.set("activeUser", "please log in!");
});
Template.side.activeUser = function() {
return Session.get("activeUser");
};
and
//server.js
Meteor.startup(function() {
Accounts.createUser({username: "MyName", email: "me#example.com", password: "123456"});
});
and I'm searching for a manual way to log this user created on startup in. And of course later to allow this user to create new users...
The problem is adding
// Returns an event_map key for attaching "ok/cancel" events to
// a text input (given by selector)
var okcancel_events = function (selector) {
return 'keyup '+selector+', keydown '+selector+', focusout '+selector;
};
// Creates an event handler for interpreting "escape", "return", and "blur"
// on a text field and calling "ok" or "cancel" callbacks.
var make_okcancel_handler = function (options) {
var ok = options.ok || function () {};
var cancel = options.cancel || function () {};
return function (evt) {
if (evt.type === "keydown" && evt.which === 27) {
// escape = cancel
cancel.call(this, evt);
} else if (evt.type === "keyup" && evt.which === 13) {
// blur/return/enter = ok/submit if non-empty
var value = String(evt.target.value || "");
if (value)
ok.call(this, value, evt);
else
cancel.call(this, evt);
}
};
};
Template.contents.events = {};
Template.contents.events[okcancel_events('#password')] = make_okcancel_handler({
ok: function (text, event){
var usernameEntry = document.getElementById('username');
var passwordEntry = document.getElementById('password');
Meteor.loginWithPassword({usernameEntry, passwordEntry});
event.target.value = "";
}
});
to the client doesn't work...

Resources