Nested Lists on same Page in MeteorJS - meteor

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.

Related

How to access the subscriptions inside the HTML file?

Usually when I use helpers, I can access the returned values as below:
Template.oveview.helpers({
item: function () {
return Requests.find({});
},
Then in the client side I can use {{#each item}}, But I don't know how to display them in the .html when using publish and subscribe
Here is my publish:
Meteor.startup(() => {
Meteor.publish('requests', function queryRequests() {
return Requests.find({});
});
});
And here is my subscribe:
Template.overview.onCreated(function() {
Meteor.subscibe('requests');
});
How can I display the returned value from publish in the client side?
You can use it in a few different ways. You can use Meteor templates to insert a HTML snippet for every item in-between existing HTML:
{{#each item}}
{{> htmlTemplateName}}
{{/each}}
Or you can just place raw HTML in the {{#each}} loop:
{{#each item}}
<p>{{propertyX}}</p>
<p>{{propertyY}}</p>
{{/each}}
You might run into problems with the pubsub, depending on load order (I don't know load orders, I'm afraid). I used the iron-router package in my project, to bind certain end-points to specific HTML files. iron-router has this nice parameter you can set for every page, called waitOn, where I placed my subscriptions. This means that subscribing to a certain collection happens before anything else.
Router.configure({
layoutTemplate: '_layoutTemplate',
name: 'myTemplateName',
waitOn: function() {
return [
Meteor.subscribe('requests'),
//Add other subscriptions here
];
}
});

How do I control two subscriptions to display within a single template?

Sorry kind of new to the Meteor framework!
I Subscribed to two Publish functions. Even if both publish functions target the same Collection, they both have different functions, that I would like to display in one template. How do I achieve this. I have done allot of research but there doesn't seem to be sufficient information on how to achieve.
Following are the two publish functions in code that I subscribe to:
.server/main.js:
Meteor.publish('MerchantTrending', function (categoryMan){
var currentUser = this.userId;
return buyList.find({ who:"Merchant", ownerId:currentUser, itemCategory: { $in: categoryMan } }, {skip: 0, limit: 3});
});
.server/main.js:
Meteor.publish('myTopViews', function (){
var currentUser = this.userId;
return buyList.find({ newArrivalsExpiryDate : {'$lte': new Date()}}, {ownerId:currentUser }, {skip: 0, limit: 3});
});
Following is the subscription function in code
.client/main.js:
Router.route('/MerchantLandingPage', {
subscriptions: function(){
var categoryMan = Session.get('category');
return Meteor.subscribe('MerchantTrending', categoryMan, 'merchantTopViews')
}
});
Now the helper function in code:
Template.MerchantLandingPage.helpers({
'top3Trending' : function () {
return buyList.find({}).fetch();
},
'myTopViews' : function () {
return buyList.find({}).fetch();
}
});
And now the template in code:
<template name="MerchantLandingPage">
##### *** Top three trending items *** ########
{{#each top3Trending}}
ItemName:: <b>{{itemName}}</b> <br>
Item Category:: <b>{{itemCategory}}</b> <br>
Discription:: <b>{{descriptions}}</b> <br>
Image:: {{this.photo._id}} <br>
Date Created:: {{createdDate}} <br>
{{/each}}
<br><br>
############ *** My top Views *** #############
{{#each myTopViews}}
ItemName:: <b>{{itemName}}</b> <br>
Item Category:: <b>{{itemCategory}}</b> <br>
Discription:: <b>{{descriptions}}</b> <br>
Image:: {{this.photo._id}} <br>
Date Created:: {{createdDate}} <br>
{{/each}}
</template>
Both {{#each myTopViews}} and {{#each top3Trending}} successfully display but not correctly. When the variable categoryMan in
Meteor.subscribe('MerchantTrending', categoryMan, 'merchantTopViews')
changes value, it affects both both the outcome of both {{#each myTopViews}} and {{#each top3Trending}}, when its only supposed to affect {{#each top3Trending}}.
How can I get the subscriptions to NOT have an affect on both {{#each myTopViews}} and {{#each top3Trending}}, but only {{#each myTopViews}} in my template?
Thanks for the help!
Welcome to Meteor!
The solution is straight forward once you understand that:
Subscription is just a stream of your DB documents from server into your client's MiniMongoDB. So your 2 subscriptions (it is perfectly fine having several subs on the same Collection) just fill in your client's buyList local collection.
Use of Collections client side is generally independent from how you subscribe the data. So you should simply use a similar selector and possibly options in your top3Trending and myTopViews helpers as you have done for your publication server side (not the same between the 2 helpers, obviously).
As a side note, you do not even need to fetch() the Collection cursor returned by find(), Blaze knows how to handle it directly.
I see a few problems with your code, first of all - your second subscription isn't going to work because your query is wrong:
Meteor.publish('myTopViews', function (){
var currentUser = this.userId;
return buyList.find(
{ ownerId:currentUser, newArrivalsExpiryDate : {'$lte': new Date()}},
{skip: 0, limit: 3}
);
});
You had ownerId: currentUser wrapped in curly braces, it is fixed above.
The way publications/subscriptions work is, if you have two publications sending different data, the template doesn't 'know' the data is coming from two different subscriptions. It will just have access to all of the data being sent by all subscriptions.
For that reason, your two helpers top3trending and myTopViews are returning exactly the same thing. You can delete one of them!
You should move your subscriptions out of the router and in to the Template itself. Here's an article that will help you with that!
There is a package percolate:find-from-publication that permits to filter the data from publications.

Polymer and Polymerfire: how to loop through the data from an observer?

I have a simple <firebase-query> tag, and I'd like to manipulate some of the data before having it displayed through a <dom-repeat>. For example, I need to turn some fields into links, and also parse some dates.
So, I need to get the data once it's ready, loop through each item, and change some of the values.
To do that, I have an observer on the data to detect when it's ready. However, I can't figure out how to loop through the data from that JavaScript function. For some reason, for(var i in items) doesn't work, although the items do exist.
Here is the component:
<dom-module id="cool-stuff">
<template>
<firebase-query id="query" path="/items" data="{{items}}"></firebase-query>
<template is="dom-repeat" items="{{items}}" as="item">
[[item.name]]<br />
[[item.date]]<br />
</template>
</template>
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object, observer: "_itemsChanged"},
}
itemsChanged: function(data) {
// how do I loop through the data received from firebase-query?
console.log(data);
}
});
</script>
</dom-module>
Ideally, all I'd want to do in the observer function is something like:
for(var i in data) {
obj = data[i];
obj.name = '<a href="/item/"+obj.key>'+ojb.name+'</a>';
}
But I can't seem to be able to loop through the data.
Inside the observer function, console.log(data) returns some weird stuff like this:
Array[o]
0: Object (which contains a proper item)
1: Object (same)
2: Object (same)
Update:
Here is a screenshot of what console.log(data) returns (from inside the observer):
The array seems to be populated with all the objects, but it shows as Array[0]. So it won't let me loop through them.
Update 2:
Thanks to arfost here is the solution:
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object},
}
observers: [
'_itemsChanged(items.splices)'
],
_itemsChanged: function(changeRecord) {
if (changeRecord) {
changeRecord.indexSplices.forEach(function(s) {
for (var i=0; i<s.addedCount; i++) {
var index = s.index + i;
var item = s.object[index];
console.log('Item ' + item.name + ' added at index ' + index);
// do whatever needed with the item here:
this.items[index].name = "New name";
}
}, this);
}
},
});
</script>
<firebase-query> results
Note that <firebase-query> results in an array of objects. Let's say your database contained the following items under /notes/<USER_ID>/:
Your <firebase-query> would look similar to this:
<firebase-query
id="query"
app-name="notes"
path="/notes/[[user.uid]]"
data="{{notes}}">
</firebase-query>
(where user is bound to <firebase-auth>.user).
Assuming the user is logged in, <firebase-query> would then populate its data property (i.e., bound to notes) with the following array:
Note how each object contains a $key property, which corresponds to the item's key seen in the Firebase console's Database view.
You could then iterate notes directly with <dom-repeat>:
<template is="dom-repeat" items="[[notes]]">
<li>
<div>key: [[item.$key]]</div>
<div>body: [[item.body]]</div>
<div>title: [[item.title]]</div>
</li>
</template>
Binding to HTML strings
You should be aware that the string data bindings are rendered literally in this case, so attempting to set name to obj.name = '<a href="...">' would render the literal string instead of an anchor. Instead, you should declare the tags in your template, and bind the key and name properties inside those tags. So, your observer could be replaced with this:
<template is="dom-repeat" items="{{items}}" as="item">
<a href$="/item/[[item.key]]">[[item.name]]</a><br />
[[item.date]]<br />
</template>
Iterating an array
The following note is only relevant if you prefer to mutate the data before displaying it...
When iterating an array, you should avoid for..in because it doesn't guarantee order of iteration, and because it may iterate over enumerable properties you might not necessarily care about. Instead, you could use for..of (assuming ES6 is available to your app):
for (let note of notes) {
note.title += ' ...';
}
or Array.prototype.forEach():
notes.forEach(function(note) {
note.title += ' ...';
});
I thinks I have run into the same issue as you.
It come from the way firebase query is getting the array, the way polymer obersvers works, and is hidden by the fact that the javascript console is reference based when it show the objects.
In fact what really happen here, is that firebase query is creating an empty array, which trigger your polymer observer.
So your function is called as soon as the array is created, but still empty and you can't iterate through, since it's empty. You then log it, where the primitives sub-properties are correctly displayed (array[0])
Then firebase begin to populate the array with the datas. The arrays reference stay the same, so polymer won't fire the observer again, and in the console, when it try to display the array it display the array referenced in the log, which now contains the datas.
I recommend that you use a array mutation observer in place of your simple one as follow
`properties: {
items: {type: Object},
},
,
observers: [
'_itemsChanged(items.splices)'
],`
It will fire every time an object is added to your array, and you would be able to do the work you need :)
I had the link for the documentation on array mutation observer :)
polymer array mutation observer
I hope this will solve your issue,
have a good day.
i don't think i can think of a scenario where you'd need to mutate the data by looping through the array rather than just using computed bindings. like this:
<template is="dom-repeat" items="{{items}}" as="item">
<child-el date="{{_computeDate(item.date)}}"></child-el><br />
<child-el attr1="{{_someOtherConversion(item.prop1)}}"></child-el><br />
<child-el attr2="{{_iPromiseAnyConversionCanBeDoneLikeThis(item.prop2)}}"></child-el><br />
</template>
<script>
_computeDate: function(item) {
//do date converstion
}

Meteor JS and State (address) select element

I want to have a state select in meteorjs for an address input field. I feel like listing out all the states in a massive html string of <option>s is wrong. Is there a documentation or preferred way to do this?
In frameworks like CakePHP, I would create a DB table related to address and just use the form helper methods to output the markup based on the table.
If you'd prefer to fetch a set of states from a database, you could create a Meteor Collection to store them.
States = new Mongo.Collection("states");
If you've removed the autopublish package (which if you haven't, you should), you need to then publish this collection,
if (Meteor.isServer) {
Meteor.publish("states", function() {
return States.find();
}
}
and then subscribe to it, and make it available to your template with a helper:
if (Meteor.isClient) {
Meteor.subscribe("states");
Template.myForm.helpers({
states: function() {
return States.find();
}
});
}
Then you can output the collection in your template as such:
<select>
{{#each states}}
<option>{{name}}</option>
{{/each}}
</select>
A nice way to enter the data into the database is through the Meteor Mongo shell or through a GUI like RoboMongo

Basic pattern: Populate a template with JSON from an external URL in Meteor

I am struggling to figure out the basic pattern for populating a template with data from a call to an external API in Meteor.
These are the elements in play
A fresh Meteor project, created by running meteor create monkeyproject
The URL of an external API that returns a JSON array. Let's say it's example.com/api/getmonkeys. It returns an array of monkeys, each with a different name.
A Handlebar template called monkeyTemplate with an {{#each}} loop. Let's say it's this:
<template name="monkeyTemplate">
{{# each monkeys}}
One of our monkeys is named {{name}}. <br>
{{/each}}
<input type="button" id="reload" value="Reload monkeys" />
</template>
What I want to happen
When the page loads fill monkeyTemplate with monkeys from our external URL.
When the user clicks the button, call the external URL again to reload the monkeys.
The question
What is a standard pattern for doing the above in Meteor? At the risk of cluttering up the question, I'll include some starting points, as I understand them.
We can populate the template with whatever we return from our Template.monkeyTemplate.monkeys function. How do we fill it with content from an external URL, given that the page will load before the external request is finished?
We can get our JSON by using Meteor.HTTP.call("GET", "http://example.com/api/getmonkeys", callback ). Where do we put this request, and what do we put into our callback function in this situation?
We can control what happens on the server side and what happens on the client side by using the Meteor.isServer/Meteor.isClient conditions, or by putting our code into files called client and server folders. What code needs to be on the server side vs. the client side?
We determine what happens when the button is clicked by attaching a function to Template.monkeyTemplate.events['click #reload']. What goes into our callback function in this situation?
I will refrain from cluttering up the question with my crappy code. I am not looking for anyone to write or rewrite an application for me—I am just looking for the guidelines, standard patterns, best practices, and gotchas. Hopefully this will be instructive to other beginners as well.
I'm not sure if this is the "standard" template, but it serves the purpose pretty well.
Set up two data helpers for the template, monkeys and loading. First one will display the actual data once it's fetched, the latter will be responsible for notifying user that the data is not yet fetched.
Set up a dependency for these helpers.
In created function of the template, set loading helper to true and fetch the data with HTTP call.
In the callback, set the template data and fire the dependency.
html
<template name="monkeys">
{{#if loading}}
<div>Loading...</div>
{{/if}}
{{#if error}}
<div>Error!</div>
{{/if}}
{{#each monkeys}}
<div>{{name}}</div>
{{/each}}
<div><button class="monkeys-reloadMonkeys">Reload</button></div>
</template>
js
var array = null;
var dep = new Deps.Dependency();
Template.monkeys.created = function() {
reloadMonkeys();
};
Template.monkeys.events({
'click .monkeys-reloadButton': function(e,t) {
reloadMonkeys();
};
});
var reloadMonkeys = function() {
array = null;
dep.changed();
HTTP.get('http://example.com/api/getmonkeys', function(error, result) {
if(!error && result) {
array = result;
} else {
array = 0;
}
dep.changed();
});
};
Template.monkeys.monkeys = function() {
dep.depend();
return array ? array : [];
};
Template.monkeys.loading = function() {
dep.depend();
return array === null;
};
Template.monkeys.error = function() {
dep.depend();
return array === 0;
};

Resources