I'm playing around with Meteor and react-meteor. However, I can't seem to grok how the template event-handling works when using react (if it even does).
index.html:
<head>
<title>reactjs</title>
</head>
<body>
<h1>Welcome to Meteor!</h1>
{{> FormTest}}
</body>
lib/components/testform.jsx:
var FormTest = ReactMeteor.createClass({
templateName: "FormTest",
render: function() {
return (
<div>
<button className="my-button">My byutton</button>
</div>
);
}
});
index.js:
if (Meteor.isClient) {
Template.FormTest.events({
"click .my-button": function (event, template) {
alert("My button was clicked!");
}
});
}
I get nothing.
Is there something wrong with my code or my approach? If approach, what would be the proper way to handle events?
If you add an event handler here:
<button className="my-button" onClick={this.handleClick}>My button</button>
You can then do this in your testform.jsx file:
handleClick: function(e) {
if (Meteor.isClient) {
e.preventDefault();
console.log("My button was clicked");
}
}
I'm also testing React with Meteor, but this seems to work.
Related
I am having collection issues in my Meteor project. I added the following code to my Resolutions.js file:
Resolutions = new Mongo.Collection('resolutions');
if (Meteor.isClient) {
Template.body.helpers({
resolutions: function() {
Resolutions.find({});
}
});
Template.body.events({
'submit .new-resolution': function(event) {
var title = event.target.title.value;
Resolutions.insert({
title: title,
createdAt: new Date()
});
event.target.title.value = "";
return false;
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
and added the following to my Resolutions.html file:
<head>
<title>Resolutions</title>
</head>
<body>
<div class="container">
<header>
<h1>Monthly Resolutions</h1>
<form class="new-resolution">
<input type="text" name="title" placeholder="A new resolution" />
<input type="submit" value="Submit"/>
</form>
</header>
<ul>
{{#each resolutions}}
{{> resolution}}
{{/each}}
</ul>
</div>
</body>
<template name="resolution">
<li>{{title}}</li>
</template>
After running the app I get zero errors but still the site does not return any of the collection value. I have no clue what is wrong with this project.
Template.body.helpers({
resolutions: function() {
return Resolutions.find({});
}
});
You forgot to use the return keyword.Remember that when creating a helper, you always need to return a value.
I am trying to write the tests for a Backbone app which uses twig and symphony to render the underscore templates, using mocha, chai and sinon, with blanket for code-coverage and phantomjs for browser simulation. I have managed to load the template markup into test.html by doing the following in the body tag:
<!-- fixtures -->
<script>
$(function(){
$("#fixtures").load("fixtures/comment-fixture.html");
});
</script>
I have created a html fixture, or at least that is what I was aiming for, however I still get the following error when running my tests:
TypeError: 'undefined' is not an object (evaluating 'text.replace')
I understand that it refers to basically underscore evaluating _template(undefined), but I don't understand why it is doing that. Has anyone come across the a similar issue before or know why it is triggering this error?
This is my set-up:
Backbone view I want to test:
var CommentView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
model: null,
// Cache the template function for a single item.
template: _.template($('#comment-template').html()),
// This is the timeout used to re-render the times
refreshTimer: null,
initialize: function(options) {
this.model = options.model;
this.listenTo(this.model, 'change', this.render);
this.listenTo(Backbone, 'stopCommentRefresh', this.stopRefreshTimer);
},
render: function() {
this.$el.html(this.template(this.model.renderAttributes()));
this.refreshTimer = setTimeout(_.bind(this.render, this), 60000);
return this;
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},
stopRefreshTimer: function () {
clearTimeout(this.refreshTimer);
this.remove();
}
});
My fixture file for it :
<script type="text/template" id="comment-template">
<article class="comment">
<h4><%- user.username %></h4>
<p><%- comment %></p>
<time><%- time %></time>
</article>
</script>
My test file for it (so far nothing special):
describe("Comment View", function () {
before(function () {
// create test fixture
this.$fixture = $('<li id="comment-view-fixture"></li>');
});
beforeEach(function () {
// empty out and rebind the fixture for each run
this.$fixture.empty().appendTo($("#fixtures"));
// new default model and view for each test
this.view = new CommentView ({
el: this.$fixture,
model: new Comment({
comment: "Hello world!",
created: new Date(),
modified: new Date(),
user: {
username: "Mario"
}
})
});
});
afterEach(function () {
this.view.model.destroy();
});
after(function () {
$("#fixtures").empty();
});
});
And my test.html file:
<html>
<head>
<title>Backbone.js Tests</title>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link rel="stylesheet" href="js/lib/mocha.css" />
</head>
<body>
<div id="blanket-main" class="hidden" style="display: none;"></div>
<div id="mocha"></div>
<!-- Utility libraries -->
<script src="js/lib/mocha.js"></script>
<script src="js/lib/chai.js"></script >
<script src="js/lib/sinon-chai.js"></script >
<script src="js/lib/sinon.js"></script >
<script src="js/lib/chai-datetime.js"></script>
<!-- JavaScript Coverage Libraries. -->
<script src="js/lib/blanket.js"></script>
<!-- jquery library -->
<script src="../../../../ApiBundle/Resources/public/js/thirdParty/jquery-1.11.js"></script>
<!-- fixtures -->
<script>
$(function(){
$("#fixtures").load("fixtures/comment-fixture.html");
});
</script>
<!-- JavaScript Core Libraries -->
<script src="../../../../ApiBundle/Resources/public/js/thirdParty/underscore.js"></script>
<script src="../../../../ApiBundle/Resources/public/js/thirdParty/jquery.dataTables.js"></script>
<script src="../../../../ApiBundle/Resources/public/js/thirdParty/backbone.js"></script>
<!-- Javascript Application Libraries - Views -->
<script src="../../../../ApiBundle/Resources/public/js/comment.js" data-cover></script>
<script>
var expect = chai.expect;
mocha.setup({
ui: "bdd",
globals: ['stats', 'failures', 'runner'], // Blanket leaks.
bail: false
});
// Set up Mocha with custom Blanket.js reporter.
mocha.reporter(function (_reporter) {
// Updated for Mocha 1.15.1 integration.
// See: https://github.com/alex-seville/blanket/pull/356
var blanketReporter = function (runner) {
// Listeners.
runner.on("start", function () { blanket.setupCoverage(); });
runner.on("suite", function () { blanket.onModuleStart(); });
runner.on("test", function () { blanket.onTestStart(); });
runner.on("test end", function (test) {
blanket.onTestDone(test.parent.tests.length,
test.state === 'passed');
});
runner.on("end", function () {
blanket.onTestsDone();
$("#blanket-main").removeClass("hidden").show("fast");
$("html, body").animate({ scrollTop: 0 });
});
_reporter.call(this, runner);
};
blanketReporter.prototype = _reporter.prototype;
return blanketReporter;
}(mocha._reporter));
blanket.beforeStartTestRunner({
callback: function () {
(window.mochaPhantomJS || mocha).run();
}
});
</script>
<script src="js/spec/views/view_CommentView.spec.js"></script>
<!-- Coverage style helpers -->
<style type="text/css">
#blanket-main {
margin-top: 65px;
margin-right: 20px;
margin-left: 20px;
border-radius: 5px;
border: 1px solid #666;
}
</style>
<!-- Test Fixtures. -->
<div id="fixtures" style="display: none; visibility: hidden;"></div>
</body>
</html>
It turns out its because $("#fixtures").load("fixtures/comment-fixture.html") is asynchronous and isn't actually loaded when I run the tests. By simply copying the contents of fixture.html into test.html body tag, the error stops happening!
I have a very simple function that checks wether user is logged in and else send him to the signup page.
var requireLogin = function() {
if (! Meteor.user()) {
this.render('signUpForm', {
path: '/signup',
layoutTemplate: 'signup'
});
}
// else if () {
// this.render('listProfiles');
// }
else {
this.next();
}
};
this is the layoutTemplate (as simple as can be I believe) :
<template name="signup">
<div class="signup-page">
<div class="container">
{{> yield}}
</div>
</div>
</template>
I can't use the layoutTemplate with .render?
I am going to assume you are using Iron Router.
As far as I know, you can't pass the layout template into the render function.
The easiest way get around this would be to use Router config:
// router.js
Router.configure({
layoutTemplate: 'signup'
});
Then in your js:
if (! Meteor.user()) {
this.render('signUpForm');
}
I successfully implemented a form with quill wysiwyg but now I want to create a component to reuse it. This is my working implementation:
<template name="form">
<form>
<div id="toolbar"> ... html with toolbar buttons </div>
<div id="editor"></div>
<input type="submit" value="save"/>
</form>
</template>
Template.form.rendered = function () {
this.quill = new Quill('#editor', {
modules: {
'toolbar': { container: '#toolbar' },
'link-tooltip': true
},
theme: 'snow' }
);
}
Template.form.events({
'submit form': function(e, tmpl) {
e.preventDefault();
var html = tmpl.quill.getHTML();
// code to save the form data
}
This is what I want to make it reusable. My questions are inside the code:
<template name="form">
<form>
{{> editor }}
<input type="submit" value="save"/>
</form>
</template>
<template name="editor">
<div id="toolbar"> ... html with toolbar buttons </div>
<div id="editor"></div>
</template>
Template.editor.rendered = function () {
this.quill = new Quill('#editor', {
modules: {
'toolbar': { container: '#toolbar' },
'link-tooltip': true
},
theme: 'snow' }
);
// How can I pass quill to the parent template?
}
Template.form.events({
'submit form': function(e, tmpl) {
e.preventDefault();
// How can I access quill variable on the nested template, so I can
// call quill.getHTML()?
}
Here's a pattern I use to solve this sort of problem. Define a class called Editor and a template editor. The intention is that the data context inside editor will be an instance of Editor.
function Editor() {
this.quill = null;
}
Template.editor.rendered = function () {
this.data.quill = new Quill(...);
}
<template name="editor">...</template>
Inside form's created callback, create an Editor and store it on the template instance. Then when you call out to the editor template, pass in the Editor instance as the data context.
Template.form.created = function () {
this.editor = new Editor();
}
Template.form.helpers({
editorInstance: function () {
return Template.instance().editor;
}
});
<template name="form">
<form>
{{> editor editorInstance }}
<input type="submit" value="save"/>
</form>
</template>
You can then define methods on the Editor prototype which can be called by the form:
Editor.prototype.getTextAsHTML = function () {
return this.quill && this.quill.getHTML();
}
Template.form.events({
"submit form": function(e, tmpl) {
e.preventDefault();
var html = tmpl.editor.getTextAsHTML();
// ...
}
}
This is a nice way to abstract the details of the editor so that the form doesn't need to know about it. You can reuse the editor and if you ever want to change it, you just have to make sure getTextAsHTML works the same.
If I have a template like this
<template name="my_form">
<form name="my_form">
<input name=" ....
</form>
</template>
I'd like to listen to the submit event of "my_form".
I tried this:
Template.my_form.events({
'submit form': function( event ){ // also tried just 'submit'
console.log( 'Submitting form!' );
event.preventDefault();
event.stopPropagation();
return false;
}
});
But no luck. It appears the event handler doesn't get registered. Is there something I'm missing?
p.s. I am aware that I can handle a form "submission" by listening to the submit button click event, but I need to use the form submit event in this specific scenario.
Doesn't seem like you are missing something. I was not able to reproduce your problem. When hitting return while the textinput has focus the console prints 'Submitting form!' as expected.
My code, just two files:
form.html:
<head>
<title>form</title>
</head>
<body>
{{> my_form}}
</body>
<template name="my_form">
<form name="my_form">
<input></input>
</form>
</template>
form.js
if (Meteor.isClient) {
Template.my_form.events({
'submit form': function( event ){ // also tried just 'submit', both work for me!
console.log( 'Submitting form!' );
event.preventDefault();
event.stopPropagation();
return false;
}
});
}
You can use general listener for it.
$("my_form").submit(function (e) {
e.preventDefault();
});
You can create a single submit form event, and you conditionally check the event target field. Call appropriate Meteor method based on the collection you are inserting into.
Template.detailsViewTemplate.events({
'submit form': function(ev){
ev.preventDefault();
var detail_input_field = template.$('#detail_entry');
var message_input_field = template.$('#message_entry');
if(detail_input_field != undefined){
var detailFormData = {
detail: $(ev.target).find('[name = detail]').val(),
parentId: $(ev.target).find('[name = parentId]').val(),
checkboxStatus: ''
}
Meteor.call('addDetail', detailFormData);
$('.form-group').children().val('');
} else if(message_input_field != undefined){
var newMessageData = {
listId: this.params_id,
message: $(ev.target).find('[name = message]').val()
}
Meteor.call('addMessage', newMessageData);
$('.form-group').children().val('');
}
}
}