I have a simple template which has a search box in it:
<template name="search">
<input type="text" name="search" id="search" />
{{> search_results}}
</template>
and obviously, the search results template:
<template name="search_results">
<ul>
{{#each results}}
<li>{{this.resultValue}}</li>
{{/each}}
</ul>
</template>
There is an event for keyp on the search input:
Template.search.events({
'keyup input#search': function (e) {
// fetch result from db
}
});
My problem is, where i have the comment: fetch result from db how do i get the search results template to auto update with the results from the db query?
To be clear: i can get the results fine, just cant see how to get the results template to update.
Essentially, something in the template you want to rerender (search_results) has to be reactive and register a changed event. Out of the box, that means it could be any of the data sources listed here. Since this isn't happening for you, I assume that when you "fetch results from db" you're not actually returning the result of a minimongo query, as this would be reactive by default.
If so, this kind of problem is often most easily solved by rolling your own reactivity using the Deps.Dependency prototype, which (as I understand it) underpins all the other reactive data sources other than the minimongo itself.
If you set var resultsChanged = new Deps.Dependency(), you get an object with two methods: depends and changed. Invoke the former in any computation you want to rerun when it changes and the latter to register a change. Session variables are basically just key/value stores with these Deps.Dependency methods attached to their get and set methods respectively.
So if you have resultsChanged initialised, you just need to make sure your search_results template depends on it, either by adding a new helper function (if results is in the data context as opposed to being a call to a helper), or amending the results helper itself. I'll assume the latter, but adding a new helper instead would be equally trivial.
Template.search_results.helpers({
results: function() {
resultsChanged.depend();
// get your results from wherever
return results;
}
});
That will rerun every time resultsChanged changes, so you just have to add the appropriate code in your callback when you fetch the results:
'keyup input#search': function (e) {
// fetch result from db, passing "resultsChanged.changed()" in a callback
}
Obviously, if the result fetching is synchronous, you don't even need to pass a callback, you can just do it on the next line.
I hope that is vaguely helpful - please let me know if it's not. If my assumption that you're not just pulling something out of the database is wrong, then this is probably on totally the wrong track, so I would recommend posting your actual "fetch result from db" code in your question.
Related
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.
I've got a Newsfeed idea that I wanted to build with Meteor, but I'm having a bit of a struggle figuring out how to make the news feed itself constant, that is not reactive, but update the sub-items (comments, likes, etc) as soon as they're updated.
I've got everything stored in a single collection, and I'd like to keep it that way if possible. So the collection is setup like this:
[
{
title: 'A random title',
date_created: '01/01/2001',
comments:
[
{'message': 'Lorem ipsum', date_created: '01/01/2001'},
[...]
]
},
[...]
]
So what I'd like to do is have the newsfeed non-reactive, so that when a new news item is inserted or updated, the template holding the list of news won't get re-rendered. But if a comment is added, deleted, or someone likes the news feed, I'd want that to get updated right away in the template.
I've been trying to figure out how to use {{#isolate}} and {{#constant}} but to no prevail.
Here's my client side JS:
Template.group_feed.feed_data = function() {
var feed = Newsfeed.find({}, {
sort: {updated_time: -1},
limit: 10,
reactive: false
}).fetch();
return feed;
};
I set reactive: false so that it doesn't update the template, but that makes it static also when comments or likes are updated. So I'm guessing there's a better way to do this then to make the whole collection non-reactive.
Here's my template code:
<template name="group_feed">
<div id="feed-wrapper">
<ul>
{{#each feed_data}}
{{> group_feed_item}}
{{/each}}
</ul>
</div>
</template>
<template name="group_feed_item">
<li>
<h1>{{title}}</h1>
<div class="comments">
{{#each comments}}
<p>{{message}}</p>
{{/each}}
</div>
</li>
</template>
Anyone got a nice way of achieving this?
You have two options here. (I'll use coffeescript for my pseudocode)
use observeChanges:
NewsCursor = NewsItems.find()
NewsCursor.observeChanges
changed: (id, fields) ->
if fields.comments
Session.set ("commentsForId"+id), fields.comments
Split your data into two collections: one for the posts and one for the comments.
If you don't do one of those, {{isolate}} isn't going to help you. With your current data setup, Meteor just sees whether the post changed or not, and updates any templates when it does; it doesn't keep track of which part changed.
I did not test it, but I guess it would be most straightforward to limit the subscription of the client, thus reducing data transfer an eliminataing the need for the preserve:
it would be something like:
on server:
Meteor.publish('tenItemsBefore',function (time) {
Newsfeed.find({updated_time: {$lt time}}, {
sort: {updated_time: -1},
limit: 10
})}
on client, in reactive context eg. in Meteor.autorun():
Newsfeed.subscribe('tenItemsBefore',Session.get('lastUpdate'));
on client, triggered by an event eg. refresh using router package:
Session.set('lastUpdate', (new Date()).getTime());
hope that helps,
best, Jan
I'm pretty sure the problem is that you are returning an array rather than a cursor.
It seems that Spark behaves differently with the two.
Remove the .fetch() from the end of the feed query.
For more information about how this stuff works, I would strongly suggest looking at Chris Mather's great presentations at http://www.eventedmind.com/
Specifically, the one on reactivity in slow motion illustrates the difference between an array and a cursor:
http://www.eventedmind.com/posts/meteor-ui-reactivity-in-slow-motion
UPDATE: In my original answer I didn't fully understand problem - my apologies, so the bit about removing the fetch() will not help you. Here are a couple of options you might want to explore:
Using the Meteor.observe or Meteor.observeChanges to watch for changes to the comments field and updated the DOM with code ('by hand').
Create a custom collection on the server side that publishes just the comments from your original collection. So I'm not suggesting you change the data model as stored in mongo, just publish a different view of it. With this way it might be easier to continue to use templates to update the comments.
After initial loading of the collection, make a note of the viewed item ids, and then subscribe to the collection passing a list of ids to watch (or date range). This would require a new publish function that took a array of ids, and filtered the returned documents. This approach would prevent new documents disturbing what you had on screen, and you would still get comment changes, but you would also get deletions and field changes that affected non-comment fields.
Hopefully one of these approaches may fit the bill for you.
In case for some reason you don't want / cannot restrict the data seen by the client with publish or if new posts with earlier post time could be inserted and destroy the preservation the above solution wont work.
Therefore I suggest a different solution to more directly achieve preservation:
On opening the posts save the _id's of the watched posts in a non reactive way as by:
Session.set('watched',
_.map(
Newsfeed.find({}, {
sort: {updated_time: -1},
limit: 10,
reactive: false //not sure wheather you need both, as #StephenD
}).fetch(), // pointed out sth. fetched is non reactive by default
function (obj) {return obj._id;}
)
);
and:
Template.group_feed.feed_data = function () {
return Session.get('watched');
};
Template.group_feed_item.item = function () {
return Newsfeedd.findOne(this);
};
as well as a small update in the html (where with saves you from definining an extra template):
<template name="group_feed_item">
<li>
{{#with item}}
<h1>{{title}} - {{_id}}</h1>
<div class="comments">
{{#each comments}}
<p>{{message}}</p>
{{/each}}
</div>
{{/with}}
</li>
</template>
best, Jan
From the docs (emphasis mine) - http://docs.meteor.com/#find
Cursors are a reactive data source. On the client, the first time you
retrieve a cursor's documents with fetch, map, or forEach inside a
reactive computation (eg, a template or autorun), Meteor will register
a dependency on the underlying data. Any change to the collection that
changes the documents in a cursor will trigger a recomputation. To
disable this behavior, pass {reactive: false} as an option to find.
Note that when fields are specified, only changes to the included
fields will trigger callbacks in observe, observeChanges and
invalidations in reactive computations using this cursor. Careful use
of fields allows for more fine-grained reactivity for computations
that don't depend on an entire document.
I want to perform a Meteor collection query as soon as possible after page-load. The first thing I tried was something like this:
Games = new Meteor.Collection("games");
if (Meteor.isClient) {
Meteor.startup(function() {
console.log(Games.findOne({}));
});
}
This doesn't work, though (it prints "undefined"). The same query works a few seconds later when invoked from the JavaScript console. I assume there's some kind of lag before the database is ready. So how can I tell when this query will succeed?
Meteor version 0.5.7 (7b1bf062b9) under OSX 10.8 and Chrome 25.
You should first publish the data from the server.
if(Meteor.isServer) {
Meteor.publish('default_db_data', function(){
return Games.find({});
});
}
On the client, perform the collection queries only after the data have been loaded from the server. This can be done by using a reactive session inside the subscribe calls.
if (Meteor.isClient) {
Meteor.startup(function() {
Session.set('data_loaded', false);
});
Meteor.subscribe('default_db_data', function(){
//Set the reactive session as true to indicate that the data have been loaded
Session.set('data_loaded', true);
});
}
Now when you perform collection queries, you can check if the data is loaded or not as:
if(Session.get('data_loaded')){
Games.find({});
}
Note: Remove autopublish package, it publishes all your data by default to the client and is poor practice.
To remove it, execute $ meteor remove autopublish on every project from the root project directory.
Use DDP._allSubscriptionsReady() (Meteor 0.7)
As of Meteor 1.0.4, there is a helper that tells you exactly when a particular subscription is ready: Template.instance().subscriptionsReady().
Since this question is a duplicate, please check my answer in the original question, Displaying loader while meteor collection loads.
You can also do template level subscriptions:
Template.name.onCreated(function(){
var self = this;
this.autorun(function(){
const db = this.subscribe('publicationname', [,args]);
if(db.isReady()){
"You'll know it's ready here" .. do what you need.
}
});
})
This makes it easier to know inside the template too. you can just call
{{#if Template.subscriptionsReady}}
{{else}} Loading Screen may be
{{/if}}
You could check when a result is finally returned if you know that your Games collection is never empty:
Meteor.autorun(function() {
if(Games.findOne() && !Session.get("loaded")) {
Session.set("loaded",true);
//Its ready..
console.log(Games.findOne({}));
}
});
You can also use this in your templates:
Client js:
Template.home.isReady = function() { return Session.get("loaded") };
Html
<template name="home">
{{#if isReady}}
Yay! We're loaded!!
{{else}}
Hold an a second
{{/if}}
</template>
Here is another tidbit of information for those who may be using userid or some part of user info stored in Meteor.users database. When the page first loads the Meteor subscribe, going on in the background, may not be complete itself. Therefor when you try to connect to another database to query for that, it will not pull the information. This is because the Meteor.user() is still null itself;
The reason, like said above, is because the actual Meteor users collection has not gotten through pulling the data.
Simple way to deal with this.
Meteor.status().connected
This will return true or false, to let you know when the Meteor.user collection is ready. Then you can go on about your business.
I hope this helps someone, I was about to pull my hair out trying to figure out how to check the status. That was after figuring out the Meteor user collection itself was not loaded yet.
I'm having a few problems when trying to get data from a Meteor Collection and I need some advice.
The collection has been defined, published, and subscribed successfully. If I send the data to a template, it displays fine:
Template.Lists.Projects = function(){
return Projects.find();
};
But I am trying to use the data before displaying it, and this is where I run into problems. First, I'm getting some inconsistencies between find() and findOne(). find(selector) works fine and returns a cursor, but findOne(selector) returns "undefined." I really am only looking for 1 entry, so find() seems unnecessary.
Returns LocalCollection.Cursor:
var find = Projects.find({_id: "3fd33eed-9735-4376-860e-3be383beae2f"});
console.log(find);
Returns undefined:
var find = Projects.findOne({_id: "3fd33eed-9735-4376-860e-3be383beae2f"});
console.log(find);
My next problem arises when using .fetch() on the LocalCollection.Cursor. It returns an empty array.
var find = Projects.find({_id: "3fd33eed-9735-4376-860e-3be383beae2f"});
var fetch = find.fetch();
console.log(fetch);
all this returns is the following line:
[ ]
When I try to specify a specific key from the array I want to display, like:
var find = Projects.find({_id: "3fd33eed-9735-4376-860e-3be383beae2f"});
var fetch = find.fetch();
console.log(fetch.name);
It returns undefined.
I'm still familiarizing myself with Meteor and have never used MongoDB (or minimongo), so I'm probably just making some stupid mistake. If anyone can point it out to me I would be thrilled!
Your results for find() and findOne() are consistent. Basically, Mongo or minimongo is simply not finding a document that matches that _id. FindOne() is precisely like doing a find(selector, options).fetch()[0].
Your Lists.Projects template is likely expecting a collection, array or hash it can iterate over. You cannot return one specific document. If you are using {{#each Projects}} you must provide some way for the template to iterate not just a single value.
I recently had the same problem,
The collection find() returned nothing when used from a query.observe.
The problem was the order of the subscribe of collections.
For example if you have a collection called Lists and one called Projects,
If you're getting the projects by observing a query on lists, and you had :
Meteor.subscribe('Lists');
Meteor.subscribe('Projects');
What happens is the query observe trigger is called, but the Projects are not yet fetched from the server. So Projects.find().fetch().length = 0.
To fix it, simply do
Meteor.subscribe('Projects');
Meteor.subscribe('Lists');
if u've removed autopublish, what if u publish the collection to all user without using a publish name?
Meteor.publish null, ->
Products.find {}
where u subscribe your collection ?
template helper
Handlebars.registerHelp = 'products', (id) ->
Product.find _id: Session.get 'productId'
like if we have price in each product.
the template part looks like...
<template name="products-list">
<div class="products-list">
{{#each products}}
{{> product-item}}
{{/each}}
</div>
</template>
<template name="product-item">
<div class="product-item">
{{price}}
</div>
</template>
the js part, i'll use coffeescript...
Template['product-item'].price = ->
console.log # # # is this as it is product itself, if we have product inserted.
console.log this
return 'the price is: ' + #price
try this way
Meteor.subscribe('testData', function() {
var document = Documents.find();
console.log(document);
});
You are working on the client and you never know when the client got all the data you need. Your functions can be fired when the collections are empty or still not finished synchronized. So you have to make a deferred request to your minimongo (when all data is available local)
And yes you canĀ“t access stuff when its not rendered in the DOM via getElementById() or something but in your case you try to access data from the minimongo (your local mongodb version in the browser) not the DOM so your template is not important here.
Just wait until your subscribtion is ready that your minimongo own all the data with the onReady callback in your subscribtion call and fire your functions.
https://docs.meteor.com/api/pubsub.html#Meteor-subscribe
callbacks (Function or Object).
Optional. May include onStop and onReady
callbacks. If there is an error, it is passed as an argument to
onStop. If a function is passed instead of an object, it is
interpreted as an onReady callback.
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.