Meteor creating selection and then find selected state - meteor

I'm new to Meteor and building a demo site to play around with it but noticed an interesting problem. In the code below I'm trying to create a selection box and populate it with years for a user's date of birth. The helper for that seems straight forward. The issue is tying the query of Mongo to it so I can display the selection chosen. What I ended up creating is horrible. Multiple calls to the db and then the isSelected helper runs again and spins over everything needlessly.
Clearly there must be a way to create the selection and at the same time find the selected option in one pass preferably w/o an additional db call. But helper only returns one value. Maybe returning an array with year and selection state? I don't think I've seen that on anything I've read yet but it's early days with Meteor for me still, I know nothing. Anyone have ideas on how to create this with great performance? Thanks
<template name="accountProfile">
{{#with accountDetails}}
Other bits of data using accountDetails here
<li>
{{> userDateOfBirth}}
</li>
{{/with}}
</template>
<template name="userDateOfBirth">
<select name="userDateOfBirthYear">
{{#each displayYears}}
<option value="{{this}}" selected="{{isSelected}}">{{this}}</option>
{{/each}}
</select>
</template>
Template.accountProfile.helpers({
'accountDetails': function(){
var currentUser = Meteor.userId();
var userProfileCreated = User.findOne({ createdBy: currentUser });
return userProfileCreated;
}
});
Template.userDateOfBirth.helpers({
'displayYears': function(){
var listOfYears = [];
var numberOfYearsToList = 120;
for (var i = numberOfYearsToList-1; i >= 0; i--) {
listOfYears[i] = 2015 - i;
};
return listOfYears;
},
'isSelected' : function(){
var currentUser = Meteor.userId();
var userProfileCreated = User.findOne({ createdBy: currentUser });
var numberOfYearsToList = 120;
for (var i = numberOfYearsToList-1; i >= 0; i--) {
console.log(2015 - i + " " + this);
if((this) == moment(new Date(userProfileCreated.dateOfBirth)).format("YYYY")){
return "selected";
}
};
}
});

Since accountDetails already returns the profile of current user, you can access its dateOfBirth even inside displayYears context by using ../ (access parent data context), which helps you to avoid multiple queries to the DB.
<template name="accountProfile">
{{#with accountDetails}}
//- Other bits of data using accountDetails here
<li>
{{> userDateOfBirth}}
</li>
{{/with}}
</template>
<template name="userDateOfBirth">
<select name="userDateOfBirthYear">
{{#each displayYears}}
<option value="{{this}}" selected="{{isSelected ../dateOfBirth}}">{{this}}</option>
{{/each}}
</select>
</template>
Template.userDateOfBirth.helpers({
'isSelected' : function(dateOfBirth) {
if (this == moment(new Date(dateOfBirth)).format("YYYY")) {
return "selected";
}
}
});

Related

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

Inserting text at specific point in a list generated using #each

I'm trying to find a nice, Meteor-style way to handle this issue.
I have a set of Mongo documents sorted by date that I can easily display in a list:
<template name="logbook">
<h1>{{title}}</h1>
<div>
{{#each entries}}
{{> Entry}}
{{/each}}
</div>
</template>
Now, each time the year changes, I'd like to output it, so that I get something like this:
2014
doc 1
doc 2
2013
doc 3
doc 4
doc 5
etc.
This this is Meteor, I'd like the list to be reactive. If a new document arrives then it should be inserted in the right place in the list, and the year added if necessary.
Can anyone suggest a sensible approach to handle this?
You could probably use helper that will check if year is the same as in the last record, if not - he will output it, something like
<template name="Entry">
{{year}}
{{data}}
</template>
And in js
year: function(){
//operations that will return year to some variable, for example year_variable
if(global_variable===undefined){
global_variable=year_variable;
return year_variable;
}
if(global_variable===year_variable) return false;
else return year_variable;
}
There is no need to make it global tho, you can use sessions for it
This may not be precisely what you are looking for with respect to naming conventions, but it will give you an idea of how I would approach this problem:
Create a list of unique years
For each year, render a template (logbook)
Within each logbook, iterate over all of the entries for that year
Here is a complete working solution:
app.html
<body>
{{#each years}}
{{> logbook}}
{{/each}}
</body>
<template name="logbook">
<h2>{{year}}</h2>
<ol>
{{#each entries}}
<li>{{text}}</li>
{{/each}}
</ol>
</template>
app.js
if (Meteor.isClient) {
// create a client-side collection for testing
Entries = new Mongo.Collection(null);
Meteor.startup(function() {
// insert some data in the wrong order to test sorting
Entries.insert({text: 'doc6', date: new Date('1/3/2013')});
Entries.insert({text: 'doc4', date: new Date('1/1/2013')});
Entries.insert({text: 'doc5', date: new Date('1/2/2013')});
Entries.insert({text: 'doc3', date: new Date('1/3/2014')});
Entries.insert({text: 'doc1', date: new Date('1/1/2014')});
Entries.insert({text: 'doc2', date: new Date('1/2/2014')});
});
Template.body.helpers({
years: function() {
// return a list of unique sorted objects with a year field
return _.chain(Entries.find().fetch())
// pluck out the dates
.pluck('date')
// convert each date to a year
.map(function(date) {return date.getFullYear();})
// sort the years in reverse order
.sortBy(function(year) {return -year;})
// find only the unique values
.uniq(true)
// '2014' -> {year: '2014'}
.map(function(year) {return {year: year};})
.value();
}
});
Template.logbook.helpers({
entries: function() {
var year = this.year;
var entries = Entries.find({}, {sort: {date: 1}}).fetch();
// return a list of entries only for this year
return _.filter(entries, function(entry) {
return entry.date.getFullYear() === year;
});
}
});
}

How to find new products in a Meteor collection?

I have a Meteor collection which store products information. The collection also has a createdAt date field. I want to know how I can find products added to this collection in last 7 days.
Assuming you are using collection Products, you can do it in this way:
Products = new Meteor.Collection("products");
function getLastWeek(){
var today = new Date();
var lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7);
return lastWeek ;
}
if(Meteor.isClient){
// note you are losing reactivity here:
var products = Products.find({createdAt:{$gt:getLastWeek()}}).fetch();
}
Above example is really not useful in real world, as you probably want to get products in some template helpers and use reactivity feature.
Template.EXAMPLE.helpers({
products:function(){
return Products.find({createdAt:{$gt:getLastWeek()}});
}
})
and then use in template EXAMPLE.html :
<template name="EXAMPLE">
<ul>
{{#each products}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>

Meteor Group Collection by Field

I am trying to return a collection (Postings) grouped by a field (status). I am pretty new to mongo and meteor. The query below gives me the collections grouped by status with # of docs by that status... basically I want the same thing but have the actual documents in there.
Also, I would like to be able to publish/subscribe to this so that they reactivly update. I am creating an admin dashboard that groups all the Postings by current status.
A friend provided the following gist, but it is a bit over my head: https://gist.github.com/ryw/8827179
db.postings.group({ key: {status: 1}, initial: {sum:0}, reduce: function(doc, prev) { prev.sum += 1; } })
Thanks!
If you need all of the documents on the client, then I would just publish the whole collection and let the template code group them.
client
Tracker.autorun(function() {
if (Meteor.user()) {
Meteor.subscribe('allPostings');
}
});
Template.admin.helpers({
postings: function() {
if (Session.get('currentStatus')) {
return Postings.find({status: Session.get('currentStatus')});
}
},
statuses: function() {
return _.uniq(_.pluck(Postings.find().fetch(), 'status'));
}
});
Template.admin.events({
'click .status': function() {
Session.set('currentStatus', String(this));
}
});
<template name="admin">
<div class="left-panel">
<ul>
{{#each statuses}}
<li class="status">{{this}}</li>
{{/each}}
</ul>
</div>
<div class="right-panel">
<ul>
{{#each postings}}
<li>{{message}}</li>
{{/each}}
</ul>
</div>
</template>
server
Meteor.publish('allPostings', function() {
var user = Meteor.users.findOne(this.userId);
if (user.isAdmin) {
return Postings.find();
}
});
I'm assuming you have some way to identify admin users (here I used isAdmin). I am also assuming that a posting has a status and a message.
Instead of using aggregate functions or map reduce operations, you could denormalize your data and store a separate collection of the groups and their counts.
You can update your counts using observe functions as in the following example from the relevant section of meteor docs:
// Keep track of how many administrators are online.
var count = 0;
var query = Users.find({admin: true, onlineNow: true});
var handle = query.observeChanges({
added: function (id, user) {
count++;
console.log(user.name + " brings the total to " + count + " admins.");
},
removed: function () {
count--;
console.log("Lost one. We're now down to " + count + " admins.");
}
});
// After five seconds, stop keeping the count.
setTimeout(function () {handle.stop();}, 5000);
This way, you can present the groups and their counts on a template and it would be reactive.

Is there a way to get index while iterating through a collection in Meteor? [duplicate]

This question already has answers here:
How can I get the index of an array in a Meteor template each loop?
(6 answers)
Closed 7 years ago.
The below example will generate a list of names of players, where players is a data set from a MongoDB database.
<template name="players">
{{#each topScorers}}
<div>{{name}}</div>
{{/each}}
</template>
However, I want to display four of them in a row, and after four players is printed, I want to divide the line by <hr /> and then continue. For instance,
<template name="players">
{{#each topScorers}}
<div style="float:left;">{{name}}</div>
{{if index%4==0}}
<hr style="clear:both;" />
{{/if}
{{/each}}
</template>
How can I do something like that while iterating through collections?
Another solution, in line with maintaining the reactivity of the collection, is to use a template helper with the map cursor function.
Here's an example showing how to return the index when using each with a collection:
index.html:
<template name="print_collection_indices">
{{#each items}}
index: {{ this.index }}
{{/each}}
</template>
index.js:
Items = new Meteor.Collection('items');
Template.print_collection_indices.items = function() {
var items = Items.find().map(function(doc, index, cursor) {
var i = _.extend(doc, {index: index});
return i;
});
return items;
};
There's no easy way to do this right now, the latest version of handlebars supports an #index field (which would do what you want), but it's not yet implemented in meteor's version - https://github.com/meteor/meteor/issues/489.
Certainly you could implement your own {{#each_with_index}} helper, it would look something like this:
Handlebars.registerHelper('each_with_index', function(items, options) {
var out = '';
for(var i=0, l=items.length; i<l; i++) {
var key = 'Branch-' + i;
out = out + Spark.labelBranch(key,function(){
options.fn({data: items[i], index: i});
});
}
return out;
});
The downside of this is you lose the niceness of meteor's {{#each}} helper, which doesn't reactively re-render the whole list when a single item changes.
EDIT: thanks #zorlak for pointer to https://github.com/meteor/meteor/issues/281#issuecomment-13162687

Resources