Meteor user profile page with data from another collection - javascript

I have a profile page for each seller that is privately and publicly available. I have the seller (user) information published and sending data to the client but I'm struggling with sending that seller's product collection to the client.
If I could send the user information for the profile page to the Products collection I could publish the products for the specific seller, but I'm currently stuck. I used this post as a reference: Meteor: User Profile Page with Iron Router
This is what I have thus far, it's not DRY:
template on client
<template name="profile">
<div style="margin-top 5em;">
<h1>Profile.Name: {{profile.name}}</h1>
<h1>Username: {{username}}</h1>
<h1>Profile.Description: {{profile.description}}</h1>
{{#each products}}
<h1>Products! {{username}}</h1>
<h1>price {{price}}</h1>
{{/each}}
</div>
</template>
helpers on client
Meteor.subscribe("userProfile", this.params.username);
Template.profile.helpers({
products : function(){
return Products.find();
}
});
router.js on lib
Router.route("/artist/:username", {
name:"profile",
waitOn:function(){
var username=this.params.username;
return Meteor.subscribe("userProfile", username);
return Meteor.subscribe("products-by-id", username);
},
data:function(){
var username=this.params.username;
return Meteor.users.findOne({username:username});
return Meteor.subscribe("products-by-id", username);
},
fastRender: true
});
publications.js on server
Meteor.publish("userProfile",function(username){
// simulate network latency by sleeping 2s
Meteor._sleepForMs(2000);
var user=Meteor.users.findOne({
username:username
});
if(!user){
this.ready();
return;
}
if(this.userId==user._id){
}
else{
return Meteor.users.find(user._id,{
fields:{
"profile.name": 1,
"profile.description" : 1,
"_id" : 1,
"username" : 1,
"profile.username" : 1
}
});
return Products.find({username: user.username});
}
});
Meteor.publish("allProducts", function(){
return Products.find();
});
Thank you for any input!

You can add reywood:publish-composite package. This package allow "link" collecttion like joins.
Meteor.publishComposite('AutorTexts', function(avatar) {
check(avatar, Match.Any);
return {
find: function(autorId) {
check(autorId, Match.Any)
return Roman.find({
autor_id: avatar
});
},
children: [{
find: function(avtor) {
return Avtor.find({
_id: avatar
});
}
}]
};
});
this code returns data from two collections: Roman & Avtor (code is weird, i know).
Also you need configure iron-router subscribe on route:
Router.route('/a/:_id', function() {
//console.log(this.params._id);
this.render('AvtorAll');
SEO.set({
title: 'blahblah title'
});
}, {
path: '/a/:_id',
// data: {id: this.params._id},
name: 'AvtorAll',
waitOn: function() {
return Meteor.subscribe('AutorTexts', this.params._id);
},
onAfterAction: function() {
if (!Meteor.isClient) { // executed only on client side!!
return;
}
SEO.set({
title: 'blahblah : ' + Avtor.findOne().autor_name,
og: {
"title": "blahblah : " + Avtor.findOne().autor_name,
"description": "blahblah . Powered by MeteorJS",
"url": Router.current().route.path(this),
"site_name": "blahblah ",
"locale": "you_locale_here" // didnt work
}
});
}
});

Related

StateParams passed through function not working using meteor

Here is my function used to retrieve data from the database depending on the parameter idCats:
this.getSubcat = function(){
//Load products on scroll.
this.subscribe('SubcatIndex', () => [ $stateParams.idCats, self.loaded ], {
onReady: function() {
Meteor.call('allSubcats', $stateParams.idCats, function(err, count) {
self.allsubcats = count;
self.limit = self.loaded;
console.log("Test Log: " + $stateParams.idCats);
self.subcats = Products.find({
catLinkID : $stateParams.idCats
},{
fields: {
_id: true,
name: true,
catLinkID: true,
idCat: true,
image: true,
listingsCount: true,
productOffersCount: true,
productSoldCount: true
}
}).fetch();
window.localStorage.setItem('subcats', JSON.stringify(self.subcats) );
self.contentLoaded = true;
self.noPosts = 'No posts available.';
$ionicLoading.hide();
return;
});
},
onStop: function(err){
if(err){
self.contentLoaded = true;
self.noPosts = "No internet connection.";
console.log(JSON.stringify(err));
return;
}
}
});
}
this.getSubcat();
When i change this line:
self.subcats = Products.find({
catLinkID : $stateParams.idCats
}
To:
self.subcats = Products.find({
catLinkID : 7 // 7 for example
}
It is working well ! But as soon as I replace it with $stateParams.idCats, I receive this message coming from the function: No posts available.
Note that there are products using the idCats: 7.
When I log it:
console.log("Test Log: " + $stateParams.idCats);
This returns the same number: Test Log: 7.
If you have any suggestion or a starting point to solve this issue, it will be welcome !
Notice that there are no error in the Console (Both server and client side).
Thank you.

Subscribe to Meteor.users() both listing all users and this.userId

PAGE CUSTOMERS: Lists all users in the users collection.
PAGE PROFILE: List only logged in user profile information.
userProfiles.js:
if (Meteor.isServer) {
Meteor.publish("userData", function () {
return Meteor.users.find({}, {
fields: {
// VISIBLE
'profile.mobile': 1,
'profile.zipcode': 1,
'profile.first_name': 1,
'profile.work_title': 1,
'emails[0].address': 1,
}});
});
}
profile.js
Template.profileDetails.helpers({
user: function() {
return Meteor.users.find({_id: this.userId});
},
userEmail: function() {
return this.emails[0].address;
},
userFirstName: function() {
return this.profile.first_name;
},
userTitle: function() {
return this.profile.work_title;
},
userMobile: function() {
return this.profile.mobile;
},
userZip: function() {
return this.profile.zipcode;
},
});
customers.js
Template.customerDetails.helpers({
user: function() {
return Meteor.users.find();
},
userEmail: function() {
return this.emails[0].address;
},
userFirstName: function() {
return this.profile.first_name;
},
userTitle: function() {
return this.profile.work_title;
},
userMobile: function() {
return this.profile.mobile;
},
userZip: function() {
return this.profile.zipcode;
},
});
The profile page is not showing any information at all. How can i get it to only display the logged in user information? Thank you!
the "this" in the helpers isn't the user. since you're looking for the current user in your profile template, you can do it in Blaze, without a helper:
{{currentUser.profile.first_name}}
for the customers, you can loop over the users returned by your helper. i would rename the helper:
Template.customerDetails.helpers({
customers(){
return Meteor.users.find({});
}
});
then you can loop over them, in Blaze, like this:
{{#each customer in customers}}
{{customer.profile.first_name}}
{{else}}
No customers found.
{{/each}}
note that you don't need any other helpers to make that work.
c.f. http://blazejs.org/guide/spacebars.html#Each-in
To get the currently logged-in user, you could use: Meteor.user():
//...
user: function() {
return Meteor.user();
},
//...

Associate Lists and Tasks in Meteor todo

I'm building the todo application from the Meteor tutorial and continue it. I'm building some lists based on the task model, but I don't know how to join them and say when I click on one list, I want all the tasks from this one.
For the moment, I have the Tasks.js with:
'tasks.insert'(text, privacy, priority, listId) {
...
Tasks.insert({
text,
listId: listId,
owner: this.userId,
username: Meteor.users.findOne(this.userId).username,
});
},
Body.js
Template.body.events({
'submit .new-task' (event) {
event.preventDefault();
const listId = ???
const target = event.target;
const text = target.text.value;
...
Meteor.call('tasks.insert', text, privacy, priority, listId);
...
},
And then where I display it:
Template.body.helpers({
tasks() {
const instance = Template.instance();
if (instance.state.get('hideCompleted')) {
return Tasks.find({ checked: { $ne: true } }, { sort: Session.get("sort_order") });
}
return Tasks.find({}, { sort: Session.get("sort_order")});
},
lists() {
return Lists.find({}, { sort: { createdAt: -1 } });
},
I my body.html, I just display each items (lists and tasks) separately. But the problem is I don't know how to make the relation between both ...
Can you help me please ?
Thanks a lot
I see you are already using Session. Basically, you will use a Session variable that tracks what the list the user has selected, and then filter your tasks with that variable.
In your body, where you're displaying your list names, add the list's id as an HTML attribute:
{{#each lists}}
<a href='#' class='list-name' data-id='{{this._id}}'>
{{this.name}}
</a>
{{/each}}
Add an event for clicking on a list name that saves its id to a Session variable:
Template.body.events({
'click .list-name' (event) {
event.preventDefault();
Session.set('listId', event.currentTarget.attr('data-id'))
}
})
In your tasks helper, filter your query using the Session variable:
return Tasks.find(
{ listId: Session.get('listId') },
{ sort: Session.get("sort_order") }
);
Let me know if anything could be more clear.

Meteor Blaze order sub-documents by sub-document property

Profile:
_id: Pe0t3K8GG8,
videos: [
{id:'HdaZ8rDAmy', url:'VIDURL', rank: 2},
{id:'22vZ8mj9my', url:'VIDURL2', rank: 0},
{id:'8hyTlk8H^6', url:'VIDURL3', rank: 1},
]
The profile is displayed together with the list of videos. I have a Drag & Drop which updates the videos rank using a Server Method.
1) the database updates correctly on Drop.
2) To sort the videos Array - I declare a helper on the Profile Template and SORT the videos array based on a custom comparison function.
Template.Profile.helpers({
'videosSorted': function(){
let videos = (this.videos);
let videosSorted = videos.sort(function(a, b) {
return parseFloat(a.rank) - parseFloat(b.rank);
});
return videosSorted;
}
});
Problem:
A) In Blaze the {{#each videosSorted}} does not reactively update.
If I F5 refresh then i can see the new order.
I think the issue is because I am providing videosSorted which does not update on changes to the document in the db.
How can I make videosSorted reactive?
Update:
All related code:
Iron Router Controller - I subscribe and set the data context for the layout
ProfileController = RouteController.extend({
subscriptions: function() {
this.subscribe('profile',this.params.slug).wait();
},
data: function () {
//getting the data from the subscribed collection
return Profiles.findOne({'slug':this.params.slug});
},
})
Publication:
Meteor.publish('profile', function (slug) {
const profile = Profiles.find({"slug":slug});
if(profile){
return profile;
}
this.ready();
});
The Profile HTML template:
<template name="Profile">
<ul class="sortlist">
{{#each videosSorted}}
{{> Video}}
{{/each}}
</ul>
</template>
I am using mrt:jquery-ui - sortable function
Template.Profile.onRendered(function () {
thisTemplate = this;
this.$('.sortlist').sortable({
stop: function(e, ui) {
el = ui.item.get(0);
before = ui.item.prev().get(0);
after = ui.item.next().get(0);
if(!before) {
newRank = Blaze.getData(after).rank - 1
} else if(!after) {
newRank = Blaze.getData(before).rank + 1
}
else {
newRank = (Blaze.getData(after).rank +
Blaze.getData(before).rank) / 2
}
let queryData = {
_id: thisTemplate.data._id, //the id of the profile record
videos_objId: Blaze.getData(el).objId, //the id of the sub document to update
new_rank: newRank //the new rank to give it
};
//Update the sub document using a server side call for validation + security
Meteor.call("updateVideoPosition", queryData, function (error, result) {
if(!result){
console.log("Not updated");
}
else{
console.log("successfully updated Individual's Video Position")
}
});
}
})
});
And finally the Meteor method that does the updating
'updateVideoPosition': function (queryData){
let result = Individuals.update(
{_id: queryData._id, 'videos.objId': queryData.videos_objId },
{ $set:{ 'videos.$.rank' : queryData.new_rank } }
)
return result;
}
Note :
As i mentioned - the database updates correctly - and if i have an Incognito window open to the same page - i see the videos reactivly (magically !) switch to the new order.
The schema
const ProfileSchema = new SimpleSchema({
name:{
type: String,
}
videos: {
type: [Object],
optional:true,
},
'videos.$.url':{
type:String,
},
'videos.$.rank':{
type:Number,
decimal:true,
optional:true,
autoform: {
type: "hidden",
}
},
'videos.$.subCollectionName':{
type:String,
optional:true,
autoform: {
type: "hidden",
}
},
'videos.$.objId':{
type:String,
optional:true,
autoform: {
type: "hidden",
}
}
});
I came up with really crude solution, but I don't see other options right now. The simplest solution I can think of is to rerender template manually:
Template.Profile.onRendered(function () {
var self = this;
var renderedListView;
this.autorun(function () {
var data = Template.currentData(); // depend on tmeplate data
//rerender video list manually
if (renderedListView) {
Blaze.remove(renderedListView);
}
if (data) {
renderedListView = Blaze.renderWithData(Template.VideoList, data, self.$('.videos-container')[0]);
}
});
});
Template.VideoList.onRendered(function () {
var tmpl = this;
tmpl.$('.sortlist').sortable({
stop: function (e, ui) {
var el = ui.item.get(0);
var before = ui.item.prev().get(0);
var after = ui.item.next().get(0);
var newRank;
if (!before) {
newRank = Blaze.getData(after).rank - 1
} else if (!after) {
newRank = Blaze.getData(before).rank + 1
}
else {
newRank = (Blaze.getData(after).rank +
Blaze.getData(before).rank) / 2
}
let queryData = {
_id: tmpl.data._id, //the id of the profile record
videos_objId: Blaze.getData(el).objId, //the id of the sub document to update
new_rank: newRank //the new rank to give it
};
//Update the sub document using a server side call for validation + security
Meteor.call("updateVideoPosition", queryData, function (error, result) {
if (!result) {
console.log("Not updated");
}
else {
console.log("successfully updated Individual's Video Position")
}
});
}
});
});
Template.VideoList.helpers({
videosSorted: function () {
return this.videos.sort(function (a, b) {
return a.rank - b.rank;
});
}
});
And HTML:
<template name="Profile">
<div class="videos-container"></div>
</template>
<template name="VideoList">
<ul class="sortlist">
{{#each videosSorted}}
<li>{{url}}</li>
{{/each}}
</ul>
</template>
Reativeness was lost in your case because of JQuery UI Sortable. It doesn't know anything about Meteor's reactiveness and simply blocks template rerendering.
Probably you should consider using something more adopted for Meteor like this (I am not sure it fits your needs).

Displaying all users in Meteor

I have a template that I am trying to display all users in called userList.
//server
Meteor.publish("userList", function() {
var user = Meteor.users.findOne({
_id: this.userId
});
if (Roles.userIsInRole(user, ["admin"])) {
return Meteor.users.find({}, {
fields: {
profile_name: 1,
emails: 1,
roles: 1
}
});
}
this.stop();
return;
});
Thanks in advance for the help!
if you want show all the user you can try in your publish.js file:
Meteor.publish('userList', function (){
return Meteor.users.find({});
});
in your router you susbcribe to this
Router.route('/users', {
name: 'usersTemplate',
waitOn: function() {
return Meteor.subscribe('userList');
},
data: function() {
return Meteor.users.find({});
}
});
The next step is iterate your data in the template.
if you don't want subscribe in the router, you can subscribe in template level, please read this article for more details.
https://www.discovermeteor.com/blog/template-level-subscriptions/
Regards.
This should work!
// in server
Meteor.publish("userList", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client
Meteor.subscribe("userList");
This should work.
subscribe(client)
publish(server)
Client:
UserListCtrl = RouterController.extend({
template: 'UserList',
subscriptions: function () {
return Meteor.subscribe('users.list', { summary: true });
},
data: function () {
return Meteor.users.find({});
}
});
Server:
Meteor.publish('users.list', function (options) {
check(arguments, Match.Any);
var criteria = {}, projection= {};
if(options.summary){
_.extend(projection, {fields: {emails: 1, profile: 1}});
}
return Meteor.users.find(criteria, projection);
});

Categories

Resources