Multiple events handlers invoked by single text input event - meteor

I have two separate form input's (both text type), one in template A, and one in template B. Template A invokes template B. All the specific names/properties of these two input form's are unique. I have event handlers for both, within their own properly named Template.name.events().
When I build a very simple test case of this, no problems, everything works fine. But in my larger and more complex actual app, when I enter text into the template B form, the correct template B submit event handler gets invoked. And then...the template A submit event handler gets invoked! This happens even when I do nothing but an event.PreventDefault call in handler B (side question: are event handlers ever invoked for reactive reasons, or strictly "event occurred" reasons?). I am able to work around this odd behavior for the moment by checking in the A event handler for an undefined name property and just exiting if that's the case, but that's just a kludge for something wrong somewhere. Any suggestions as to a likely culrprit for this odd behavior in my code? Thanks!
Here's the code for the two templates in the failing case; the first (entryHall, with the "new-room" form input) is the "A" template, the second (knock, with the "knock-room" form input) the "B" template. Underneath the event handling code for those two templates is the html+handlebars code for the template definitions and invocations. Sorry for the verbosity and lack of a simpler failing case!
Template.entryHall.events({
// NEW ROOM REQUEST PROCESSING
"submit.new-room": function (event) {
event.preventDefault();
if (event.target.roomName === undefined) {
console.log ("in submit new room and roomname in event is undefined");
return;
}
var rName = event.target.roomName.value;
// Is there already a room name of this same name in the Rooms collection?
var roomsCursor = Rooms.findOne({ roomName: rName });
if (roomsCursor != null) {
// It's a dup; don't allow it
event.target.roomName.value = "Duplicate room name, try again";
return(null);
}
var uName = Session.get ('userName');
// It's a unique name, put it in the Rooms collection.
Rooms.insert({
roomName : rName,
owner : uName,
members: [], // an array of user names
knockRequests: [], // an array of user names
chat : null,
files : null
});
// We have the room document added to the Rooms collection, now we have to
// add the room to the owned list for the user
var userEntry = PZUsers.find({ userName : uName }).fetch();
PZUsers.update({ _id : userEntry[0]._id},
{ $push: { ownedRooms: rName }});
ownedRoomCount++;
roomReactor.changed();
event.target.roomName.value = "";
}
});
Template.knock.events({
// Knock on a room request processing
"submit.knock-room": function (event) {
// Prevent default browser form submit
event.preventDefault();
var knockName = event.target.knockName.value;
event.target.knockName.value = "";
console.log("in knock room submit!");
// Can only knock on a room that exists!
var knockRoomCursor = Rooms.findOne({ roomName: knockName });
if (knockRoomCursor == null) {
console.log ("no such room found to knock on");
return;
}
// Add a knock request to this room, and add this room the the user's list of "open knocks" rooms
var roomEntry = Rooms.find({ roomName : knockName }).fetch();
console.log ("_id of room: " + knockName + " is: " + roomEntry[0]._id);
Rooms.update({ _id : roomEntry[0]._id },
{ $push: { knockRequests: Session.get('userName') }});
roomReactor.changed();
}
});
And here's the invoking html:
<template name="entryHall">
<h2>Welcome {{userName}}</h2>
<h3>Create a new room:</h3>
<div class="roomName">
<form class="new-room">
<input type="text" name="roomName" id="roomName" placeholder="Select a room name" />
</form>
</div>
{{markNoOwnedRooms}}
{{#each ownedRooms}}
{{#if firstOwnedRoom}}
<h3>Enter one of your own rooms:</h3>
{{/if}}
{{ > room }}
{{/each}}
{{markNoMemberRooms}}
{{#each memberRooms}}
{{#if firstMemberRoom}}
<h3>Enter one of your member rooms:</h3>
{{/if}}
{{ > room }}
{{/each}}
<h3>Knock to request entry:</h3>
{{ > knock }}
</template>
<template name="room">
<li>{{this}}</li>
</template>
<template name="knock">
<div class="knockName">
<form class="knock-room">
<input type="text" name="knockName" id="knockName" placeholder="Enter room name" />
</form>
</div>
</template>

Related

Meteor Autoform validateForm ignores unique

The method AutoForm.validateForm(formID) returns true although unique is true in SimpleSchema and a duplicate value is entered. Nobody else seems to have this issue so I wonder what I'm doing wrong. This is the full sample code:
common/collections.js
import SimpleSchema from 'simpl-schema';
SimpleSchema.extendOptions(['autoform']);
const afCollection = {};
Meteor.isClient && Template.registerHelper('afCollection', afCollection);
checkTable = afCollection.checkTable = new Meteor.Collection('checkTable');
// Meteor.isServer && checkTable._dropCollection();
checkTable.attachSchema(new SimpleSchema({
checkValue: {
type: String,
index:true,
unique:true,
optional:false
}
}, { tracker: Tracker }));
client/maintenance.js
AutoForm.debug();
Template.Maintenance.events({
'click .save' () {
if (AutoForm.validateForm("newOne")) {
$('form#newOne').submit() }
else {
console.log("should see error message now")
};
console.log("Saved:",checkTable.find().fetch())
}
});
client/maintenance.html
<template name="Maintenance">
<a class='save' href=#>Save</a>
{{#autoForm id='newOne' type="insert" collection=afCollection.checkTable autosave=false }}
{{> afQuickField name="checkValue"}}
{{/autoForm}}
</template>
packages:
aldeed:autoform#6.2.0
aldeed:collection2-core#2.0.4
aldeed:schema-index#2.1.1
validateForm works correctly in case of input is empty. In case of unique is violated, validateForm returns true. When you call .submit(), the error message in the template is displayed correctly and you could react on the error using an AutoForm.hook (probably, not tested).
Unfortunately this does not help in my situation, because clicking on "save" will submit several forms at once. I must ensure that all forms are error-free before the first one is submitted.
What am I missing?

Meteor Autoform - Prevent Field from Update

In my autoform the value of a field is the difference of two other input fields. It is not allowed to be updated by the user. Unfortuantly at the moment it is not possible to set a single field to readonly in a form. So my approach is to create an autoValue and a custom Validation to prevent an update
My code so far:
'SiteA.Settings.RXSignalODU1difference': {
type: Number,
label: "RX Signal [dBm] ODU1 difference (without ATPC +/- 3dbm)",
decimal: true,
autoform: {
type: "number"
},
autoValue: function() {
var ODU1gemessen = this.field("SiteA.Settings.RXSignalODU1");
var ODU1planned = this.field("SiteA.Settings.RXSignalODU1planned");
if (ODU1gemessen.isSet || ODU1planned.isSet) {
return ODU1gemessen.value - ODU1planned.value;
}
},
custom: function() {
var ODU1gemessen = this.field("SiteA.Settings.RXSignalODU1");
var ODU1planned = this.field("SiteA.Settings.RXSignalODU1planned");
var dif = ODU1gemessen.value - ODU1planned.value;
if (this.value !== dif) {
return "noUpdateAllowed";
}
}
},
My Simple.Schema message:
SimpleSchema.messages({noUpdateAllowed: "Can't be updated"});
Unfortunatly no message pops up.
EDIT
This method will create a disabled input box within your form that will automatically show the difference between two other input fields as the user types.
First, we define session variables for the values used in the calculation and initialize them to undefined.
Template.xyz.onRendered({
Session.set("ODU1gemessen", undefined);
Session.set("ODU1planned", undefined);
});
Then we define two events, that will automatically update these session variables as the user types.
Template.xyz.events({
'keyup #RXSignalODU1' : function (event) {
var value = $(event.target).val();
Session.set("ODU1gemessen", value);
},
'keyup #RXSignalODU1planned' : function (event) {
var value = $(event.target).val();
Session.set("ODU1planned", value);
}
});
Then we define a helper to calculate the difference.
Template.xyz.helpers({
RXSignalODU1difference : function () {
var ODU1gemessen = Session.get("ODU1gemessen");
var ODU1planned = Session.get("ODU1planned");
if (!!ODU1gemessen || !!ODU1planned) {
return ODU1gemessen - ODU1planned;
}
}
});
My HTML markup looks like this. Note, to still control the order of the form, I use a {{#autoform}} with a series of {{> afQuickfields }} rather than using {{> quickForm}}.
To display the calculated difference, I just create a custom div with a disabled text box.
<template name="xyz">
{{#autoForm collection="yourCollection" id="yourId" type="insert"}}
<fieldset>
<legend>Enter legend text</legend>
{{> afQuickField name="SiteA.Settings.RXSignalODU1" id="RXSignalODU1"}}
{{> afQuickField name="SiteA.Settings.RXSignalODU1planned" id="RXSignalODU1planned"}}
<div class="form-group">
<label class="control-label">RXSignalODU1difference</label>
<input class="form-control" type="text" name="RXSignalODU1difference" disabled value="{{RXSignalODU1difference}}">
<span class="help-block"></span>
</div>
</fieldset>
<button class="btn btn-primary" type="submit">Insert</button>
{{/autoForm}}
</template>
Original Answer - not recommended
If you are generating your form as a quickForm, you could do something like
{{>quickForm collection='yourCollection' omitFields='SiteA.Settings.RXSignalODU1difference'}}
This will leave this field off the form, so the user won't be able to update it.
If you still want to display the value somewhere along with the form as the user types in the other two values, you could define a helper in your client side js
something like
Template.yourFormPage.helpers({
diff: function () {
var ODU1gemessen = $('[name=SiteA.Settings.RXSignalODU1]').val();
var ODU1planned = $('[name=SiteA.Settings.RXSignalODU1planned]').val();
if (!!ODU1gemessen || !!ODU1planned) {
return ODU1gemessen - ODU1planned;
}
}
});
You'll want to double check how the field names are being rendered in your DOM. Autoform assigns the name attribute using the field names in your schema, but I don't know how it handles nested keys... (i.e. whether it names the element 'SiteA.Settings.RXSignalODU1' or just 'RXSignalODU1' )
And then just display the value somewhere in your html as :
{{diff}}

Meteor.js autosubscribe after callback from server

I am trying to use meteor autosubscribe function on the client but sometimes it works and sometimes it doesn't. So here is the case:
Working version: I have dropdown which is populated with channels. When user clicks on the channel I set session variable and start loading threads:
Template.channelDropdown.events({
"click #channelLink": function() {
Session.set("currentChannel", this);
}
});
html
<ul class="dropdown-menu">
{{#each channels}}
<li>
<a id="channelLink" href="#">{{name}}</a>
</li>
{{/each}}
</ul>
and
Tracker.autorun(function() {
Meteor.subscribe("threadsByChannel", Session.get("currentChannel"));
});
Meteor.publish("threadsByChannel", function (channel) {
return threads.find({channel: channel});
});
and loading threads:
"channelThreads": function() {
return threads.find({channel: Session.get("currentChannel")}).fetch();
},
Now this works. However I have other method to open channel which doesn't work. It is possible to enter channel name and if it doesn't exist it is created, otherwise existing one is returned.
Template.channelSearchBar.events({
"submit #joinChannelForm": function() {
event.preventDefault();
var channelName = $("#channelNameField").val();
Meteor.call("getChannelByName", channelName, function(error, result) {
if (error) {
// TODO error handling
} else {
Session.set("currentChannel", result);
}
});
$("#channelNameField").val("");
}
});
server:
'getChannelByName': function (channelName) {
var channel = channels.findOne({name: channelName});
if (channel) {
return channel;
} else {
var newChannel = {
name: channelName
}
return channels.insert(newChannel);
}
}
html
<template name="channelSearchBar">
<form id="joinChannelForm" class="navbar-form navbar-left" role="search">
<div class="form-group">
<input id="channelNameField" type="text" class="form-control" placeholder="Enter channel name">
</div>
<button type="submit" class="btn btn-default">Join</button>
</form>
</template>
Now the only difference is that session variable is set in callback. I'm pretty sure this is the problem as it is asynchronious call to the server and somehow threads are not populated in client when requested. When I set breakpoint in loading threads function (threads.find() on client), I see that session variable is correctly set, but it just does not return anything. Also sometimes it is called two times (for example in working first case first call returns nothing and then second call returns real results for some reason. Is this is how it suppose to work?). I am just beginning to learn meteor and trying to understand how it all works. Would be glad if someone could explain or direct me to the right way.
EDIT: Its very strange. I have put breakpoint in publish function and it seems it works fine - exactly like it should. However on the not working case it simply returns nothing right from the server side even though both working and not working situations provides (seemingly) exactly the same channel object. It seems that the problem is related with mongodb query.
Why don't remove the Meteor.call, and do everything on the client side?, the subscription on the Autorun seems to be fine, lets try with this code, just make sure you have the allow/deny permissions in order.
Template.channelSearchBar.events({
"submit #joinChannelForm": function() {
event.preventDefault();
var channel = channels.findOne({name: channelName}),
channelName = $("#channelNameField").val();
if (channel) {
return channel;
} else {
var newChannel = {
name: channelName
}
var chanelCreated = channels.insert(newChannel);
Session.set("currentChannel", chanelCreated);
$("#channelNameField").val("");
}
}
});
OK it seems the real problem was not that of meteor publish/subscribe mistake but because of mongodb query which was not recognizing channel object. Problem was solved by changing this:
threads.find({channel: channel})
to this:
threads.find({"channel.name": channel.name})
I have found that mongo queries cares about order of object parameters, but channel had only one parameter (name) at the moment, so I'm still not sure why they were not considered equal. One channel was returned from findOne query and another from find. One from find was recognized.

Best way to prevent a template helper to be rerun when it is unnecessary?

I'm trying to prevent a template helper to be rerun when it is unnecessary. I made a simple application to illustrate this behavior:
Let's say I want to display some items that contain only a title and a description.
<template name="Tests">
{{#each items}}
{{> TestsItems}}
{{/each}}
</template>
<template name="TestsItems">
<div class="title">{{title}}</div>
<div class="description">{{description}}</div>
</template>
I have autopublish enabled.
Template.Tests.helpers({
items: function () {
return Items.find();
}
});
Template.TestsItems.helpers({
description: function () {
// I'm using this helper to do some updates
// on a jQuery plugin when the description field change.
// see example 1: https://github.com/avital/meteor-ui-new-rendered-callback/
console.log("The description is run");
return this.description;
}
});
When a new update is made on the title field only, you can see that the description helper is rerun. What I'm trying to achieve is to only rerun this helper when there is a new value for the description field and not every time a field has changed in the document.
As {{#constant}} and {{#isolate}} are deprecated, how can I get this behavior in the latest Meteor versions?
Note 1: Create a new subtemplate including the description does not fix the problem.
I would avoid side effects in template helpers. Instead I would use an autorun:
Template.TestItems.rendered = function () {
var _id = this.data._id;
this.autorun(function () {
// Select only the description field, so that we only
// trigger a re-run if the description field changes
var description = Items.findOne(_id, {fields: {description: 1}}).description;
// update the JQuery plugin
});
}

in Meteor, how do i update a property on only one instance of a template?

If I have an {{# each}} binding in Meteor, and I want to update a property on only one instance of the template inside the #each. How would I do that? I've tried setting a value on the "template" object inside the events map, but that doesn't seem to be reactive. I've also tried binding to a Session property, but that will cause every instance to update instead of just the one I want...
for example:
{{#each dates}}
{{> dateTemplate}}
{{/each}}
<template name="dateTemplate">
{{date}}
<span style="color: red;">{{errorMsg}}</span> <--- how do i update errorMsg?
</template>
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = 'not valid'; <--- this doesn't do anything
}
});
EDIT TO ADDRESS ANSWER BELOW:
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid';} <--- this also doesn't do anything
}
});
You don't have to use handlebars for this, because its not something that needs reactivity to pass the message through, reactive variables work best with db data, or data that would be updated by another client over the air.
You could use JQuery (included by default) to update it, it can also get a bit fancier:
<template name="dateTemplate">
{{date}}
<span style="color: red;display: none" class="errorMessage"></span>
</template>
Template.dateTemplate.events({
'click': function(event, template) {
$(template.find('.errorMessage')).html('Your Error Message').slideDown();
}
});
Ive edited it so the error is hidden by default, and slides down with an animation
I'm experimenting handling this by passing a different reactive object to each instance of the template. Then the template can bind to the reactive object (which is unique per instance) and we don't have any extra boilerplate.
It ends up looking like this:
Initial render:
Template.firstTemplateWithPoll(ContextProvider.getContext())
Template.secondTemplateWithPoll(ContextProvider.getContext())
// (I actually pass getContext an identifier so I always get the same context for the same template)
JS:
Template.poll.events = {
'click .yes' : function() {
this.reactive.set('selection', 'yes');
},
'click .no' : function() {
this.reactive.set('selection', 'no');
}
};
Template.poll.selection = function(arg) {
return this.reactive.get('selection');
}
Template:
<template name="poll">
<blockquote>
<p>
Your selection on this poll is {{selection}}
</p>
</blockquote>
<button class='yes'>YES</button>
<button class='no'>NO</button>
</template>
template.errorMsg should be a function that returns your error.
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid'; };
}
});

Resources