I'm fairly confused as to which is the best way of doing this, since I didn't manage to find a package that solves this easily and other answers of similar problems don't address this properly.
I need to have a template that lists days and display all the documents created on each single day, for example:
10/27/2015
- Document A
- Document B
10/26/2015
- Document C
- Document D
- Document E
Or, less vaguely:
10/23/2014
- John Smith received 10 points on Basquet
- Paul Atreides received 20 points on Future Telling
10/21/2014
- Heisenberg received 25 points on National Trade
- etc.
Being the displayed document something like a 'Reports' collection that joins 'Players' with 'Activities', for example.
What is the proper way of achieving this functionality?
I guess creating a Days Collection is not the best option.
Thanks in advance
You can just create an array of days in your template helper, iterate over that with {{#each}} then have another helper that returns a cursor of documents for each day.
html:
<template name="docsByDay">
{{#each days}}
Date: {{this}} <!-- 'this' will be an individual date ->
{{#each documents this}}
{{content}}
{{/each}}
{{/each}}
</template>
Replace {{content}} with whatever field(s) you want to display from your collection.
js:
Template.docsByDay.helpers({
days: function(){
var arrayOfDates =[];
// create your array based on the date range and interval you want
return arrayOfDates;
},
documents: function(d){
var start = Date(getFullYear(d),getMonth(d),getDate(d));
var end = Date(getFullYear(d),getMonth(d),getDate(d)+1);
return Documents.find({ date: { $gte: start, $lt: end }});
}
});
See javascript - get array of dates between 2 dates
Related
In my code, I am trying to access a collection that stores information about books. I'm looping over an array that only stores the ID of each book. Then I am using that ID to query the book from a collection called Books, which stores complete information for each book
Template.myBorrows.helpers({
storeInSession:function(ilendbooksId) {
console.log("storeInSession is called");
var currentBorrowBook = Books.findOne({_id: ilendbooksId});
Session.setAuth('currentBorrowBook' , currentBorrowBook);
},
getAuthor: function() {
var currentBorrowBook = Session.get('currentBorrowBook');
return currentBorrowBook.ItemAttributes[0].Author[0];
},
});
I am querying the book document by the book's _id in the Books collection. Then I am storing it in a session, then calling appropriate methods to get information. The only problem is that I have to do this for an array of books, so every time the previous document in the Session gets overwritten and all my data on my page changes to the most current Session document. How and where can I store each book's document and ensure that the correct information is displayed without it updating to the most current Session doc?
To answer your comment then, you have an array of document _ids and you want to fetch the document in your templates.
html:
<template name="myTemplate">
{{#each array}}
{{#with doc}}
Author: {{author}}
{{/with}}
{{/each}}
</template>
js:
Template.myTemplate.helpers({
array(){ return theArrayOfIdsThatYouveDefined; },
doc(){ return Books.findOne(this); }
});
Inside the {{#each array}} loop this will be an element of the array. The doc helper simply looks up a book from the Books collection and returns it. Now inside the {{#with doc}} block the data context will be a book object.
No session variables or method calls required.
In my application I have posts, and in my post page I'm showing posted date as a minute ago using moment package.
I'm displaying dates using the following code
Template.registerHelper("postedTime",function(date){
return moment(date).fromNow();
});
and in my HTML
{{#with post}}
.............
............
{{postedTime date}}
............
............
{{/with}}
I know these dates are not reactive. In my post I have hundreds of comments also with the same date format.
What is the best way to update all those timings without much load to the client browser?
Dates are not reactive by themselves, so you need to include a reactive data source in your helper to force it to rerun.
In this example, we'll update a session variable that will force all instances of postedTime to be reevaluated every 60 seconds:
Template.registerHelper('postedTime', function(date) {
Session.get('timeToRecompute');
return moment(date).fromNow();
});
setInterval(function() {
Session.set('timeToRecompute' new Date);
}, 60 * 1000);
I have an app where users can take notes.
In the html page I iterate over each note like so:
<div id="notes-container" class="notes">
{{each notes}}
{{> note}}
{{/each}}
</div>
and in my router file I return the data like so:
#route: 'notes'.
path: '/notes/:_id',
data: ->
notes = Notes.find
threadId: #params._id
trash:
$exists: false
,
sort:
date: -1
All is typical meteor stuff so far. But I am confused now about how to adjust the data that is iterated on in the html page.
Each notes has a array field for tags like tags: ['apple' ,'red', 'green']
What if the user wants to return all notes with the tag 'red'. So there is a input box on the site the user enters a tag and presses enter.
How can I adjust the data that is sent to the page so queries mongodb to return all notes with tag red? I know how to write the query I am not sure how to set this up though in meteor.
One way I tried to do it is called the same route with query paramters like: '/notes/326363235474?tags=apple'
And in iron router I can look for query parameters and return the right set of documents but then when I call the original route again to clear the search, it does not load all of the original documents again.
Any suggestion on how I can set this up? Thanks
the data function simply needs to return the data you want available within the template context, if I'll define this function to a certain route:
data: ->
return Drawing.findOne
_id: window._drawing_id
I will have that data in my "this" object when proccessing that template.
I am currently working on a chat app in meteor. When first entering the room you get 25 messages initially. Now as new messages come into the page, that value should go up accordingly.
Now I so far I have a tried a couple different things, all not giving the desired result.
I have tried setting up a session variable on the client side that reactively re-subscribes to a given publish as the message count goes up. The problem with this route is this gives an adverse effect as new messages come in where all of the messages on the page need to reload because of the subscribe.
I have recently tried using the reactive-publish package with little luck in that the package has some various adverse effects of it's own.
What might be the best way to tackle this kind of problem? I am hoping there is a solution where I can set up some kind of publish that just streams in messages based on a numerical value that I in the database for each user.
EDIT: To add context
I am thinking something along the lines of
Meteor.publish 'messages', (roomId) ->
dl = // Some value that I pull from the database, which gets updated as new messages come into a room
Messages.find({room: roomId, type: "user_message"}, {sort: {time: -1}, limit: dl, fields: {_id: 1, name: 1, message: 1, room: 1, time: 1, type: 1}})
A huge amount of flexibility in Pub/Sub flexibility is achievable by using the low-level publications API - so much so that I've just written a blog post about it. It should make it pretty clear how you update variables when new documents appear in a query set.
It seems like you want each user to have a unique subscription based on the time that they enter the chat room, i.e. Meteor.subscribe("messages", roomId, new Date), that includes up to 25 messages from before they entered the room. Here's one option:
Meteor.publish("messages", function (roomId, time) {
var lastRecent = _.last(Messages.find({
room: roomId, type: "user_message"
}, {sort: {time: -1}, limit: 25}).fetch());
var cutoffTime = lastRecent && lastRecent.time || time;
return Messages.find({
room: roomId, type: "user_message", time: {$gt: cutoffTime}
});
});
If you want to successively add e.g. 25 old messages at a time whenever a user scrolls to the top of the chat window, consider that you may not actually need to "subscribe" to those older messages. You may be able to set up a method call like Meteor.call("getNOlderMessages", {roomId: roomId, N: 25, priorTo: oldestShownTime}) to fetch them, insert them into a local collection on the client (e.g. OlderMessages = new Meteor.Collection(null);), and then do something like:
<template="messages">
{{#each olderMessages}} {{> message}} {{/each}}
{{#each messages}} {{> message}} {{/each}}
</template>
Imagine you have multiple collections you want displayed on a single social news feed, for example Posts and Users (new signups). How would you reactively display both in a list sorted by creation date?
Idea 1 - merging in client
You publish the entities separately, and in the client, sort each of them by date, and merge them into a single sorted array, which you then display in an {{#each}} iterator. Problem: AFAIK this requires flattening the reactive cursors into static arrays, so now the page won't update. (Perhaps there's a way of making the page recalculate this array when any collection changes, salving this approach?)
Idea 2 - creating a new collection
You create a new collection, say FeedItems. When a new Post or User is created, you also create a new FeedItem and copy the relevant information into it. Displaying the items in the client is now very straightforward. Problem: Now there is no reactivity between the canonical objects and the FeedItem versions of them, so if someone changes their name, deletes a post, etc., this won't be reflected in the feed. (Perhaps there's a way of creating reactivity between collections to salvage this approach?)
Idea 3 - merging in the publication
Perhaps there's some way of sticking with the existing collections, but creating an additional 'newsFeed' publication which would somehow merge them. I haven't seen any way of doing this, however. I see in the docs that you can publish an array of collections, but AFAIK this is equivalent to publishing the same collections one at a time.
Is one of these approaches on the right track? Or is there another I haven't thought of?
By far the easiest solution is to merge them on the client in a template helper. For example:
Template.dashboard.helpers({
peopleAndPosts: function() {
var people = People.find().fetch();
var posts = Posts.find().fetch();
var docs = people.concat(posts);
return _.sortBy(docs, function(doc) {return doc.createdAt;});
}
});
and the template (assuming people and posts have a name):
<template name="dashboard">
<ul>
{{#each peopleAndPosts}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>
This will be reactive because the helper is a reactive context so any changes to People or Posts will cause the returned array to be recomputed. Be aware that because you are not returning a cursor, any changes to either collection will cause the entire set to render again. This won't be a big deal if the length of the returned array is relatively short.
Idea 1 seems to work out of the box. I created a new meteor project and change it as follows:
test.js:
Users = new Meteor.Collection("users");
Posts = new Meteor.Collection("posts");
if (Meteor.isClient) {
Template.hello.array = function () {
var a = Users.find().fetch()
.concat(Posts.find().fetch());
return _.sortBy(a, function(entry) { return entry.votes; });
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Users.insert({name: "user1", votes: 1});
Users.insert({name: "user2", votes: 4});
Users.insert({name: "user3", votes: 8});
Users.insert({name: "user4", votes: 16});
Posts.insert({name: "post1", votes: 2});
Posts.insert({name: "post2", votes: 4});
Posts.insert({name: "post3", votes: 6});
Posts.insert({name: "post4", votes: 8});
});
}
test.html:
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{#each array}}
<div>{{votes}} {{name}}</div>
{{/each}}
</template>
This gives the expected list:
1 user1
2 post1
4 user2
4 post2
6 post3
8 user3
8 post4
16 user4
Then I did Users.insert({name: "new", votes: 5} in the console and got (reactively):
1 user1
2 post1
4 user2
4 post2
5 new
6 post3
8 user3
8 post4
16 user4
I came across this problem and I was wondering if a mixed solution would be better (or at least more efficient):
The solution proposed by David works great but, as he says, problems could arise when dealing with big collections.
What about keeping a local collection (just in the client), merge both collections into that local one when template is created and then register an observer (or maybe an observer for each remote collection) to keep the local collection up to date?