I'm trying to save user content on blur and am encountering a weird UI freeze after the save that I've never seen before.
A simplified version of the template:
{{#each UserSession.getQuestions}}
<textearea class='question'>{{UserSession.getVal this._id}}</teaxtarea>
{{/each}}
In other words, we iterate over a cursor from the Questions collection that selects all questions a user must answer, and we populate the textarea with a value from the UserSessions collection that contains this user's response to that question. For what it's worth, the textarea is actually in its own template (there are other things to show in there so i figured best to keep them isolated).
The user can modify the content of his response, and then I have an event handler that basically does this:
"blur .question": function(e) {
var val = $(e.target).val();
var questionId = this._id;
var userSessionId = Meteor.user().getSessionId();
var modifier = {$set: {}};
modifier["$set"]["questions." + questionId + "] = val;
UserSessions.update({_id: userSessionId}, modifier);
}
The update succeeds but the browser freezes for about a second after the save executes, so that if the blur is triggered by a user clicking on another element, that element doesn't come into focus cleanly. This is a problem because I also want to do periodic saves while the user is typing, but when I do that, the hiccup interrupts the typing: the resulting experience is pretty brutal.
The interesting thing is that the browser only seems to freeze when the property being updated is an object or in a list. In other words:
UserSessions.update({_id: userSessionId}, {$set: {"questions.questionId": "someVal"}}) causes the freeze, as does UserSessions.update({_id: userSessionId}, {$set: {"lastSavedAt": new Date()}}).
However, UserSessions.update({_id: userSessionId}, {$set: {"someOtherProp": "someVal"}}) works fine.
I've tried several different approaches, including an async client-side pattern and executing the save via Meteor Method in a if (Meteor.isServer) block -- all the same result. Must be something to do with the publication updating, but it happens even when i set the publish function query to {reactive: false}.
I'm out of ideas. Your help, as always, is greatly appreciated.
Thanks in advance,
db
Ok I think I figured it out. For anyone else who has this problem, if you're using Iron Router you should check it for un-necessary dependencies. In this case it was the router being re-run that clogged up the works, because the data hook included a reactive find request on the UserSessions collection. I set reactive to false and specified the fields to prevent a recomputation, and everything smoothed itself out.
Related
I'm trying to follow the "Use the return value of a Meteor method in a template helper" pattern outlined here, except with collections.
Essentially, I've got something like this going:
(server side)
Meteor.methods({
queryTest: function(selector) {
console.log("In server meteor method...");
return MyCollection.find(selector);
}
});
(client side)
Meteor.call('queryTest', {}, function(error, results) {
console.log("in queryTest client callback...");
queryResult = [];
results.forEach(function(result) {
// massage it into something more useful for display
// and append it to queryResult...
});
Session.set("query-result", queryResult);
});
Template.query_test_template.helpers({
query_test_result: function() {
return Session.get("query-result");
}
});
The problem is, my callback (from Meteor.call) doesn't even get invoked.
If I replace the Method with just 'return "foo"' then the callback does get called. Also, if I add a ".fetch()" to the find, it also displays fine (but is no longer reactive, which breaks everything else).
What gives? Why is the callback not being invoked? I feel like I'm really close and just need the right incantation...
If it at all matters: I was doing all the queries on the client side just fine, but want to experiment with the likes of _ensureIndex and do full text searches, which from what I can tell, are basically only available through server-side method calls (and not in mini-mongo on the client).
EDIT
Ok, so I migrated things publish/subscribe, and overall they're working, but when I try to make it so a session value is the selector, it's not working right. Might be a matter of where I put the "subscribe".
So, I have a publish that takes a parameter "selector" (the intent is to pass in mongo selectors).
On the client, I have subscribe like:
Meteor.subscribe('my-collection-query', Session.get("my-collection-query-filter"));
But it has spotty behaviour. On one article, it recommended putting these on Templates.body.onCreate. That works, but doesn't result in something reactive (i.e. when I change that session value on the console, it doesn't change the displayed value).
So, if I follow the advice on another article, it puts the subscribe right in the relevant helper function of the template that calls on that collection. That works great, but if I have MULTIPLE templates calling into that collection, I have to add the subscribe to every single one of them for it to work.
Neither of these seems like the right thing. I think of "subscribing" as "laying down the pipes and just leaving them there to work", but that may be wrong.
I'll keep reading into the docs. Maybe somewhere, the scope of a subscription is properly explained.
You need to publish your data and subscribe to it in your client.
If you did not remove "autopublish" yet, all what you have will automatically be published. So when you query a collection on client (in a helper method for example), you would get results. This package is useful just for quick development and prototyping, but in a real application it should be removed. You should publish your data according to your app's needs and use cases. (Not all users have to see all data in all use cases)
This problem probably has been asked before, but I cannot seem to figure out an easy way to do it.
I have a Meteor page that shows messages posted by users in chronological order (newest message at the bottom). I want the page to:
#1) Subscribe to the messages (using publish/subscribe) based on a parameter supplied in the URL
#2) Render the page
#3) Display the messages on the page
#4) Scroll to the bottom.
There's no apparent way to know when 1, 2, and 3 are complete before initiating the scroll to bottom. Meteor does have an observe/added function to do an event when new messages are added to a subscription, however that's only when documents are insertted into Mongo, and it does not trigger when displaying results to the initial subscription.
I'll show code for the steps above:
#1: Subscribe to the messages using publish/subscribe: in /client/messages.js
Template.messages.created = function() {
this.autorun( function() {
this.subscription = Meteor.subscribe("messages", Router.current().params.category);
}.bind(this));
};
#2 Render the page, in /client/messages.html
<template name="messages">
{{#each messages}}
{{messageText}}<br><br>
{{/each}}
</template>
#3: Display the mssages on the page: /client/messages.js
Template.messages.helpers({
messages: function() {
var category = Router.current().params.category;
return Messages.find({category: category}, { sort: { messageDate: 1 } });
},
});
All this works, but does not automatically scroll to the bottom.
I cannot add a jquery scroll command to the Meteor onRendered section because it runs BEFORE the data is written to the DOM. I can do a Meteor.setTimeout to wait 1 second before scrolling to the bottom, but does not work if it takes longer than a second to fill the DOM with subscribed data.
Here's another thing complicating the matter. I am supplying the category variable in the URL. When the client selects another category, using Meteor/Blaze pathFor,
{{pathFor 'messages' channelid='new'}}
the browser does not reload/rerender the page, it simply updates the URL parameter which triggers the autorun to change what messages it has subscribed to. It simply writes the new data to the DOM. Because of this, I cannot to a jquery $(document).ready to detect whether the page is ready (because the page is always ready), and I cannot use some fancy handlebars thing like {{scrollToBottom}} at the end of my {{#each}} in messages.html because that it not re-run.
So, is there a simple way to detect when Meteor/Blaze completely finishes writing new data to the browser?
If I understand correctly, you really just want to know when all of your data is published from the server to the client (so you can then do something, in this case, scroll to the bottom).
All calls to Meteor.subscribe() return a subscription handle. This has a ready() method, that tells you all the data has been sent. This includes those done at a template level (which you might want to consider if appropriate for your use-case). More information here:
http://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
There are several ways to use this - you need to decide what is appropriate for you. As ready() is reactive, you can just use this. However, you might find it easier to implement an onReady callback within your subscription (see documentation above).
I suspect you will need to implement an autorun within your template rendered. This is to avoid the reverse scenario where the data arrives before the rendering (and so the callback fires too early). Something like this should do the trick (noting you have already set your subscription handle in your template creation function to this.subscription):
Template.messages.onRendered(function() {
var self = this;
this.autorun(function(computation) {
if(self.subscription.ready()){
$(document).scrollTop($(document).height());
computation.stop()
}
});
});
This should ensure both that the function is only called after rendering and the data from the subscription is complete. The autorun is also stopped after executing the scroll to stop continued calling should new documents cause ready() to no longer be truthy (and then become ready again) - which might surprise people with the page re-scrolling to the bottom!
I'm attempting to use observechanges with iron router but they don't seem to be compatible at all.
Router.route('/gaming', {
waitOn: function() {
return Meteor.subscribe('chat', function() {
window.chatmessagesCache = new ReactiveVar;
chatmessagesCache.set([]);
return chat.find().observeChanges({
added: function(id, doc) {
var tmpArr;
tmpArr = chatmessagesCache.get();
tmpArr.push(doc);
return chatmessagesCache.set(tmpArr);
}
});
});
}
If I leave the route and come back to it, observechanges begins being handled as many times as I've navigated away and returned, for each new record. What's the deal?
If I use subs manager it works as expected, but I don't understand why Meteor.subscribe inside waitOn is so cache/subscription unaware when it ALREADY gets called multiple times per load. Why!? I can't decipher what's causing this behavior at all.
Also, what I'm trying to accomplish is simple. I want to let chat messages that the user's client has received remain on the page even if the chat cursor is no longer publishing them (I'm publishing the last 10 chat messages)
Iron router has reactivity built in, which means when something inside your route function is invalidated, it will repeat the function as well as anything reactive with a Router.current(). These unexpected invalidation runs are a primary reason why folks made the exodus to flow router.
To solve this, you'll want to abstract your code away from the router. You can leave the sub, but I'd suggest you remove the sub's callback from the waitOn and move it into an onRendered callback. If you don't want the history loaded in chunks, you can first do a var collectionCount = chat.find({},{reactive:false}).count() on how many docs are in the collection & then in the added callback you can do something like if (++currentCount === collectionCount) /* add stuff */ to add al the records to the history when it reaches the last record.
On a bigger picture level, consider eliminating the observeChanges & just do an #each over the chat collection in spacebars to show your messages. Fewer cycles, cleaner code.
Iron router just has no management of observations you created yet it manages subscriptions itself, hence the multiple additions.
I figured this out by using a window level variable to check if I'm observing. Even in cases when the subscription is unhooked by iron, if I go back and never re-add the handler, the original observation hook still runs (!). ALSO, if you navigate away and drop the subscription, the handler is no longer called--which is the behavior I want in this scenario (This is all very insane behavior but at least it's now predictable to me )
This is caused by the fact that subscriptions != collections and the API for observations doesn't seem to expose any metadata, unfortunately, so I don't know how the iron router maintainers would account for this. Not to mention you return iron router a subscription, not a collection.
#Matt K if you were correct, this would always be an infinite loop (which admittedly I had a bunch of while trying to solve this) but the posted code is adding too many handlers, not looping indefinitely. Thanks for the post though.
This is what I settled on
Router.route('/gaming',
waitOn: ->
Meteor.subscribe('chat', ->
window.chatmessagesCache = new ReactiveVar(chat.find().fetch().reverse())
if !window.chatListening
window.chatListening = true
after = chat.find().count()
chat.find().observe(
added: _.after(after + 1,(doc) ->
tmpArr = chatmessagesCache.get()
tmpArr.push(doc)
chatmessagesCache.set(tmpArr))
changed : (id, doc) ->
))
I really just wanted to test out a pattern of locally "disconnected" documents. I still may use subs manager because it keeps subscriptions and their handlers alive without rerunning them constantly (which was rerunning the sub handler, which was adding multiple observations)
Here's my scenario. When child is added via a browser event post page load, I want to indicate it in the title. But on page load, child added is called as well.
How can I differentiate the initial child added vs one where a new entry has been actually added.
Thanks, Tim
Firebase very intentionally does not distinguish "initial" data from "new" data. This allows for much simpler development in most cases, because you only have to write one set of logic for your data, rather than handling both the initial data case and the new data case.
I can see how you would want the distinction in this case. I'm not sure exactly what you're doing, but if you're building a chat application, you might want to flash the title based on the timestamp of the most recent message rather than whether or not it's a "new" message. This would allow the title to flash on page load if a message was sent slightly before the page was loaded, which may be desirable. In some other cases, you may actually want to flash the title for unread data, and you may want to consider marking children as "read" and flashing the title only for children that show up without the "read" bit. This would allow things to work seamlessly across page refreshes.
If you absolutely need to know when "new" data shows up, you could try using "once" with a "value" event type to get the data, and then use "on" with a startAt query and a "child_added" event type to display new data after that. It would look something like this:
var data = new Firebase(...);
data.once("value", function(d) {
//TODO: display initial state...
data.startAt(null, <last id in snapshot>).on("child_added", function(newMessSnapshot) {
//TODO: render new child and flash title bar.
}
}
Or if you want to do it the really simple way, you could just set a timer so that the title won't flash for any messages received within the first N seconds of page load.
Hope that helps!
You can set up a call that only receives new events rather than ones already existing using the approach from this SO question.
So basically, call .on('child_added'...) with endAt and limit. You still get one entry (the last one), which you can just ignore:
// connect to firebase
var fb = new Firebase(...);
// retrieve the last record from `ref` (endAt() tells it to start at the end)
var first = true;
fb.child('table_name').endAt().limit(1).on('child_added', function(snapshot) {
if( first ) {
// ignore the first snapshot, which is an existing record
first = false;
return;
}
// all records after the last continue to invoke this function
console.log(snapshot.name(), snapshot.val());
});
I thought I would update Kato's answer as Datasnapshot.name() and limit have now both been depreciated.
I decided to make it a bit more simple as well ;)
// connect to firebase
var fb = new Firebase("https://your-firebase-ref/");
var firstTime = false;
fb.limitToLast(1).on('child_added', function(snapshot) {
if (firstTime) {
console.log(snapshot.key(), snapshot.val());
};
firstTime = true;
});
I hope this helps
Is there a way to subscribe to the Meteor Session object so that a reactive template view automatically renders when data is .set on the Session object? Specifically the key/name and value data?
I have a similar question related to rendering Meteor Session object data when iterated over. This question is specifically different on purpose. I want to get an answer out on an alternate way and possibly better way to do the same thing.
I do not want to have to call Session.get('name'); This use case is because I don't know the names in the Session object.
So, I would like to be able to have something in handlebars that allows me to
Psuedo code...
{{#each Session}}
{{this.key}} {{this.value}}
{{/each}}
Unsure about subscribing to the session, but for the second part of your question, you can use this:
Template.hello.session = function () {
map = []
for (prop in Session.keys) {
map.push({key: prop, value: Session.get(prop)})
}
return map
}
then in your template:
{{#each session}}
{{key}} {{value}}
{{/each}}
Not sure if there's a more elegant way but it works.
I don't think so.
Have a look at https://github.com/meteor/meteor/blob/master/packages/session/session.js. It's actually reasonably simple. The invalidation happens in lines 45-47. You'll see calling Session.set invalidates anyone listening to that key specifically (via Session.get) or to the new or old value (via Session.equals). Nothing in there about invalidating the session as whole.
On the other hand, considering how simple it is, it wouldn't be super hard to write your own data structure that does what you want. I'm not sure what your use case is, but it might make a lot of sense to separate it from the session.
Yes you can subscribe to Session values.
Take a look a the docs for autosubscribe:
http://docs.meteor.com/#meteor_autosubscribe
// Subscribe to the chat messages in the current room. Automatically
// update the subscription whenever the current room changes.
Meteor.autosubscribe(function () {
Meteor.subscribe("chat", {room: Session.get("current-room");});
});
This code block shows one way to use this. Basically any time the value of "current-room" is changed Meteor will update the views.
EDIT
I misunderstood the initial question and decided I need to redeem myself somewhat. I just did some tests and as far as I can tell you can currently only subscribe to collection.find() and session.get() calls. So you can't subscribe to the whole session object or even to the keys object.
However, you can set a Session value to an object this may not be the most elegant solution but this worked for me on keeping track of the keys object with some hackery to keep from getting a circular object error.
var mySession = {
set: function(key, val){
var keys = Session.keys,
map = {};
Session.set(key, val);
for (var prop in keys) {
if(!(prop === "keys")){
map[prop] = keys[prop];
}
}
Session.set('keys', map);
}
};
This gives you something that looks a lot like original functionality and can help you keep track and update templates as you add or change Session values.
Here's my template helper (borrowed from previous answer):
Template.hello.keys = function() {
map = [];
keys = Session.get('keys');
for (var prop in keys) {
map.push({key:prop, value:keys[prop]});
}
return map
};
And here's the actual template:
<template name="hello">
<div class="hello">
{{#each keys}}
{{key}} {{value}} <br />
{{/each}}
</div>
</template>
This way you can just call mySession.set("thing", "another thing") and it will update on screen. I am new to Javascript so someone please let me know if I'm missing something obvious here, or let me know if there is a more elegant way of doing this.
While accessing the session object in this manner is doable .. You're not drinking the cool-aid.
In other words, Meteor excels in the respect of having publish/subscribe. Its not clear (to me) how much data you've got to put into the Session object before a browser crashes; I would rather not find out.
You would merely put all your session dependent code in a Deps.autorun() function to plug into subscriptions as mentioned in an earlier answer. Any time a session changes it'll modify the subscription; you can attach a ready callback, or use subscription.ready() checks to initiate specific actions, but ideally you'd structure your templates in a way that you could use rendered/destroyed functions, taking care to use {{isolate}}/{{constant}} when possible.
What you're getting at is kind of a difficult way to do something simple; and may not adhere to changes in meteor in the future; we can always be assured publish/subscribe will function as expected... Given Session is such a simple object.. whats to say someone designs something better shortly.. and it gets merged into meteor; and you're left with some weird workflow based on custom objects that may not break the Session; but may impact other parts of your interface.