meteor reactivity breaks when using server/client folders - meteor

So I made something super simple to test out the reactivity in Meteor but when I came to make a server and client folder the reactivity broke. I can no longer manually edit the database and see the change instantly in the browser.
Template:
<template name="hello">
<input type="button" value="Click" />
{{#each tt}}
{{test}}
{{/each}}
</template>
client/test.js:
Template.hello.events(
{
'click input': function ()
{
Meteor.call('set');
}
});
Template.hello.helpers(
{
tt: function()
{
Meteor.call('get', function(error, result)
{
Session.set('aa', result);
});
return Session.get('aa');
}
});
server/testS.js:
Test = new Meteor.Collection("test");
Meteor.methods(
{
set: function()
{
Test.insert({test: "test 1"});
},
get: function()
{
return Test.find().fetch();
}
});
What am I missing to get reactivity using this folder structure?

The following is an issue.
Meteor.call('get', function(error, result) {
Session.set('aa', result);
});
This only occurs once in your case. Meteor.call is generally meant as a singular request, and is completely different than the publication/subscription model. The only "reactivity" you would experience in this case is if you manually do Session.set('aa', result);
If you want reactivity between the client/server DB, you need to set up publication/subscription code (see http://docs.meteor.com/#meteor_publish). By default, all documents in the database are published to the client via the auto-publish package, so keep that in mind. This is to auto-allow you do stuff like Collection.find() on the client, which returns a cursor, and is reactive by default.
In other words, your Meteor.call is redundant. The Test collection already exists on the client, allowing you to do the following.
Template.hello.helpers({
tt: function() {
return Test.find();
}
});

Related

meteor: publication / subscription not working

I can't for the life of me figure out why nothing shows up client-side in this meteor app. I have tried all the advise in all the related topics and nothing seems to work. I'm using msavin:mongol and I don't even see the subscription on the client at all, despite console.log() debug output indicates that it is there with the current number of entries.
/imports/api/friends.js:
export const Friends = new Mongo.Collection('friends');
Friends.deny({ insert() { return true; }, update() { return true; }, remove() { return true; } }); // access to collections only through method calls
/imports/api/server/friends.js:
import { Meteor } from 'meteor/meteor';
import { Friends } from '../friends.js';
Meteor.publish('friends.all', function(){
return Friends.find();
})
/imports/ui/pages/friends.js:
import { Friends } from '/imports/api/friends.js';
import './friends.html';
Template.friends.onCreated(function() {
this.subscribe('friends.all');
});
Template.friends.helpers({
friends: ()=>{ return Friends.find(); }
});
/imports/ui/pages/friends.html:
<template name="friends">
<h1 class="ui header">Friends</h1>
{{#if Template.subscriptionsReady}}
<h2 class="ui heder">friends list:</h2>
<div class="ui list">
{{#each friend in friends}}
<div class="item">{{friend.name}} ({{friend.email}})</div>
{{/each}}
</div>
{{/if}}
</template>
The "friends list" header shows up, so the subscriptionsReady call returns, but I don't get any data (verified that data exists in the database).
I've also tried moving the subscription into the router (using ostrio:flow-router-extra) and there the waitOn() function never returns when I add the subscription
What is going on here?
If you are missing to include your publication on the server then your client's subscription will immediately be 'ready' but there will be no error message.
This can cause a lot of confusion when creating templates with template-level-subscriptions.
In order to check if a publication exists or not, you can use on the server after start the (undocumented) server.publish_handlers array. It keeps a record of the registered publications.
Meteor.startup( () => {
console.log( Meteor.server.publish_handlers );
});
This comes in very handy, if you have designed your api in way that it keeps track of its intended publications:
friendsdef.js
// definition file for Friends collection and it's
// surrounding functionality
export const FriendsDef = {
collectionName: 'friends',
schema: { ... },
methods: { ... },
publications: {
all: {
name: 'friends.all'
}
}
}
someServerStartup.js
// make sure on startup, that all intended
// publications are registered
Meteor.startup( () => {
Object.values( FriendsDef.publications).forEach( pub => {
if (!Meteor.server.publish_handlers[pub.name]) {
throw new Error('publication should exist, but does not');
}
});
});
The same works with method_handlers for methods. Use these data structures with a well defined API and you will decrease errors of missing includes or misspelled names a lot.

Meteor: accessing user details on client [duplicate]

This question already has answers here:
How to use Meteor methods inside of a template helper
(6 answers)
Closed 6 years ago.
I'm trying to access (another) user's details on the client side in meteor. I have a server side method called 'userDetails' that I'm calling from a template helper called 'acc'.
Server method:
'userDetails': function(userId) {
check(userId, String);
return Meteor.users.findOne({_id: userId},
{fields: {
"services.facebook.first_name": 1,
"profile.birthday": 1,
"services.facebook.gender": 1,
"profile.location.name": 1
}});
}
Template helper:
acc: function(_id) {
Meteor.call('userDetails', _id, function(err, res) {
if(err) throw error;
return res;
});
}
When I try to access acc.profile.birthday in the template I don't get anything. What could cause this?
Meteor calls are asynchronous calls, that is why your helper is not returning any data.
Best option here is to either use Session or ReactiveVar or ReactiveDict
I'll use Session option here
acc: function(_id) {
Meteor.call('userDetails', _id, function(err, res) {
if(err){
}else{
Session.set('userDetails', res)
}
});
return Session.get('userDetails')
}
In your html you can use this helper like this
{{#if acc}}
{{name}}
...
{{else}}
<p>Information not found</p>
{{/if}}
You have to wrap the return in a else statement.
if(error) {
}
else {
return res;
}
The call to you method in asynchronous. This means that the callback function will be executed when your server method completes.
If you want to display the result on a template, you have two possibilities:
1/ Use a session.
acc: function(_id) {
Meteor.call('userDetails', _id, function(err, res) {
if(err){
}else{
Session.set('data', res)
}
});
return Session.get('data')
}
2/ Use Template subscriptions (better solution):
On the server, you publish the data:
Meteor.publish("data", function(){
return Meteor.users.findOne(...)
});
On the client, you subscribe:
Template.mytemplate.onCreated(function () {
Template.instance().subscribe("data");
});
Then directly on the client you will be able to create a helper and call the findOne.
In the html:
{{#if Template.subscriptionsReady}}
{{#each myHelper}}
{{acc.profile.birthday}}
{{/each}}
{{else}}
<p>Loading...</p>
{{/if}}
Important Notice about users:
Users profile are editable by default. Please read this: https://dweldon.silvrback.com/common-mistakes

How to call function every time collection updates from server?

For example, in this controller parties object autoupdates when another user changes Paries collection, but how do we catch this update and run some controller logic?
this.helpers({ parties: () => Parties.find({}) });
SPECIFIED QUESTION:
Recieved answers do not solve my problem as it's not server logic or any database manipulations that I need to perform upon update. Actually it's controller logic that I need to run.
In the following example I disable submit button if form is not changed. isFormChange function compares party with originalParty. I need to redefine originalParty value when party changes from server side. So how can I do this?
<form ng-submit="vm.updateParty()">
<input type="text" ng-model="vm.party.name">
<input type="submit" ng-disabled="!vm.isFormChanged()" value="Submit">
</form>
directive controller:
function Ctrl($scope, $reactive) {
let vm = this;
$reactive(vm).attach($scope);
vm.helpers({ party: () => Parties.findOne({_id: vm.partyId}) });
let originalParty = angular.copy(vm.party);
vm.isFormChanged = isFormChanged;
vm.updateParty = updateParty;
function isFormChanged() {
return !angular.equals(vm.party, originalParty);
}
function updateParty() {
Meteor.call('updateParty', vm.party._id, vm.party.name);
}
}
Use the collection-hooks package to run code before or after updates, upserts, inserts, and removes.
myCollection.after.update(userId, doc, fieldNames, modifier, options){
...your code
}
You can run your logic on the server, if you have your database functionality in Meteor methods.
Example:
Parties = new Mongo.Collection("parties");
if (Meteor.isClient) {
// This code only runs on the client
Meteor.subscribe("parties");
/* a helper or event can run the Meteor.call() */
var partyData = {}; // get partyData
Meteor.call("insertParty", partyData,
// callback function
function (error, result) {
if (error) { console.log(error); };
});
}
if (Meteor.isServer) {
// This code only runs on the server
Meteor.publish("parties", function () {
return Parties.find({});
});
Meteor.methods({
insertParty: function (partyData) {
// insert logic to run on partyData here
Parties.insert(partyData);
}
})
}

Meteor publish ALL Users does not seem to work (or even get called?)

I have the following in server/publications.js...
Meteor.publish("users", function(){
return Meteor.users.find({}, {fields: {profile: 1}});
});
... and in my iron router route...
Router.route('/', function() {
this.layout('ConfLayout');
this.render('UserList', {
waitOn: function () {
return Meteor.subscribe("users");
},
data: function () {
return {
users: function () {
return Meteor.users.find();
}
};
}
});
});
...then in my template....
<template name="UserList">
<h1>Users</h1>
<ul>
{{#each users}}
<li>
{{#linkTo route='user.show'}}
<div>
{{profile.lastName}}, {{profile.firstName}}
</div>
{{/linkTo}}
</li>
{{/each}}
</ul>
</template>
...and it sort of works except the only user on the client is the currently logged in user. I am trying to get a list of ALL users for admins (don't worry about the for admins part for now).
What is also odd is that is I add a console.log statement to the publish function it never gets logged. However all the other publications in the same file seem to work fine. Also if I enable autopublish, then all the users show up as expected.
What an I missing here? Based on all I could find it seems like publishing specific fields should work for displaying all users in the client, but it almost seems like Meteor is ignoring any publications on Meteor.users altogether. I am using Meteor 1.1.0.3.
Any thoughts or help appreciated!
TIA
OK... not entirely sure why, but I suspect I may have been missing a "this" somewhere or something, but if I change the route to not use a function as the 2nd param and just pass options and then leave EVERYTHING else EXACTLY the same, it works...
Router.route('/', {
waitOn: function () {
return Meteor.subscribe('users');
},
data: function () {
return {
users: function () {
return Meteor.users.find();
}
};
},
template: 'UserList',
layoutTemplate: 'ConfLayout'
});

Meteor: Prevent Live Update/Re-Rendering of one section

In Meteor, how can I prevent certain collection fields or certain portions of the page from being affected by the Live Updating/Re-Rendering system?
I found some answers such as {{#constant}} and {{#isolate}} but they are depreciated now.
Also reactive: false doesn't seem to work for me either.
You could try Tracker.nonreactive. That lets you access it a reactive data source non-reactively. For example:
<template name="tmplt">
The counter is {{getCounter}}.
</template>
Template.tmplt.helpers({
getCounter: function () {
return Tracker.nonreactive(function () {
return Session.get("counter");
});
}
});
Changes to the "counter" session variable will not cause tmplt to update.
When passing a database query to #each, make sure you call fetch:
// Don't do this
return Tracker.nonreactive(function () {
return Collection.find(...);
});
// Do this
return Tracker.nonreactive(function () {
return Collection.find(...).fetch();
});

Resources