Meteor: Using an HTML Template to add to the DOM. - meteor

I'm building an app with Meteor.js, and I've got a form where I would like to be able to allow the user to add a new row to the form when they click a button (button.addExperience). I'm using an HTML Template to populate each row of the form.
How would I have the template (experienceRow) rendered each time the user clicks the button?
See example code below:
<body>
<form>
{{> experienceRow }}
</form>
<button class="addExperience">Add Experience</button>
</body>
<template name="experienceRow">
<div id={{experienceNumber}} class='experienceRow'>
<input type="text" placeholder="name" value="" class="name">
<input type="text" placeholder="address" value="" class="address">
<input type="text" placeholder="phone" value="" class="phone">
</div>
</template>

You will need to use an each block along with a reactive variable. That variable could be a ReactiveVar, a session varaible, a local collection, etc. Here's an example implementation using a ReactiveVar to hold an array of ids:
html
<body>
{{> experienceForm }}
</body>
<template name="experienceForm">
<form>
{{#each experienceIds}}
{{> experienceRow }}
{{/each}}
</form>
<button class="addExperience">Add Experience</button>
</template>
<template name="experienceRow">
<div id={{this}} class='experienceRow'>
<input type="text" placeholder="name" value="" class="name">
<input type="text" placeholder="address" value="" class="address">
<input type="text" placeholder="phone" value="" class="phone">
</div>
</template>
js
Template.experienceForm.onCreated(function() {
this.experienceIds = new ReactiveVar(Random.id());
});
Template.experienceForm.helpers({
experienceIds: function() {
return Template.instance().experienceIds.get();
}
});
Template.experienceForm.events({
'click .addExperience': function(e, template) {
e.preventDefault();
var ids = template.experienceIds.get();
ids.push(Random.id());
template.experienceIds.set(ids);
}
});
Note that you'll need to meteor add reactive-var for this to work.
Recommended reading: scoped reactivity.

Related

materialize modal in Meteor how to trigger modal

I think I'm confused on how to trigger a modal from the Materialize Modal package. Materialize Modal I've got the package loaded and I'd like to use one of the pre-baked modals.
JS:
MaterializeModal.form({
title: "Enter some Data!",
bodyTemplate: "my-form",
callback: function(error, response) {
if (response.submit) {
// Iterate over form results & display.
for (var field in response.form) {
Materialize.toast(field + ": " + response.form[field], 5000, "green");
}
} else {
Materialize.toast("Cancelled by user!", 5000, "red");
}
}
});
MaterializeModal.display({
bodyTemplate: "my-form"
});
And the template:
<template name="my-form">
<div class="row">
<div class="input-field col s6">
<input id="first_name" type="text" name="first-name" class="validate">
<label for="first_name">First Name</label>
</div>
<div class="input-field col s6">
<input id="last_name" type="text" name="last-name" class="validate">
<label for="last_name">Last Name</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input disabled id="disabled" type="text" name="disabled-input" class="validate">
<label for="disabled">Disabled</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="password" type="password" name="password" class="validate">
<label for="password">Password</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="email" type="email" name="email" class="validate">
<label for="email">Email</label>
</div>
</div>
</template>
If I've got a button, say:
<button id="client-search" class="someClass">Search</button>
How would the modal be triggered from the button click? Apologies for the dumb question.
As per the docs: Specify the Modal ID in button data-target, add the class modal-trigger to the button and register the trigger. See the docs at http://materializecss.com/modals.html
HTML
<button id="client-search" class="modal-trigger">Search</button>
JavaScript
Template.my-form.onRendered({
$('.modal-trigger').leanModal();
});
There are some caveats though: modals and dropdowns do not work in the latest version of Materialize on Meteor because Meteor uses a crazy old outdated version of jQuery (it seem impossible to change Meteor's jquery version without branching and repackaging your own Meteor distro) and Materialize relies on newer jquery functionality. I worked around this by using an older version of Materialize, specifically poetic:materialize-scss#1.97.1 (SASS version of Materialize).

Creating a dynamic form with Meteor Spacebars

I want to create a form that shows different sections depending on which template it belongs to. I want the data that the user enters to be reactive: if the user changes from one template to another, her previous entries should still be visible. I plan to create a "form" template, so that I can have a single Template.form.events function that will set the appropriate Session variables as the form is filled in.
My question is about how to populate the form dynamically. Here's how I've attempted to go about this, but all the fields appear all the time.
<body>
{{> form1}}
{{> form2}}
<h1>Log in</h1>
{{> form name=1 password=1}}
</body>
<template name="form1">
{{#form name=1 species=1}}
<h1>Form 1</h1>
{{/form}}
</template>
<template name="form2">
<h1>Form 2</h1>
{{>form name=1 planet=1 asMenu=1}}
</template>
<template name="form">
{{#if name=1}}
<input type="text" name="name" placeholder="Name" value="">
{{/if}}
{{#if species=1}}
<input type="text" name="species" placeholder="Species" value="">
{{/if}}
{{#if planet=1}}
{{#if asMenu=1}}
<select name="">
<option value="Erth">Erth</option>
<option value="Othr">Othr</option>
</select>
{{else}}
<input type="text" name="planet" placeholder="Planet" value="">
{{/if}}
{{/if}}
{{#if password=1}}
<input type="password" name="password" value="">
{{/if}}
<br />
<button type="submit">Submit</button>
</template>
Can you help me to understand how to resolve this?
Firstly spacebars doesn't support comparisons so {{#if var=1}} is invalid syntax. However 1 is true so I'm going to take advantage of that to replace {{#if name=1}} with {{#if name}}:
<body>
{{> form1}}
{{> form2}}
<h1>Log in</h1>
{{> form name=1 password=1}}
</body>
<template name="form1">
<h1>Form 1</h1>
{{>form name=1 species=1}}
</template>
<template name="form2">
<h1>Form 2</h1>
{{>form name=1 planet=1 asMenu=1}}
</template>
<template name="form">
{{#if name}}
<input type="text" name="name" placeholder="Name" value="">
{{/if}}
{{#if species}}
<input type="text" name="species" placeholder="Species" value="">
{{/if}}
{{#if planet}}
{{#if asMenu}}
<select name="">
<option value="Erth">Erth</option>
<option value="Othr">Othr</option>
</select>
{{else}}
<input type="text" name="planet" placeholder="Planet" value="">
{{/if}}
{{/if}}
{{#if password}}
<input type="password" name="password" value="">
{{/if}}
<br />
<button type="submit">Submit</button>
</template>

Meteor Template Not Rendering Content Inside HTML Elements

What I'm trying to do
It seems so simple, but I'm trying to use Meteor's account system with Iron:Router to setup an admin system for a web app. The admin section is the only part of the app with accounts. The admin section is to be at /admin and has a login template, and a data template which should be displayed after the admin logs in.
The code
Base Routers
Router.route('/', {
template: 'user' // Not shown; works fine
});
Router.route('/admin', {
template: 'adminDataPage'
});
Templates
<template name="adminLoginPage">
<div class="cover-wrapper-outer page-admin login">
<div class="cover-wrapper-inner">
<div class="cover-container">
<div class="cover">
<form class="admin-login-form">
<div class="alert alert-danger login-failure">
The username or password is incorrect.
</div>
<input class="form-control username" name="username" type="text" placeholder="Username">
<input class="form-control password" name="password" type="password" placeholder="Password">
<input class="form-control submit" name="submit" type="submit" value="Log in">
</form>
</div>
</div>
</div>
</div>
</template>
<template name="adminDataPage">
<div class="container-fluid page-admin admin-content">
<div class="row">
<div class="col-xs-12">
<div class="scrolling-table">
<table class="table table-striped">
<thead>
<tr>
<th>Year</th>
<th>Make</th>
<th>Model</th>
<th>Engine</th>
</tr>
</thead>
<tbody>
<tr>
<td>Year</td>
<td>Make</td>
<td>Model</td>
<td>Engine</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<template name="adminLoggingIn">
Logging in...
</template>
(This is incomplete as of right now but it doesn't relate to the problem.)
The issue
The content inside the adminDataPage template is never rendered, no matter what I do. However, anything that is not inside an HTML element is rendered. For example, this would render:
<template name="adminDataPage">
Hello world!
</template>
The world in the following would not render:
<template name="adminDataPage">
Hello <span>world</span>!
</template>
The same goes with the adminLoggingIn template. However, anything in the adminLoginPage template works without an issue.
In essence, the login box is displayed, the client-side Javascript (not shown) takes care of logging in the user, the user sees Logging in..., then the user sees nothing and nothing is shown in the browser's inspector (unless there is un-nested plain text in the adminDataPage template).
What I've tried
If currentUser
<template name="adminDataPage">
{{#if currentUser}}
<div class="container-fluid page-admin admin-content">
<div class="row">
<div class="col-xs-12">
<div class="scrolling-table">
<table class="table table-striped">
<thead>
<tr>
<th>Year</th>
<th>Make</th>
<th>Model</th>
<th>Engine</th>
</tr>
</thead>
<tbody>
<tr>
<td>Year</td>
<td>Make</td>
<td>Model</td>
<td>Engine</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{{else}}
<div class="cover-wrapper-outer page-admin login">
<div class="cover-wrapper-inner">
<div class="cover-container">
<div class="cover">
<form class="admin-login-form">
<div class="alert alert-danger login-failure">
The username or password is incorrect.
</div>
<input class="form-control username" name="username" type="text" placeholder="Username">
<input class="form-control password" name="password" type="password" placeholder="Password">
<input class="form-control submit" name="submit" type="submit" value="Log in">
</form>
</div>
</div>
</div>
</div>
{{/if}}
</template>
This will render the login page when not logged in, as expected. However, once logged in, nothing is rendered. I have also tried integrating {{#if loggingIn}} but to no avail.
Iron:Router hooks
Router.route('/', {
template: 'user'
});
Router.route('/admin', {
template: 'adminDataPage'
});
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render('adminLoggingIn');
} else {
this.render('adminLoginPage');
}
} else {
this.next();
}
};
Router.onBeforeAction(requireLogin, {only: 'admin'});
This will render the login page if not logged in, as expected, but will not render any content inside HTML elements within the adminLoggingIn or adminLoginPage templates.
Specifications
Meteor v1.1.0.3
Iron:Router v1.0.9
Conclusion
I'm so very confused why Meteor (or Iron:Router) refuses to render certain content in certain contexts. Of course, if anybody has any ideas to help resolve this, please shoot; it'd be much appreciated.
Blaze.render() appears to be at fault here. Using Blaze.render(Template.adminDataPage, document.body) in the console will not render anything but—just like in my problem—plaintext nested directly inside the body element. It will not nest any DOM elements beneath body.
To test, I created a DOM element <div class="test"></div>, nested it within the body of the page, and called Blaze.render(Template.adminDataPage, $('.test')[0]) and it worked. It wasn't formatted correctly, but it worked.
Adding a layout template for Iron:Router to use fixes the issue, like so:
<template name="adminPage">
<div class="test">
{{> yield}}
</div>
</template>
Router.route('/admin', {
name: 'admin',
template: 'adminDataPage',
layoutTemplate: 'adminPage'
});
var requireLogin = function() {
login_dep.depend();
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render('adminLoggingIn');
console.log('Logging in page');
} else {
this.render('adminLoginPage');
console.log('Login page');
}
} else {
this.next();
console.log('Data page');
}
};
Router.onBeforeAction(requireLogin, {only: 'admin'});
Also, nesting the {{#if currentUser}} inside another element works:
<template name="adminPage">
<div class="test">
{{#if currentUser}}
<div class="container-fluid page-admin admin-content">
<div class="row">
<div class="col-xs-12">
<div class="scrolling-table">
<table class="table table-striped">
<thead>
<tr>
<th>Year</th>
<th>Make</th>
<th>Model</th>
<th>Engine</th>
</tr>
</thead>
<tbody>
<tr>
<td>Year</td>
<td>Make</td>
<td>Model</td>
<td>Engine</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{{else}}
<div class="cover-wrapper-outer page-admin login">
<div class="cover-wrapper-inner">
<div class="cover-container">
<div class="cover">
<form class="admin-login-form">
<div class="alert alert-danger login-failure">
The username or password is incorrect.
</div>
<input class="form-control username" name="username" type="text" placeholder="Username">
<input class="form-control password" name="password" type="password" placeholder="Password">
<input class="form-control submit" name="submit" type="submit" value="Log in">
</form>
</div>
</div>
</div>
</div>
{{/if}}
</div>
</template>
Router.route('/admin', {
name: 'admin',
template: 'adminPage'
});
I'm not sure why Blaze refuses to attach certain elements to the body element, but these are some workarounds.
Edit
A better workaround I found is to add an element to which Blaze can prepend the template DOM. Blaze.render(Template.adminDataPage, document.body, $('.render-fix')[0]) where a hidden element with the class of render-fix is placed in the DOM at the time of the initial render will work. In essence:
<template name="adminPage">
{{#if currentUser}}
<div>User logged in stuff</div>
{{else}}
<div>User not logged in stuff</div>
{{/if}}
<div class="render-fix" style="display: none"></div>
</template>
It appears to me that Blaze will not attach any templates to body after the initial render.

Meteor - How to create a custom field validation for a form

I want to create a custom field validation for forms using Meteor. I don't want a packaged solution.
Here is my code so far.
JavaScript:
var errorState = new ReactiveVar;
Template.lottoForm.created = function() {
errorState.set('errors', {});
};
Template.lottoForm.events({
'blur input': function(e, t) {
e.preventDefault();
validate($(this));
}
});
Template.lottoForm.helpers({
errorClass: function (key) {
return errorState.get('errors')[key] ? 'has-error' : '';
}
});
Template.errorMsg.helpers({
errorMessages: function(key) {
return errorState.get('errors');
}
});
function throwError(message) {
errorState.set('errors', message)
return errorState.get('errors');
}
function validate(formField) {
$("input").each(function(index, element){
var fieldValue = $(element).val();
if(fieldValue){
if(isNaN(fieldValue) == true) {
throwError('Not a valid Number');
}
}
});
}
HTML:
<template name="lottoForm">
<form class="playslip form-inline" role="form">
<fieldset>
<div class="form-group {{errorClass 'ball0'}}">
<input type="text" class="input-block form-control" id="ball0" maxlength="2" name="ball0">
</div>
<div class="form-group {{errorClass 'ball1'}}">
<input type="text" class="input-block form-control" id="ball1" maxlength="2" name="ball1">
</div>
<div class="form-group {{errorClass 'ball2'}}">
<input type="text" class="input-block form-control" id="ball2" maxlength="2" name="ball2">
</div>
<div class="form-group {{errorClass 'ball3'}}">
<input type="text" class="input-block form-control" id="ball3" maxlength="2" name="ball3">
</div>
<div class="form-group {{errorClass 'ball4'}}">
<input type="text" class="input-block form-control" id="ball4" maxlength="2" name="ball4">
</div>
<div class="form-group {{errorClass 'ball5'}}">
<input type="text" class="input-block form-control" id="ball5" maxlength="2" name="ball5">
</div>
{{> errorMsg}}
<div class="play-btn">
<button type="submit" value="submit" class="btn btn-primary">Play Syndicate</button>
</div>
</fieldset>
</form>
</template
<template name="errorMsg">
<div class="errorMsg">
{{#if errorMessages}}
<div class="list-errors">
{{#each errorMessages}}
<div class="list-item">{{> fieldError}}</div>
{{/each}}
</div>
{{/if}}
</div>
</template>
<template name="fieldError">
<div class="alert alert-danger" role="alert">
{{errors}}
</div>
</template>
I'm also getting this error message in the console - "Uncaught Error: {{#each}} currently only accepts arrays, cursors or falsey values."
Why do I get this error and how would I implement the custom validation?
Just as the error says, #each template functions must be passed the proper type. Are you sure your errorMessages helper is returning an array? It looks like you are initializing it as a hash.

How to style Meteor.js loginButtons?

The docs specify to use the template {{> loginButtons}} to implement the default login buttons.
What is the best way to apply my own styles to this?
Rewrite a new template (how would this be done?)
Add styles to my CSS files marked !important
Other?
It turns out a combination of the two ideas can be used. After delving into the accounts-ui package, it turns out that it contains only one .less file. The actual template is included in accounts-ui-unstyled, which is automatically included when accounts-ui is added to a meteor project.
Hence, the CSS can be removed as follows:
meteor remove accounts-ui
meteor add accounts-ui-unstyled
You are then left with the raw HTML, which can be styled as you choose.
Creating your own templates would definitely give you more control.
You create a template by using the "template" tag:
<template name="player">
<div class="player {{selected}}">
<span class="name">{{name}}</span>
<span class="score">{{score}}</span>
<span class="wins"> {{wins}} </span>
<span class="losses"> {{loss}} </span>
</div>
</template>
OR You can check the "classes" or the "id" of the login buttons after they are rendered on a webpage, using "Inspect Element" on Chrome, And the use those classes as CSS selectors to style them accordingly.
For example:
HTML:
//The login button has a class of loginButton
<button class="loginButton"> Login! </button>
CSS:
#Then you can Have a style for the login button as:
.loginButton{
width: 100px;
background-color: cyan;
}
Create your own html template similar to this below. Style it to your taste.
meteor add accounts-password accounts-ui
<template name="login">
<form class="login-form">
<div class="panel panel-default">
<div class="panel-heading">
<h3>Login</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" id="email" placeholder="Email address">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" id="password" placeholder="password">
</div>
</div>
<div class="panel-footer">
<button type="submit" class="btn btn-danger btn-lg">Login</button>
</div>
</div>
</form>
</template>
You can now call Meteor.loginWithPassword in the template event like so:
Template.login.events({
'submit .login-form': function(e) {
e.preventDefault();
var email = e.target.email.value;
var password = e.target.password.value;
Meteor.loginWithPassword(email, password,function(error){
if(error) {
//do something if error occurred or
}else{
FlowRouter.go('/');
}
});
}
});
You can create another form for registration as well.
Just call Accounts.createUser(object, callback);

Resources