Iterating unknown object in Meteor - mongodb

I am having trouble displaying data much like what is described here, but my object is not conveniently labelled in numbers: How do I iterate over an unknown object in a Meteor Spacebars template?
Data maybe be nested more than once, and might differ but always end in an array.
I am open to new data structures too.
Sample Data:
{
"Category A":
{
"Sub-Category1":
{
"Sub-Sub-Category1": ['Value1', 'Value2']
},
"Sub-Category2":
{
"Sub-Sub-Category2": ['Value3'],
"Sub-Sub-Category3": ['Value4']
}
},
"Category B":
{
"Sub-Category1": ['Value5']
}
}

You're going to need a recursive template to handle arbitrary nesting, a helper that enumerates the keys of an object, and a helper that gets the value of the parent object corresponding to a key.
html:
<template name="nest">
{{#if isArray}}
{{#each this}}
{{this}} <!-- we're assuming that array elements aren't themselves objects -->
{{/each}}
{{#elseif isObject}}
{{#each keys}}
{{#with value}}
{{> nest }}
{{/with}}
{{/each}}
{{else}} <!-- catch the case of a scalar key value (no array) -->
{{this}}
{{/if}}
</template>
js:
Template.nest.helpers({
isArray(){
return typeof(this) === "object" && this.length && this.length > 0;
},
isObject(){
return typeOf(this) === "object" && !this.length;
},
keys(){
return Object.keys(this); // returns an array of strings
},
value(){
return Template.parentData()[this]; // look up the hierarchy to get the parent object then select the data for the current key
}
});

Related

Repeating Meteor template with a specific item in mongodb collection

I have a collection with this item:
{
"item1": ["foo", "bar", "baz"],
"item2": ...
}
I made a helper function to repeat a template for each item in item1
Template.temp.helpers({
items: function() {
return Col.find({},{item1:1});
}
});
and this is the template
<template name="temp">
{{#each items}}
{{> anotherTemplate}}
{{/each}}
</template>
But I get back an empty array. Why doesn't it work?
Try with {{#each items.item1}}
Because, Col.find({},{item1:1}) just remove all other field from result.
Limit Fields to Return from a Query
It's like SELECT item1 FROM table in SQL
If you need repeat your template for each item in item1, you need to precise which array should be use.
<template name="temp">
{{#each items.item1}}
{{> anotherTemplate}}
{{/each}}
</template>
Meteor mongo methods are a little different you cannot use.
return Col.find({},{item1:1});
you can use it this way:
return Col.find({},{fields:{'item1':1}});
Maybe you want:
{{#each items}}
{{#each item1}}
{{> anotherTemplate}}
{{/each}}
{{/each}}
Or this:
Template.temp.helpers({
items: function() {
return Col.find({},{item1:1}).map(function(item){
return item.item1
})
}
});
That way items will return the item1 array. Originally it was returning an array of objects with just one element, the item1 arrays of each object:
[{
item1: ["foo","bar","baz"]
},
{
item1: ["lah","nah","jah"]
},
{
item1: ["see","gee","bee"]
}]
this way you'll get an array of arrays: [["foo","bar","baz"], ["lah","nah","jah"], ["see","gee","bee"]]
I think you need to do return something like this from helper
return Col.findOne({}).item1
It should work now
Thanks

Check if mongo document field exists

I want to check if the field "imageUrl" are inserted from a form into a MongoDb collection.
If it has a value, I want to return the URL, else return false. I'm not used to Mongo, and wonder how I can achieve this. Not every entry have a imageUrl field, but some do. So I need to check if it even exists for every document.
Here's my helper, where I want to have the logic:
Template.CatchesList.helpers({
catches: function () {
return Catches.find({}, {sort: {date: -1}});
},
image: function() {
if (this.imageURL) // Need proper logic here
return true;
else
return false;
}
});
And in my template:
{{#if image}}
<td><img width="100" src="{{imageUrl}}"></td>
{{/if}}
MongoDB has the $exists operator to filter results where the property does not exist in the document. So you can change your query to:
return Catches.find({ "imageUrl": { "$exists": true }}, { "sort": { "date": -1 }});
And then only items that have the property will be returned.
If you are only talking about templates, then a simple conditional test on the field returns logically where it is present or not present:
{{#each catches}}
{{> catch }}
{{/each}}
Then:
<template name="catch">
<tr>
<!-- other fields -->
<td>{{#if imageUrl}}<img width="100" src="{{imageUrl}}">{{/if}}</td>
</tr>
</template>
Where only the logic contained within {{#if}}{{/if}} will actually render.
I did this in an app and it worked for me:
Courses.find({owned_by_contract:user_contract._id}).forEach(function(doc){
var foo = ("your_field_name" in doc) ? doc.course_objective_english.value:"N/A";
}):

MeteorJS: Autoform + CollectionFS, associating images from an FS.Collection with Corresponding Mongo.Collection Document?

I'm building a very small Meteor application simply to get a better understanding of Autoform and CollectionFS, and their use together. I currently have everything set up with the following packages:
iron:router, aldeed:autoform, aldeed:collection2, cfs:standard-packages,
cfs:filesystem, cfs:autoform
I have a sample Mongo Collection assigned to "Books" set up with a SimpleSchema attached, with fields from the demo like title and author. The corresponding code for the file upload is:
fileId: {
type: String,
autoform: {
afFieldInput: {
type: "cfs-file",
collection: "images"
}
}
}
The FS.Collection code is:
Images = new FS.Collection("images", {
stores: [new FS.Store.FileSystem("images", {path: "~/uploads"})]
});
This is in conjunction to a quickform: {{> quickForm collection="Books" id="insertBookForm" type="insert"}}
The insert is fine, and I can iterate over the documents and display the various fields using spacebars and a helper function called "books", like so:
{{#each books}}
<li>{{title}} by {{author}}</li>
{{/each}}
I can also iterate over images uploaded to the FS.Collection with a helper returning the entire collection called "files," and looping over them like so:
{{#each files}}
<img src="{{this.url}}" />
{{/each}}
The issue I'm having is linking the two together. I want to be able to do something along the lines of:
{{#each books}}
<li>{{title}}, by {{author}} <img src="The-Corresponding-Image}}" /></li>
{{/each}}
Obviously not that exact layout, but I basically just want to be able to print images with their corresponding titles and authors to be able to use autoform with collectionfs for my needs.
I'm stuck trying to retrieve the fileId from a specific document in the Books collection, and then plugging that into an Images.findOne({fileId: fileId}) and linking the two together.
Can anyone point me in the right direction?
I was able to figure it out thanks to Ethaan's guidance. What I had to do was the following:
Autoform Hook:
AutoForm.hooks({
insertBookForm: {
after: {
insert: function(error, result, template) {
insertedFile = Books.findOne(result).fileId;
Images.update({_id: insertedFile}, {$set: {'book': result}});
}
}
}
});
I set a field of 'book' to the _id of the document being inserted (stored in the result parameter) right after it was inserted.
This is my corresponding HTML:
{{#each books}}
<li>{{title}} by {{author}}</li>
{{#with files}}
<img src="{{this.url}}" />
{{/with}}
{{/each}}
And my helpers:
Template.layout.helpers({
books: function () {
return Books.find({});
},
files: function() {
return Images.findOne({book: this._id});
}
});

mongodb query field in object that is being edited

How can I query a field in an object? My html retrieves all the objects in array called 'postcards'
Meteor.user.profile.postcards [
{
_id: 84fh83f,
field_one: "a name",
field_two: " winter whether",
field_three: " lost more writing"
},
{
_id: 6jsf58s,
field_one: "another name",
field_two: " topical issues ",
field_three: " lost more writing"
}
]
Note: I used random.Id() so each object in the array can be uniquely identified.
Setting a session value to this._id when the user is focused on the input field will retrieve this unique id, however, I would like to query the actual field in focus. The value in these fields are projected within the text input area by using the spacebars syntax within the html.
Can I somehow assign the name within the curly braces of the value attribute to a variable? Then query?
Is there a whole new way to achieve this?
I want to update that specific field in this object instead updating the entire object.
HTML:
{{#with currentUser.profile}}
{{#each postcards}}
<input id="one" value="{{field_one}}" type="text">
<input id="two" value="{{field_two}}" type="text">
<input id="three" value="{{field_three}}" type="text">
{{/each}}
{{/with}}
client.js
Within events, I would like to update the field on focus upon keyup.
Templates.myTemplate.events({
'keyup input[type=text]': _.throttle(function(event) {
Meteor.users.update(this._id, {$set: {**fieldbeingedited**: event.target.value}});
}, 500);
});
What you want to have is an ES6 capability named 'Computed property names'.
This is what is looks like :
var x = 'hello',
obj = {
[x]: 'world'
};
console.log(obj); // Object {hello: "world"}
You have two options :
- you use the meteor harmony package to transpile your es6 to es5 (https://github.com/mquandalle/meteor-harmony)
- you build your object first
To build you object first :
var obj = {};
obj[ this.targetField ] = event.target.value
Meteor.users.update(this._id, {$set: obj});

Meteor .find() from Collection returns [object Object]

Running on Ubuntu
Data.js
//Collections
Database = new Meteor.Collection('data');
if (Meteor.isClient) {
Template.main.data = function () {
var c = Database.find();
return c;
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
data.html
<head>
<title>data</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
{{data}}
</template>
I inserted into the db using mongo:
> db.Database.insert({title: 'ShouldWork'});
> db.Database.find();
{ "_id" : ObjectId("5296403855ee6e1350b35afb"), "title" : "ShouldWork" }
Yet when I run the site it just returns [object Object]..
There should be autopublish on and insecure,
This has become quite the roadblock for me to clear in learning the framework.
This is expected. This is because the results of .find() are always a cursor and have multiple objects.
You have to decide which one you want or if you want to loop through each one.
1) You want to use one result:
var c = Database.findOne();
or 2) You want to iterate through each one:
{{#each data}}
{{title}}
{{/each}}
Additionally be sure to use the property of {{data}} because {{data}}, even with findOne is still an [Object object]. You should use something like {{data.title}} instead depending on the property you want to use.
How to access data in Mongo DB from html ?
First of all you need to have the Mongo DB instance present in global variable i:e it must be declared in any .js file as below. It is not part of client or server code
Say we create a Collection of Events in one of the js files.
EventList = new Mongo.Collection('Events');
Now, in order to access all the objects from HTML, we would need a Helper function inside client in our .js file. Below is Helper used:-
Template.viewEvent.helpers ({
//NOTE : 'event' is the name of variable from html template
'event' : function () {
//returns list of Objects for all Events
return EventList.find().fetch();
}
'users' : function () {
//returns reference to Document for all users
return UserList.find().fetch();
}
});
Just left to Display content on .html:-
Say EventList Collection has fields Event_Name, Event_Date. Below would be the html template code
<template name="viewEvent">
<h2>View Event</h2>
<!-- Iterate on all Objects fetched by the Helper Class-->
{{#each event}}
{{Event_Name}} : {{Event_Date}}
{{/each}}