Displaying data from two collections using each block - meteor

So I have 2 collections as follows
Collection user, a document might look like this
{_"id":"xyz",
"name":"sam"
}
Collection items, more that one documents linked to user collection as follows
{_"id":"123345",
"userid":"xyz"
"item":"potato"
}
{_"id":"3456",
"userid":"xyz"
"item":"tomato"
}
now lets say i am running a query to display all documents with name sam as follows, (will be run as a helper)
return User.find({name:"sam"});
and I pass the records into blaze template and display value using {{#each}}.
Now, additionally I want to also display data from collection "items" along with collection "users". So my data in html after using {{#each}} might look like this
<li> sam ...potato, tomato </li>
<li> sam ...potato, tomato, orange </li>
<li> sam ...pineapple </li>
<li> sam ... </li>
i.e data is being displayed on the template using #each but from 2 different collections.
Can anyone tell me how the code in my template should look like?

You can use publish-composite package when you make your users publication. (See example 1 in the link)
So for your use case something like should work:
Meteor.publishComposite('userItems', {
find: function() {
return Users.find({
name: "sam"
},
{
sort: {
...
},
fields: {
...
}
});
},
children: [{
find: function(user) {
return Items.find({
userid: user._id
}, {
fields: {
"item": true,
...
}
});
}}]
});

The solution here is to pass a parameter to the helper. Here it should happen a little bit trickier:
1)Know how to pass parameters:
lets name the helper products
HTML
{{products paramA}}
JS
products: function(userId){
findOne({userId:userId})
}
2)Perfect, but how to pass the _id:
As you are calling the users helper with each you have access by {{_id}} so it should be possible to pass it.
If this somehow does not work. Again try to use the each. Try to reach the element in the products helper by this._id.
I hope the two ways will be enough to solve your problem.

Related

Altering a returned collection in onBefore hook or similar hook

Is it possible to alter (add more fields for each record) a returned collection in onBeforeAction or similar hook?
I have InvoiceHistory collection which I am paginating through. I also want to display as part of each invoice the company name, business address, email address and VAT registration number - and these four fields are stored in another collection. So I would like to add these four fields to each record returned from InvoiceHistory. If there is another way to do it I am open to suggestions. I am using Alethes meteor pagination which loses the helper fields returned in its itemTemplate when you navigate/browse/page to the second page. Alethes pagination also relies on iron-router, is it maybe possible to achieve what I want with the help of iron-router
========================================================================
#Sean. Thanks. Below is the code that uses meteor publish composite:
if (Meteor.isServer) {
import { publishComposite } from 'meteor/reywood:publish-composite';
publishComposite('invoicesWithCompanyDetails', function(userId, startingDate,endingDate) {
return {
find() {
// Find purchase history for userId and the two dates that were entered. Note arguments for callback function
// being used in query.
return PurchaseHistory.find({Id:userId,transactionDate:{$gte:new Date(decodeURIComponent(startingDate)),
$lt:new Date(decodeURIComponent(endingDate))}});
},
children: [
{
find() {
return CompanySharedNumbers.find(
{ _id:"firstOccurrence" },
{ fields: { companyName: 1, companyAddress: 1 } }
);
}
}
]
}
});
}
Router.route('/store/invoices/:_username/:startingDate/:endingDate', { //:_startingDate/:_endingDate
name: 'invoices',
template: 'invoices',
onBeforeAction: function()
{
...
},
waitOn: function() {
var startingDate = this.params.startingDate;
var endingDate = this.params.endingDate;
return [Meteor.subscribe('systemInfo'),Meteor.subscribe('testingOn'),Meteor.subscribe('invoicesWithCompanyDetails',startingDate,endingDate)];
}
});
Pages = new Meteor.Pagination(PurchaseHistory, {
itemTemplate: "invoice",
availableSettings: {filters: true},
filters: {},
route: "/store/invoices/:_username/:startingDate/:endingDate/",
router: "iron-router",
routerTemplate: "invoices",
routerLayout: "main",
sort: {
transactionDate: 1
},
perPage: 1,
templateName: "invoices",
homeRoute:"home"
});
Instead of adding new fields to all your document. What you could do is to add a helper to load a company document :
Template.OneInvoice.helpers({
loadCompany(companyId){
return Company.findOne({_id: companyId});
}
});
And use it in your template code:
<template name="OneInvoice">
<p>Invoice id: {{invoice._id}}</p>
<p>Invoice total: {{invoice.total}}</p>
{{#let company=(loadCompany invoice.companyId)}}
<p>Company name: {{company.name}}</p>
<p>Company business address: {{company.businessAddress}}</p>
{{/let}}
</template>
This way, the code is very simple and easily maintainable.
If I'm understanding you correctly, this sounds like a job for a composite publication.
Using a composite publication you can return related data from multiple collections at the same time.
So, you can find all your invoices, then using the company ID stored in the invoice objects you can return the company name and other info from the Companies collection at the same time.
I've used composite publications before for complex data structures and they worked perfectly.
Hope that helps

Use Flow Router Param in Autoform

Friends,
I'm working on my first app in Meteor and hitting my head against the wall on something...
I have a scenario similar to a blog + comments situation where I have one collection (call it 'posts') and want to associate documents from another collection (call it 'comments').
The best way I know to pass the post._id to the comments as a "postId" field is to use the Flow Router params, since the form is on the 'post/:id' view.
But for the life of me, I cannot figure out how to get "var postId = FlowRouter.getParam('postId');" to pass to Autoform so it populates. I've tried adding it as a function in the schema, as a hook, and as a hidden field in the form on the page (obviously don't want to go that route).
Autoform is amazing and I want to use it, but may have to wire it up the hard way if I can't get this darn value to populate.
Any ideas? I've been hitting my head against the wall on this for a couple of days now.
Thanks!
First, just so we're on the same page, if you have your route is set up like this:
FlowRouter.route('/blog/:postId', {
action: function (params, queryParams) {
FlowLayout.render('layout', { body: 'postTemplate' });
},
});
You are able to call FlowRouter.getParam('postId') from inside the AutoForm hook
You'll need to use an AutoForm hook and have a complete schema. I'm using the package aldeed:collection2 for the schema set up. The postId field must be explicity declared. This code is running on both server and client.
Comments = new Mongo.Collection("comments");
Comments.attachSchema(new SimpleSchema({
comment: {
type: String,
label: "Comment"
},
postId: {
type: String
}
}));
Setting your form up like this is not what you want:
{{> quickForm collection="Comments" id="commentForm" type="insert"}}
That's no good because it will show the postId field in the HTML output. We don't want that, so you have to fully define the form like this:
{{#autoForm collection="Comments" id="commentForm" type="insert"}}
<fieldset>
{{> afQuickField name='comment' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Insert</button>
{{/autoForm}}
Then add the AutoForm hook. This code is running on the client.
var commentHooks = {
before: {
insert: function(doc){
var postId = FlowRouter.getParam('postId');
doc.postId = postId;
return doc;
}
}
};
AutoForm.addHooks(['commentForm'],commentHooks);
Make sure you have your allow/deny rules set up, and it should be working fine.
I was struggling with this same use case as well, and I found this on the Meteor forums: https://forums.meteor.com/t/use-flow-router-param-in-autoform/14433/2
If you're using a schema to build your form (either with the autoform or quickform tags) then you can put it right in there.
For example:
campaignId: {
type: String,
autoform: {
value: function() {
return FlowRouter.getParam('campaignId');
},
type: "hidden"
}
},

AngularFire Loop denormalized data

I have categories and subcategories.
The structure of data is like the blog shows:
categories: {
-JF1RmYehtF3IoGN9xHG(categoryId): {
title: "Example",
subcategories: {
JF1RmYehtF3IoGN239GJ(subCategoryId): true
}
To now i retrieved the data this way(for just one category):[controller]
$scope.findOneCategory = function (categoryId) {
angularFire(Categories.find(categoryId), $scope, 'category');
}
And Categories service
find: function(categoryId) {
return FireRef.categories().child('/'+categoryId);
},
This works good!
But now i need to retrieve a list of subcategories in category and bind it to the scope and i dont know how...i have currently this:
The view:
<div ng-init="findSubCategories()">
<li class="list-group-item" ng-repeat="subCategory in subCategories">{{ subCategory.name }}</li>
The controller:
$scope.findSubCategories = function() {
angularFire(Categories.findSubCategories($routeParams.categoryId), $scope, 'subCategories');
}
And the service look like this i tried something but it's wrong and have no idea how it should looks like... if anyone could help i would be so glad!
findSubCategories: function(categoryId) {
var subCategoriesIdsList = FireRef.categories().child('/'+categoryId).child('subcategories');
subCategoriesIdsList.on("child_added", function(snap) {
FireRef.subcategories().child("/"+snap.name()).once("value", function(snap) {
// Render the comment on the link page.
console.log(snap.val());(i see the object in console but have no idea how to bind it now with the view...:(
});
});
},
dont know how to bind this with angular view
First, I'd recommend upgrading to the latest AngularFire version (currently 0.6), the API has changed quite a bit and it might be easier to accomplish what you're trying to do.
Second, you should only create one binding for each category and the subCategory IDs will automatically be included in that data since they're just the child nodes. Then you'll need to create a seperate binding just for the subcategory names, which you can then look up your ID.
Let's say your data looks like this:
categories: {
-JF1RmYehtF3IoGN9xHG: {
title: "Example",
subcategories: {
JF1RmYehtF3IoGN239GJ: true
}
}
},
subCategories: {
JF1RmYehtF3IoGN239GJ: {
name: "Example Subcategory",
}
}
You'll create two bindings, one for categories and another for subcategories:
function MyController($scope, $firebase) {
var ref = new Firebase("https://<my-firebase>.firebaseio.com/");
$scope.category = $firebase(ref.child("categories/" + categoryID));
$scope.subcategories = $firebase(ref.child("subCategories"));
}
And finally, in your view, use the ID you extract from $scope.category to get the sub category name from $scope.subcategories:
<ul ng-repeat="(id, val) in category.subcategories">
<li>{{subcategories[id].name}}</li>
</ul>

Nested Lists on same Page in MeteorJS

I have three lists on the same page which I want to fill with list-items. The list items are associated to the lists by a field called listId
My publications:
Meteor.publish('lists', function(options) {
return Lists.find({}, options);
});
Meteor.publish('listItems', function(listId) {
return Cards.find({listId: listId});
});
My lists-page.js (this._id param is passed with iron-router.):
Template.listsPage.helpers({
lists: function(){
return Lists.find({listsPageId: this._id});
},
listItems: function(listId){
//??
return ListItems.find({listId: listId})
}
});
My lists-page.html:
<template name="listsPage">
{{#each lists}}
<ul>
<li>{{title}}</li>
{{#each listItems}}
<li>{{listItemTitle}}</li>
{/each}
{{#each lists}}
</template>
Any help is much appreciated!
As a general recommendation, I would avoid collections and publish functions which have generic names like "lists" and "items". I find it makes code incredibly hard to understand. That being said, I think the confusion here is about the name associated with a publish function and the name of the underlying collection.
The name of the publish function (listItems in this case) is used only to facilitate the activation by a subscription, and has no other meaning. The function publishes a set of documents in the Cards collection to the client. Those documents should subsequently be retrieved from the client's local database using the Cards collection.
So I think the code you are looking for is:
listItems: function() {
return Cards.find({listId: this._id});
}
The context in which the listItems helper is run is that of a List document. So the id for listId should be this._id. Let me know if that doesn't work for some reason.

Meteor: how do you display a collection in multiple tables, where each table is a group of objects that have the same key value?

Say you have a bunch of blog posts, and each post has a "title" and "category". How would you render all post titles on a single page, where there is a table for each group of posts that have the same "category" values?
I'm starting by sorting by category, so the posts in the same category are grouped together in the cursor:
Template.postLists.posts = function() {
return Posts.find({}, {sort:{category:1}});
}
But I'm struggling with iterating through this list in a template via {{#each}}, and using Handlebars to detect when I reach a new "category", so that I can start a new , and then end the when I'm at the end of the category.
Am I coming at this the wrong way, or is there an easy way to do this?
The solution I went with was that in my Template handler, instead of returning a cursor using Posts.find(), I created a JSON object that has a structure that can be processed by a handlebars template (an array of category objects, where each category has an array of posts):
Template.postLists.categorizedPosts = function() {
var allPosts = Posts.find({}, {sort:{category:1}}).fetch();
// Then I iterate over allPosts with a loop,
// creating a new array of this structure:
// for ...
var catPosts = [ { category:"cat1", posts: [ {post1}, {post2} ] },
{ category:"cat2", posts: [ {post3}, {post4}, {post5} ] },
// etc...
];
// end loop
return catPosts;
The the template is something like this (but with tables instead of UL, just using UL for a cleaner demo here):
{{#each categorizedPosts}}
{{category}}
<ul>
{{#each posts}}
<li>{{posts.title}}</li>
{{/each}}
</ul>
{{/each}}
Note that when you return an object like this (instead of a cursor object that Posts.find() returns), Meteor's templating engine loses the ability to intelligently detect when only one of the objects in the collection has changed, and patching the DOM instead of completely re-rendering the template. So in this case, the template is completely re-rendered even if a single Posts object is updated in the DB. That's the downside. But the upside is that it works ;)

Resources