This is further information from a previous submission but I thought it would be clearer if I posted this separately.
A helper is returning a collection query:
Template.clientGrid.helpers({
'programs': function () {
var fullNameP = Session.get('clientName');
return Programs.find({FullName: fullNameP});
}
});
In the template it's printing out properties from 'programs'. For example:
...
{{#each programs}}
<p>{{formatCampYear CampYear}}: {{formatNotes Notes}}</p>
{{/each}}
....
Nothing special going on. So, if the FullName is Jane Doe, and she's got 6 documents in the programs collection, it will print the six properties in the template. But the page is getting caught in a while-loop inside Tracker (see line 449 the while-loop 'recompute all pending computations') after the properties finish printing. The CPU is tied up and prevents certain page operations. If any of you harder-core guys and gals have any clue as to what this means, perhaps I can sleuth out the problem. Here's a copy of the while loop itself (just in isolation):
// recompute all pending computations
while (pendingComputations.length) {
var comp = pendingComputations.shift();
comp._recompute();
if (comp._needsRecompute()) {
pendingComputations.unshift(comp);
}
if (! options.finishSynchronously && ++recomputedCount > 1000) {
finishedTry = true;
return;
}
}
EDIT: Here's the event map that setting the session. There doesn't seem to be anything suspicious. Since I'm pre-production, I'm not doing any updates to the collection. It's pretty much just static at this point.
Template.clientSearchButton.events({
'click #client-search-button': function(event) {
event.preventDefault();
var clientFullName = document.getElementById('full-name').value.toUpperCase();
Session.set('clientName', clientFullName);
mapAddress = Demographic.find({ "FullName": clientFullName }).map(function (a) { return (a.Address + " " + a.City + " " + a.State + " " + a.Country); });
Meteor.myFunctions.initialize();
}
});
Related
I searched Stackoverflow for an answer to my question: How to save dropped external events immediately to the database. Adding and updating events through the dialog works fine. Dragged external events are rendered fine.
This is the code I use in the eventReceive function. The first alert to show the event data is correct, but the second is never reached.
eventReceive: function (event, delta, revertFunc) {
alert(event.title + " was dropped on " + event.start.format()); //REPLACE WITH AJAX TO SAVE EVENT DATA
var eventToAdd = {
title: event.title,
description: "Unknown",
start: event.start.format,
end: event.end.format,
allDay: isAllDay(event.StartDate, event.EndDate)
};
if (checkForSpecialChars(eventToAdd.title) || checkForSpecialChars(eventToAdd.description)) {
alert("please enter characters: A to Z, a to z, 0 to 9, spaces");
}
else {
alert(event.title + " was dropped on " + event.start.format());
PageMethods.addEvent(eventToAdd, addSuccess);
}
},
I digged somewhat deeper and as far as I can tell, after the var eventToAdd JQuery 3.3.1 triggers the same functions over and over again as soon as the mouse is hovered over any element in the page. Functions involved are: matchFromGroupMatchers, elementmatcher, prefilter and Sizzle. The javascript of fullcalendar does not resume.
It seems the variable eventToAdd was in use. Changing it to a different name solved it. I have it now this way:
eventReceive: function (event) {
// alert(event.title + " was dropped on " + event.start.format());
var eventAdd = {
start: event.start.format(),
end: event.end.format(),
title: event.title,
description: "Onbekend",
hwType: "Proefwerk",
};
PageMethods.addEvent(eventAdd, addSuccess);
},
I have angular-meteor app that needs Material md-autocomplete from a collection with 53,296 documents with angularUtils.directives.dirPagination but this amount of data make my browser hang.
I'm publishing the collection with:
Meteor.publish('city', function (options, searchString) {
var where = {
'city_name': {
'$regex': '.*' + (searchString || '') + '.*' ,
'$options': 'i'
}
};
return City.find(where, options);
});
I subscribe with:
subscriptions: function () {
Meteor.subscribe('city');
this.register('city', Meteor.subscribe('city'));
}
and have pagination on controller :
$scope.currentPage = 1;
$scope.pageSize = 100;
$scope.sort = {city_name_sort : 1};
$scope.orderProperty = '1';
$scope.helpers({
city: function(){
return City.find({});
}
});
but it takes a long time to load and its make chrome stop working.
You already have most of the server-side searching done because your search is running inside a subscription. You should make sure that the city_name field is indexed in mongo! You should only return that field to minimize data transfer. You can also simplify your regex.
Meteor.publish('city', function (searchString) {
const re = new RegExp(searchString,'i');
const where = { city_name: { $regex: re }};
return City.find(where, {sort: {city_name: 1}, fields: {city_name: 1}});
});
What I've found helps with server-side auto-complete is:
Don't start searching until the user has typed 3 or 4 characters. This drastically narrows down the search results.
Throttle the search to only run every 500ms so that you're not sending every character to the server because then it has to keep re-executing the search. If the person is typing fast the search might only run every 2 or 3 characters.
Run the same .find() on the client that you're running on the server (instead of just querying for {}). That's just good practice since the client-side collection is the union of all subscriptions on that collection, there might be documents there that you don't want to list.
Lastly I don't know why you're subscribing twice here:
subscriptions: function () {
Meteor.subscribe('city');
this.register('city', Meteor.subscribe('city'));
}
only one of those Meteor.subscribe('city') calls is necessary.
hope my first question here is not a stupid one.
Lets say we want to build a chat application with meteor, with logedin and anonymous users. The chat should be filled like that:
var post = {
userId: user._id, // is empty if anonymous user
submitted: new Date().getTime(),
text: chat_message
});
var postId = Posts.insert(post);
The publication could looks like this to make sure that the userId is not transfered
Meteor.publish('getTheChat', function() {
return Post.find({}, {fields: {userId: false});
});
But is there a way to add a field in the returned collection dynamically?
The userId should not be published but a status like "Your_Post","another_logedin_user" or "an_anonymous_user". By having that, I could include some css, so the chat looks a little bit more like WhatsApp or iMessage.
The logic inside the publish method could be something like
if (userId == this.userId) {
status = "Your_Post";
} else if (userId != null) {
status = "another_logedin_user";
} else {
status = "an_anonymous_user";
}
You see, the publication should include different values when called from different users. Is there a way in Meteor.publish?
Thanks for any insight or suggestions
Thank you both for your ideas! But as I had to find out (just for my inward peace) how it is possible inside the publish method server sided, I came, with the help of David's link, to this solution -maybe it will help someone later:
Meteor.publish('getTheChat', function(postId) {
var currentUserId = this.userId;
var ownerUserId = Posts.findOne({'_id':postId}).userId;
var findOptions = {}; // in my final coding these differ for 'chat room owners' and 'normal users'
var transform = function(post) {
if (currentUserId && post.userId == currentUserId) {
post.userId = "posted by you";
} else if (post.userId == null) {
post.userId = "anonym posted";
} else if (post.userId == ownerUserId) {
post.userId = "posted by owner";
} else {
post.userID = "posted by another loged in";
return post;
};
var self = this;
var handle = Posts.find(findOptions).observe({
added: function (document) {
self.added('posts', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('posts', document._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('posts', oldDocument._id);
}
});
self.ready();
self.onStop(function(){
handle.stop();
});
By having this I am finally able to overwrite values dynamically.
It looks like you need to add a transform on your Posts collection. You can do this in your publish function (as seen here), but server-side transforms are computationally inefficient and tricky to write. Though they are necessary in cases where only the server could perform the action - e.g. signed URLs. In your case, I'd recommend a standard collection transform which is a filter applied after the documents are fetched.
Unfortunately, this kind of transform would require the userId on the client. I've never seen a case where simply publishing a id could cause a security issue. If you believe this is the case with your app, I'm very interested to know why. If you can overcome this restriction, keep reading...
You can read about transforms in the documentation on collections, and you can see an example on evented mind. Personally I like to use the collection-helpers package for this.
If you try collection-helpers, your transform could look like:
Posts.helpers({
status: function() {
if (this.userId === Meteor.userId()) {
return 'Your_Post';
} else if (this.userId != null) {
return 'another_logedin_user';
} else {
return 'an_anonymous_user';
}
}
});
And then you could use it in your template like:
{{#each posts}}
<p>{{text}} - <span class='status'>{{status}}</span></p>
{{/each}}
Of course, you can also use template helpers to achieve the same result but transforms are more easily reusable across your application.
Sadly, this has been a huge issue for me too, and I am sorry to say, it is not technically possible to just add a field on the publisher's query and use it conveniently in your view. BUT, I have a solution that may work for you. It will also give you an idea of how complex it can become as soon as you want to keep some reactive data private in Meteor.
Server side:
First, create two different publishers: one for the current user's posts, one for all the others:
Meteor.publish('getTheOthersChat', function() {
return Post.find({$ne:{userId: this.userId}}, {fields: {userId: false});
});
Meteor.publish('getTheOwnChat', function() {
return Post.find({userId: this.userId});
});
Client/router side:
Then, subscribe to both of these: what this will do is include the post's userId only when it is the own user's id. If not, it'll be undefined.
Then, we still need to identify the case "anonymously posted" vs "posted by user". For this, you can add another field during the post creation, for example is_anonymous, which you then set to true or false depending on the case if the user is logged in or not. The check would then be:
if (userId) {
status = "Your_Post";
} else if (is_anonymous === false) {
status = "another_logedin_user";
} else {
status = "an_anonymous_user";
}
This solution should work. I know, it is sad to have to come to this kind of means. It makes Meteor look clunky and impractical for tasks that should be dead easy. Such a shame for such a cool framework!
General question: in Meteor, what's the best way to implement business logic that triggers whenever a model is updated -- e.g., for updating dependent fields or validations or...
Specific example: I'd like to add a "slug" field to Lists collection in the Meteor todos example. The slug needs to automatically update whenever a list's name is changed.
Here's what I've got... I'm observing every change to a list to see if its slug needs to be created/updated. This is in a shared models.js (runs server and client-side, to get the benefits of latency compensation):
// Lists -- {name: String}
Lists = new Meteor.Collection("lists");
var listsObserver = Lists.find().observe({
added: updateSlug,
changed: updateSlug
});
function updateSlug(doc, idx) {
var slug = (doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + doc.name + "' to " + slug);
Lists.update(doc._id, {$set: {slug: slug}});
}
}
(And as in the original todos example, server/publish.js publishes all of Lists.find() as "lists", and client/todos.js subscribes to that collection.)
The code above seems to work, but somehow doesn't look quite right to me. Questions:
Is observing the Lists collection like this a reasonable approach? It seems like
it could be inefficient -- any change to a Lists document will trigger this code.
Should I be doing a different (simulated) update client-side, or is it OK to let
this same Mongo/Minimongo update run on both?
Do I need to call listsObserver.stop() at some point to dispose the observer?
And if so, when?
(I'm just getting started with Meteor, so perhaps my biases from other environments are leaking through. The implied meta-question here is, am I even thinking about this problem in the right way?)
I would suggest using the Collection-Hooks package. It extends the collection operations with before and after hooks. This is better than having a lot of collection Observes or ObserveChanges, especially on the server where the overhead for collection observes can get very large.
This works on both the client and the server. If you implement it on the client you will get the benefit of updating the local collection (latency compensation) and the change will be pushed to the server so no need to do it again.
You also get the benefit of only doing one MongoDB operation instead of two or more like you would with observes or observeChanges.
You might use it like so:
var beforeInsertSlug = function(userId, doc) {
var slug = (doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + doc.name + "' to " + slug);
doc.slug = slug;
}
};
var beforeUpdateSlug = function(userId, doc, fieldNames, modifier, options){
if(modifier && modifier.$set && modifier.$set.doc && _.isString(modifier.$set.doc.name)){
var slug = (modifier.$set.doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + modifier.$set.doc.name + "' to " + slug);
modifier.$set.doc.slug = slug;
}
}
};
Lists.before.insert(beforeInsertSlug);
Lists.before.update(beforeUpdateSlug);
You can find the package here: https://atmospherejs.com/matb33/collection-hooks
I did a similar thing in server code. Basically put this code in Meteor.methods(), along with any other checks and updates you want making to the Lists Collection.
Although the code below looks a bit messy, and certainly hard to understand with the line starting with var slug:
Meteor.methods({
myupdate: function (doc) {
var slug = (doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + doc.name + "' to " + slug);
Lists.update(doc._id, {$set: {slug: slug}});
}
}
});
One way to implement this is to define a custom template function and trigger it in the template that is changing. For example:
In client.js
Template.myTemplate.custom_function_to_update = function() {
// do my update code. i.e. MyCollections.Update(...);
}
In the html file with the template
<template name="myTemplate">
<!-- Normal template code -->
{{ custom_function_to_update }}
</template>
and every time the template "myTemplate" updates, it will call your method.
I've been scratching my head as to why this code will work some of the time, but not all (or at least most of the time). I've found that it actually does run displaying the correct content in the browser some of the time, but strangely there will be days when I'll come back to the same code, run the server (as per normal) and upon loading the page will receive an error in the console: TypeError: 'undefined' is not an object (evaluating 'Session.get('x').html')
(When I receive that error there will be times where the next line in the console will read Error - referring to the err object, and other times when it will read Object - referring the data object!?).
I'm obviously missing something about Session variables in Meteor and must be misusing them? I'm hoping someone with experience can point me in the right direction.
Thanks, in advance for any help!
Here's my dummy code:
/client/del.html
<head>
<title>del</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
Hello World!
<div class="helloButton">{{{greeting}}}</div>
</template>
My client-side javascript file is:
/client/del.js
Meteor.call('foo', 300, function(err, data) {
err ? console.log(err) : console.log(data);
Session.set('x', data);
});
Template.hello.events = {
'click div.helloButton' : function(evt) {
if ( Session.get('x').answer.toString() === evt.target.innerHTML ) {
console.log('yay!');
}
}
};
Template.hello.greeting = function() {
return Session.get('x').html;
};
And my server-side javascript is:
/server/svr.js
Meteor.methods({
doubled: function(num) {
return num * 2;
},
foo: function(lmt) {
var count = lmt,
result = {};
for ( var i = 0; i < lmt; i++ ) {
count++;
}
count = Meteor.call('doubled', count);
result.html = "<em>" + count + "</em>";
result.answer = count;
return result;
}
});
I think it's just that the session variable won't be set yet when the client first starts up. So Session.get('x') will return undefined until your method call (foo) returns, which almost certainly won't happen before the template first draws.
However after that it will be in the session, so things will probably behave right once you refresh.
The answer is to just check if it's undefined before trying to access the variable. For example:
Template.hello.greeting = function() {
if (Session.get('x')) return Session.get('x').html;
};
One of the seven principles of Meteor is:
Latency Compensation. On the client, use prefetching and model simulation to make it look like you have a zero-latency connection to the database.
Because there is latency, your client will first attempt to draw the lay-out according to the data it has at the moment your client connects. Then it will do the call and then it will update according to the call. Sometimes the call might be able to respond fast enough to be drawn at the same time.
As now there is a chance for the variable to not be set, it would throw an exception in that occasion and thus break down execution (as the functions in the call stack will not continue to run).
There are two possible solutions to this:
Check that the variable is set when using it.
return Session.get('x') ? Session.get('x').html : '';
Make sure the variable has an initial value by setting it at the top of the script.
Session.set('x', { html = '', answer = ''});
Another approach would be to add the templates once the call responds.
Meteor.call('foo', 300, function(err, data) {
Session.set('x', data);
$('#page').html(Meteor.ui.render(function() {
return Template.someName();
}));
});