I am using mizzao/meteor-autocomplete and am having problems in trying to get it to work.
When viewing the page in my browser, I am getting no results at all when typing any text. I've created the appropriate collection:
Institutions = new Mongo.Collection("institutions");
and know that there is data in the actual db, however still no success.
I've included my files below.
publications.js (located in the server folder)
Meteor.publish('institutions', function(args) {
return Institutions.find({}, args);
});
registrationStart.js
I've two helpers; one that actually powers the search and the other that should be returning the institutions. I have also tried this with the token: '#' argument with no success.
if (Meteor.isClient) {
Template.registrationStart.helpers({
settings: function() {
return {
position: "top",
limit: 7,
rules: [{
collection: Institutions,
field: "name",
options: '',
matchAll: true,
template: Template.institutionSelectDisplay
}]
};
},
institutions: function() {
return Instititions.find();
}
});
Template.registrationStart.events({
"autocompleteselect input": function(event, template, doc) {
// Session.set(event.target.name, event.target.value);
console.log("selected: ", doc);
console.log("event.target.name: ", event.target.name);
console.log("event.target.value: ", event.target.value);
}
});
}
registrationStart.html template
<template name="registrationStart">
<div class="panel-body" id="loginForm">
<h2 class="pageTitle">veClient Registration</h2>
<form>
<div> </div>
<fieldset>
{{> inputAutocomplete settings=settings id="institution" class="input-xlarge" placeholder="type institution here"}}
</fieldset>
<div> </div>
<button type="submit" class="btn btn-primary btn-sm">Continue Registration</button>
</form>
</div>
</template>
And the template to be rendered
<template name="institutionSelectDisplay">
<p class="inst-state">{{city}}, {{state}}</p>
<p class="inst-name">{{name}}</p>
<p class="inst-description">{{email}}</p>
</template>
Problem resulted because there was no subscription to the "institutions" publication. So need to add a subscribe statement to the registrationStart.js file:
Meteor.subscribe('institutions');
Related
My Nested {{#each}} does not work.
I am Scraping article titles to my page and I'm using MongoDB/Mongoose. For each article (that I named News) I want user to post comments and view previous comments.
My routes, console.log, and the database itself shows that my api routes are working, but I can't get the comments to display.
My handlebars has the articles rendering to screen, but when GETting the comments it returns a 304.
The Articles display as I intend, it only has a problem with the comments
{{#each News}} is each article
{{#each comment}} is nested inside each News.
{{#each News}}
<div class="panel-block is-active">
<ul>
<li>
<div class="level-left">
<h2 class="subtitle">
<a href="https://www.atptour.com{{this.url}}">
{{this.headline}}
</a>
</h2>
</div>
</li>
<li>
<img width="348" src="https://www.atptour.com{{this.thumbNail}}">
</li>
<li>
<p>{{category}}</p>
</li>
<li>
{{!-- on click remove class .is-hidden on #getComments --}}
<p id="showComments" data-id="{{this._id}}" >Comments</p>
</li>
<li>
{{#each comment}}
<article id="getComments" class="media is-hidden">
<div class="media-content">
<div class="content">
<p>
<small>{{this.time}}</small>
<br />
{{this.body}}
</p>
</div>
</div>
<div class="media-right">
<button class="delete"></button>
</div>
</article>
{{/each}}
{{!-- on click remove class .is-hidden on #postComments --}}
<a id="addComment" >Add Comment</a>
{{!-- TO POST A COMMENT --}}
<article id="postComment" class="media is-hidden">
<div class="media-content">
<div class="field">
<p class="control">
<textarea
class="textarea"
placeholder="Add a comment..."
></textarea>
</p>
</div>
<div class="level">
<div class="level-left">
<div class="level-item">
<a class="button is-info">Submit</a>
</div>
</div>
</div>
</div>
</article>
</li>
</ul>
</div>
{{/each}}
In the {{#each comment}} {{this.time}} and {{this.body}} is blank. But if I simply put {{this}} I get the _id if each comment. The context must be wrong but I don't know why. The comments are properly associated to the News because I can check the get route and it's all there correctly.
Here are the schemas if it helps
new articles
const NewsSchema = new Schema({
headline: {
type: String,
required: true,
},
thumbNail: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
// Comments will populate the News
comment: [
{
type: Schema.Types.ObjectId,
ref: "Comment",
},
],
});
const News = mongoose.model("News", NewsSchema);
comments
const CommentSchema = new Schema({
time: {
type: Date,
default: Date.now,
required: true,
},
body: {
type: String,
required: true,
},
});
const Comment = mongoose.model("Comment", CommentSchema);
It wasn't a handlebars problem at all!
It was a route problem.
I was doing this:
app.get("/", function(req, res) {
db.News.find()
.then(News => {
res.render("index", { News });
})
.catch(err => {
console.log(err);
});
});
When I should have been doing this
app.get("/", function(req, res) {
db.News.find()
.populate("comment")
.then(News => {
res.render("index", { News });
})
.catch(err => {
console.log(err);
});
});
I forgot to populate in the res.render. (I was populating the api route, but not this 1!)
This are the event listeners for my two templates,specified in events.js
// event listeners on the addSiteForm template
Template.addCommentForm.events({
// this runs when they click the add button... you need to compete it
'click .js-add-comment':function(event){
var comment_text = $('#comment_input').val();// get the form value using jquery...
var user = 'anonymous person';
// the 'this' variable contains
// the data that this template is displaying
// which is the Website item
var site = this;
if (Meteor.user()){
user = Meteor.user().emails[0].address
}
var comment = {"text":comment_text,
"siteId":site._id,
"createdOn":new Date(),
"createdBy":user};// create a simple object to insert to the collectoin
Comments.insert(comment);
console.log("events start");
console.log("totla coomets are "+Comments.find({}).count()+"\n");
console.log("commenst in site "+Comments.find({siteId:site._id}).count()+"\n");
console.log("site id is "+site._id);
console.log("events end");
return false;
}
});
// event listeners on the addSiteForm template
Template.addSiteForm.events({
// this runs when they click the add button... you need to compete it
'click .js-add-site':function(event){
var url = $('#url_input').val();// get the form value using jquery...
var user = 'anonymous person';
if (Meteor.user()){
user = Meteor.user().emails[0].address
}
var site = {"url":url,
"createdOn":new Date(),
"createdBy":user};// create a simple object to insert to the collectoin
Websites.insert(site);
return false;
}
});
The router function is specified in router.js
Router.configure({
layoutTemplate: 'ApplicationLayout'
});
// the main route. showing the list of sites.
Router.route('/', function () {
this.render('siteList');
});
// this route is for the discussion page for a site
Router.route('/discussSite/:_id', function () {
var siteId = this.params._id;
site = Websites.findOne({_id:siteId});
this.render('discussSite', {data:site});
});
the main.js contain the collections
// shared code
Websites = new Mongo.Collection("websites");
Comments = new Mongo.Collection("comments");
helpers is
// this helper gets the data from the collection for the site-list Template
Template.siteList.helpers({
'all_websites':function(){
return Websites.find({});
},
'safer_email':function(email){
if (email.indexOf('#')!=-1){// we have an email
return email.split('#')[0];
}
else{// probably anonymouse.
return email;
}
}
});
Template.discussSite.helpers({
'comments':function(siteId){
//console.log("helper comments "+this.site);
// complete the code here so that it reruns
console.log(this.params.site._id);
return Comments.find({siteId:siteId});
// all the comments with a siteId equal to siteId.
}
});
the html file is in main.html.here it first displays the websites available in the webpage.On clicking this the user is routed to a new page.Here the user can add comments for the website
<head>
<title>week_4_peer_assessment</title>
</head>
<body>
</body>
<!-- this is the template that iron:router renders every time -->
<template name="ApplicationLayout">
<div class="container">
Home
{{>loginButtons}}
<h1>SiteAce - discuss your favourite websites</h1>
<!-- iron router will select what to render in place of yield-->
{{> yield }}
</div>
</template>
<template name="discussSite">
<h3>Discussing: {{url}} </h3>
{{> addCommentForm}}
<!-- write some code here that iterates through the comments and displays
the comment text and the author -->
<!-- clue - you have already written the 'comments' helper function -->
<ul>
{{#each comments}}
<li>{{text}} (added by {{safer_email createdBy}})
</li>
{{/each}}
</ul>
</template>
<template name="addCommentForm">
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<input type="text" id="comment_input" class="form-control" placeholder="Enter your comment">
<input type="hidden" id="site_id" class="form-control" placeholder="Enter your comment">
<span class="input-group-btn">
<button class="btn btn-default js-add-comment" type="submit">Add!</button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div>
</template>
<template name="siteList">
Fill in the form and click submit to add a site:
{{>addSiteForm}}
<h3>Sites you have added:</h3>
<ul>
{{#each all_websites}}
<li>{{url}} (added by {{safer_email createdBy}})
<br/>discuss
<br/>visit site
</li>
{{/each}}
</ul>
</template>
<template name="addSiteForm">
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<input type="text" id="url_input" class="form-control" placeholder="Enter website URL...">
<span class="input-group-btn">
<button class="btn btn-default js-add-site" type="submit">Add!</button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div>
</template>
The starup.js contains the start up code run by the server
Meteor.startup(function(){
if (!Websites.findOne()){// nothing in the database yet
var site = {"url":"http://www.google.com",
"createdOn":new Date(),
"createdBy":"Michael"};// create a simple object to insert to the collectoin
Websites.insert(site);
site = {"url":"http://www.yeeking.net",
"createdOn":new Date(),
"createdBy":"Janet"};// create a simple object to insert to the collectoin
Websites.insert(site);
site = {"url":"http://www.coursera.org",
"createdOn":new Date(),
"createdBy":"Jose"};// create a simple object to insert to the collectoin
Websites.insert(site);
}
});
This is what your template should be: (I removed safer_email. It was erroring. Not sure if it was something else you were working on.)
<template name="discussSite">
<h3>Discussing: {{url}} </h3>
{{> addCommentForm}}
<!-- write some code here that iterates through the comments and displays
the comment text and the author -->
<!-- clue - you have already written the 'comments' helper function -->
<ul>
{{#each comments}}
<li>{{text}} (added by {{createdBy}})</li>
{{/each}}
</ul>
</template>
helper:
your helper function is designed to take in siteId as an input, yet you are not passing one to it.
this._id or Router.current().params._id will get you the siteId that you are looking for. You'd used this.site._id which used to error out as this.site was not valid.You can further see what the this object contains by doing a console.log(this). That would have helped you sort this out faster.
'comments':function(){
console.log("site id in helper is ",this._id); // or Router.current().params._id;
console.log(Comments.find({siteId:this._id}).fetch());
return Comments.find({siteId:this._id});
}
I have a reactive-table in Meteor that lists usernames and email addresses from the users collection. I want to click on the email address and have a modal pop up with that email address filled in the "to" field.
Template with reactive table and email modal:
<template name="Compete">
{{> reactiveTable class="table table-bordered table-hover" settings=settings }}
{{> emailModalTemplate settings.fields}}
</template>
settings helper:
Template.Compete.helpers({
settings: function(){
if (Meteor.user()){
var col = Meteor.users.find({ }, {fields: {profile:1, emails:1} } );
var email = 'emails.0.address';
return {
collection: col,
showFilter: false,
showNavigation: 'never',
fields: [{
key: 'profile.userName',
label: 'Player'
}, {
key: email,
label: 'Email',
fn: function(email){
return new Spacebars.SafeString(
''+email+''
);
}
}]
};
}
},
And here is the email modal template:
<template name="emailModalTemplate">
<div class="modal fade" id="emailModal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Send Email</h4>
</div>
<div class="modal-body">
<form role="form" id="email-form">
<input type="email" id="inputEmail" placeholder="{{addToAddr}}">
<input type="text" id="inputSubject" placeholder="Subject">
<textarea id="inputBody" rows="5" placeholder="Message"></textarea>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Send</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
At this point, the {{addToAddr}} helper is just console logging "this" but all I can get back are the actual keys from the reactive table settings, but not the values. Am I passing in the right thing by passing in the reactive table helper?
What else could I pass in to the email modal template to get the actual email values that I can then use to populate the email to address field?
EDIT: One last note to add:
My click event is as follows:
'click .sndLnk': function(e){
e.preventDefault();
console.log( $(e.currentTarget).attr('value') );
$('#emailModal').modal('show');
}
The console.log here accurately shows the email address I want to pass in, but how do I pass that in to the email modal form? I assumed via the helper, but maybe that's wrong?
I tinkered with it some more and resolved it by setting a session variable in a new "external" helper function, "external" in that it is just a var I set, not within Template.myTemplate.helpers:
var openEmailModal = function(email) {
Session.set('theEmailAddr', email);
$('#emailModal').modal('show');
}
Then I modified the click event to call the new var/function:
'click .sndLnk': function(e){
e.preventDefault();
var email = $(e.currentTarget).attr('value');
openEmailModal(email);
}
At the end of the day this seems pretty basic, I made it into much more than it was I guess... Thanks to all who viewed.
i have a page split in two:
on left {{> expenseList }}
on right {{> Template.dynamic template="expenseForm" data=expenseData}}
on left its a list
on right its a form to enter elements in the list.
desired behaviour:
1: form is empty and ready to add expenses
2: add an expense, form is cleared and ready for next
3: if i click on the left on the submitted expense from {{> expenseList }} the form is populated with the data of that expense and i can update that entry.
i cannot get it to work though. my form doesn't dynamically change the data. i tried to simplify code and add it below
the page code:
<template name="claimsPage">
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-md-4">
{{> expenseList }}
</div>
{{> Template.dynamic template="expenseForm" data=expenseData}}
</div>
</div>
</template>
the page js
Template.claimsPage.helpers({
expenseData: function() {
var data = Session.get('expenseData');
return data;
}
});
the list js
Template.expenseList.events({
'click a': function(e) {
e.preventDefault();
var data = {
date: this.date,
description: this.description,
_id: this._id
};
Session.set('expenseData', data);
}
});
the form js
Template.expenseForm.helpers({
purposes: function() {
return Purposes.find();
},
expenseData: function(){
return Session.get('expenseData')
}
});
Template.expenseForm.events({
'click #add-expense': function(e){
e.preventDefault();
var template = Template.instance();
var expense = {
date: template.find("[name='date']" ).value,
description: template.find("[name='description']" ).value,
purposeName: template.find("[name='purpose'] option:selected" ).value,
_id: Random.id()
};
Meteor.call('addExpense', expense, function(error, result) {
if (error)
return alert(error.reason);
template.find("form").reset();
});
}
});
the form html
<template name="expenseForm">
<form>
<input name="date" type="text" value="">
<input name="description" type="text" value="">
<select name="purpose">
<option value="">Select a purpose</option>
{{#each purposes}}
<option value="{{this.purposeName}}">{{this.purposeName}}</option>
{{/each}}
</select>
<button id="add-expense" type="button">Submit</button>
</form>
</template>
I would really appreciate your help with this,
Thanks,
Cezar
I'm trying to construct a view in my app that will pop up polling questions in a modal dialog region. Maybe something like this for example:
What is your favorite color?
>Red
>Blue
>Green
>Yellow
>Other
Submit Vote
I've read that Marionette js doesn't support forms out of the box and that you are advised to handle on your own.
That structure above, branch and leaves (question and list of options), suggests CompositeView to me. Is that correct?
How do I trigger a model.save() to record the selection? An html form wants an action. I'm unclear on how to connect the form action to model.save().
My rough draft ItemView and CompositeView code is below. Am I in the ballpark? How should it be adjusted?
var PollOptionItemView = Marionette.ItemView.extend({
template: Handlebars.compile(
'<input type="radio" name="group{{pollNum}}" value="{{option}}">{{option}}<br>'
)
});
var PollOptionsListView = Marionette.CompositeView.extend({
template: Handlebars.compile(
//The question part
'<div id="poll">' +
'<div>{{question}}</div>' +
'</div>' +
//The list of options part
'<form name="pollQuestion" action="? what goes here ?">' +
'<div id="poll-options">' +
'</div>' +
'<input type="submit" value="Submit your vote">' +
'</form>'
),
itemView: PollOptionItemView,
appendHtml: function (compositeView, itemView, index) {
var childrenContainer = $(compositeView.$("#poll-options") || compositeView.el);
var children = childrenContainer.children();
if (children.size() === index) {
childrenContainer.append(itemView.el);
} else {
childrenContainer.children().eq(index).before(itemView.el);
}
}
});
MORE DETAILS:
My goal really is to build poll questions dynamically, meaning the questions and options are not known at runtime but rather are queried from a SQL database thereafter. If you were looking at my app I'd launch a poll on your screen via SignalR. In essence I'm telling your browser "hey, go get the contents of poll question #1 from the database and display them". My thought was that CompositeViews are best suited for this because they are data driven. The questions and corresponding options could be stored models and collections the CompositeView template could render them dynamically on demand. I have most of this wired and it looks good. My only issue seems to be the notion of what kind of template to render. A form? Or should my template just plop some radio buttons on the screen with a submit button below it and I write some javascript to try to determine what selection the user made? I'd like not to use a form at all and just use the backbone framework to handle the submission. That seems clean to me but perhaps not possible or wise? Not sure yet.
I'd use the following approach:
Create a collection of your survey questions
Create special itemviews for each type of question
In your CompositeView, choose the model itemView based on its type
Use a simple validation to see if all questions have been answered
Output an array of all questions and their results.
For an example implementation, see this fiddle: http://jsfiddle.net/Cardiff/QRdhT/
Fullscreen: http://jsfiddle.net/Cardiff/QRdhT/embedded/result/
Note:
Try it without answering all questions to see the validation at work
Check your console on success to view the results
The code
// Define data
var surveyData = [{
id: 1,
type: 'multiplechoice',
question: 'What color do you like?',
options: ["Red", "Green", "Insanely blue", "Yellow?"],
result: null,
validationmsg: "Please choose a color."
}, {
id: 2,
type: 'openquestion',
question: 'What food do you like?',
options: null,
result: null,
validationmsg: "Please explain what food you like."
}, {
id: 3,
type: 'checkbox',
question: 'What movie genres do you prefer?',
options: ["Comedy", "Action", "Awesome", "Adventure", "1D"],
result: null,
validationmsg: "Please choose at least one movie genre."
}];
// Setup models
var questionModel = Backbone.Model.extend({
defaults: {
type: null,
question: "",
options: null,
result: null,
validationmsg: "Please fill in this question."
},
validate: function () {
// Check if a result has been set, if not, invalidate
if (!this.get('result')) {
return false;
}
return true;
}
});
// Setup collection
var surveyCollection = Backbone.Collection.extend({
model: questionModel
});
var surveyCollectionInstance = new surveyCollection(surveyData);
console.log(surveyCollectionInstance);
// Define the ItemViews
/// Base itemView
var baseSurveyItemView = Marionette.ItemView.extend({
ui: {
warningmsg: '.warningmsg',
panel: '.panel'
},
events: {
'change': 'storeResult'
},
modelEvents: {
'showInvalidMessage': 'showInvalidMessage',
'hideInvalidMessage': 'hideInvalidMessage'
},
showInvalidMessage: function() {
// Show message
this.ui.warningmsg.show();
// Add warning class
this.ui.panel.addClass('panel-warningborder');
},
hideInvalidMessage: function() {
// Hide message
this.ui.warningmsg.hide();
// Remove warning class
this.ui.panel.removeClass('panel-warningborder');
}
});
/// Specific views
var multipleChoiceItemView = baseSurveyItemView.extend({
template: "#view-multiplechoice",
storeResult: function() {
var value = this.$el.find("input[type='radio']:checked").val();
this.model.set('result', value);
}
});
var openQuestionItemView = baseSurveyItemView.extend({
template: "#view-openquestion",
storeResult: function() {
var value = this.$el.find("textarea").val();
this.model.set('result', value);
}
});
var checkBoxItemView = baseSurveyItemView.extend({
template: "#view-checkbox",
storeResult: function() {
var value = $("input[type='checkbox']:checked").map(function(){
return $(this).val();
}).get();
this.model.set('result', (_.isEmpty(value)) ? null : value);
}
});
// Define a CompositeView
var surveyCompositeView = Marionette.CompositeView.extend({
template: "#survey",
ui: {
submitbutton: '.btn-primary'
},
events: {
'click #ui.submitbutton': 'submitSurvey'
},
itemViewContainer: ".questions",
itemViews: {
multiplechoice: multipleChoiceItemView,
openquestion: openQuestionItemView,
checkbox: checkBoxItemView
},
getItemView: function (item) {
// Get the view key for this item
var viewId = item.get('type');
// Get all defined views for this CompositeView
var itemViewObject = Marionette.getOption(this, "itemViews");
// Get correct view using given key
var itemView = itemViewObject[viewId];
if (!itemView) {
throwError("An `itemView` must be specified", "NoItemViewError");
}
return itemView;
},
submitSurvey: function() {
// Check if there are errors
var hasErrors = false;
_.each(this.collection.models, function(m) {
// Validate model
var modelValid = m.validate();
// If it's invalid, trigger event on model
if (!modelValid) {
m.trigger('showInvalidMessage');
hasErrors = true;
}
else {
m.trigger('hideInvalidMessage');
}
});
// Check to see if it has errors, if so, raise message, otherwise output.
if (hasErrors) {
alert('You haven\'t answered all questions yet, please check.');
}
else {
// No errors, parse results and log to console
var surveyResult = _.map(this.collection.models, function(m) {
return {
id: m.get('id'),
result: m.get('result')
}
});
// Log to console
alert('Success! Check your console for the results');
console.log(surveyResult);
// Close the survey view
rm.get('container').close();
}
}
});
// Create a region
var rm = new Marionette.RegionManager();
rm.addRegion("container", "#container");
// Create instance of composite view
var movieCompViewInstance = new surveyCompositeView({
collection: surveyCollectionInstance
});
// Show the survey
rm.get('container').show(movieCompViewInstance);
Templates
<script type="text/html" id="survey">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" > A cool survey regarding your life </h3>
</div>
<div class="panel-body">
<div class="questions"></div>
<div class="submitbutton">
<button type="button" class="btn btn-primary">Submit survey!</button>
</div>
</div>
</div >
</script>
<script type="text/template" id="view-multiplechoice">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<% _.each( options, function( option, index ){ %>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="<%= index %>" value="<%= option %>"> <%= option %>
</label>
</div>
<% }); %>
</div>
</div>
</script>
<script type="text/template" id="view-openquestion">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<textarea class="form-control" rows="3"></textarea>
</div>
</div >
</script>
<script type="text/template" id="view-checkbox">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<% _.each( options, function( option, index ){ %>
<div class="checkbox">
<label>
<input type="checkbox" value="<%= option %>"> <%= option %>
</label>
</div>
<% }); %>
</div>
</div>
</script>
<div id="container"></div>
Update: Added handlebars example
Jsfiddle using handlebars: http://jsfiddle.net/Cardiff/YrEP8/