Chapter 9: Forms

Forms

We’ve finished building the original Leaderboard application, but there’s still plenty of new features we can add to the project.

In this chapter, we’re going to create a form that allows users to add players to the leaderboard and a button that allows users to remove players.

To begin, create a “addPlayerForm” template inside the HTML file:

<template name="addPlayerForm">
</template>

Then include this template at the top of the “leaderboard” template:

{{> addPlayerForm}}

Within the “addPlayerForm” template, create two elements:

  1. A text field with the name attribute defined as “playerName”.
  2. A submit button with the value attribute defined as “Add Player”.

The template should then resemble:

<template name="addPlayerForm">
    <form>
        <input type="text" name="playerName">
        <input type="submit" value="Add Player">
    </form>
</template>

The resulting interface won’t look particularly glamorous, but it’s good enough for the time being.

Submit Events

At this point, we want to create an event that triggers the execution of code whenever a user submits the form we just created. This process is very similar to what we’ve already covered.

To begin, create an events block for the “addPlayerForm” template (and, as always, create this inside the isClient conditional):

Template.addPlayerForm.events({
    // events go here
});

The reason we need a new events block is because this event will be attached to the new “addPlayerForm” template, rather than the “leaderboard” template.

Within the events block, create an event type of “submit” and an event selector of “form”:

Template.addPlayerForm.events({
    'submit form': function(){
        // code goes here
    }
});

Based on this code, the event’s function will trigger whenever the form inside the “addPlayerForm” template is submitted.

You might be wondering though:

Why don’t we use the “click” event type for the form?

Won’t most users click the “submit” button anyway?

And that might be the case, but it’s important to remember that forms can be submitted in a number of ways. You can submit them by clicking a button, but you can also submit them by tapping the “Return” key on your keyboard. So by using the “submit” event, we’re able to trigger the execution of code no matter how the user submits the form. As a result, the event becomes much more versatile than if we were to simply use the “click” event type.

To confirm the event is working as expected, place a console.log statement inside the event’s function:

'submit form': function(){
    console.log("Form submitted");
}

But, as it turns out, there’s a problem.

Because, when we submit the form, the browser refreshes the page. You might see a quick flash of the “Form submitted” message in the Console, but we’ve nevertheless encountered an error that needs to be accounted for.

This error happens because, by default, the web browser assumes that the data in the form should be sent somewhere – to another page, for instance. But Meteor is a framework for building single-page applications, which means we don’t want to send the data anywhere. We want the data to remain on the current page.

To account for this, we need to disable the default behavior that web browsers attach to forms.

This is something we’ll start to fix in the next section.

The Event Object, Part 1

When an event is triggered from within a Meteor application, we’re able to access information about that event as it occurs. This might sound weird, but to show you what I mean, change the “submit form” event to the following:

'submit form': function(event){
    console.log("Form submitted");
    console.log(event.type);
}

Here, we’re passing this “event” keyword into the parentheses of the event’s function, and then outputting the value of event.type to the Console.

As a result, two things are happening:

First, whatever keyword is passed into the parentheses as the first argument of the event’s function becomes a reference for that event. So because we’ve passed through the “event” keyword, that’s now our reference. You can, however, use whatever keyword you prefer. (A common convention, for instance, is to use “evt” or “e” instead of “event”.)

Second, the event.type part is a reference to the “type” property of this particular event. So because this is a “submit” event, the console.log statement should output the word “submit” to the Console.

But of course, we still haven’t solved the original problem, because if we submit the form, the page continues to refresh.

To fix this problem with what we’ve just learned though, reference the event from inside the event’s function, and use a preventDefault function:

'submit form': function(event){
    event.preventDefault();
    console.log("Form submitted");
    console.log(event.type);
}

When attached to the event object, this preventDefault function prevents the default behavior of the event from occurring. So because we’ve attached the function to the “submit form” event:

  1. By default, submitting the form won’t do anything.
  2. We’ll need to manually define the form’s functionality.
  3. The console.log statements will work as expected.

Basically, we’ll now have complete control over the form. (And if you’re curious if you’ll need to use this preventDefault function for every form, the answer is yes.)

Save the file, switch back to Chrome, and submit the form. You’ll see that the page no longer refreshes and the appropriate output appears in the Console.

The Event Object, Part 2

Now that we have complete control over the form, we need to grab the contents of the “playerName” text field from inside the “submit form” event. Then we can use that value when adding a new player to the “PlayersList” collection.

To begin, create a variable named “playerNameVar”:

'submit form': function(event){
    event.preventDefault();
    var playerNameVar;
}

Then make this variable equal to “event.target.playerName”:

'submit form': function(event){
    event.preventDefault();
    var playerNameVar = event.target.playerName;
}

…and output this variable to the Console:

'submit form': function(event){
    event.preventDefault();
    var playerNameVar = event.target.playerName;
    console.log(playerNameVar);
}

Here, this “event.target.playerName” statement references the event to grab whatever HTML element has its name attribute defined as “playerName”.

But if we submit the form, we’ll see that the console.log statement outputs the raw HTML of the “playerName” text field, rather than the value that’s held within that field.

This is because we need to explicitly retrieve the value property of the form field:

'submit form': function(event){
    event.preventDefault();
    var playerNameVar = event.target.playerName.value;
    console.log(playerNameVar);
}

Based on this change, whatever the user types into the “playerName” text field will appear in the Console whenever they submit the form.

To then use this value to add a new player to the “PlayersList” collection, add an insert function inside the “submit form” event:

PlayersList.insert({
    name: playerNameVar,
    score: 0
});

But as we can see here, instead of passing through a static value to the name field, like “David” or “Bob”, we’re passing through a reference to the “playerNameVar” variable.

The code for this event should now resemble:

'submit form': function(event){
    event.preventDefault();
    var playerNameVar = event.target.playerName.value;
    PlayersList.insert({
        name: playerNameVar,
        score: 0
    });
}

The form should also work as expected.

But each time the user submits the form, we can see that the name of the player remains in the text field. To empty this field when the form is submitted, add the following line to the bottom of the event:

event.target.playerName.value = "";

Here, we’re simply setting the value property of the “playerName” field to an empty string. Because of this, the text field will return to an empty value each time the form is submitted.

Removing Players

Since we’ve made it possible to add players to the leaderboard, we should also make it possible to remove players from the leaderboard.

To achieve this, first create a “Remove Player” button inside the “leaderboard” template (but outside the each block):

<button class="remove">Remove Player</button>

This button should have a class of “remove”.

Then create a click event for the “leaderboard” template that’s attached to the “remove” class:

'click .remove': function(){
    // code goes here
}

Within this event, retrieve the unique ID of the currently selected player, and store this value in a “selectedPlayer” variable:

'click .remove': function(){
    var selectedPlayer = Session.get('selectedPlayer');
}

Then, on the next line, use a remove function:

'click .remove': function(){
    var selectedPlayer = Session.get('selectedPlayer');
    PlayersList.remove({ _id: selectedPlayer });
}

We haven’t talked about the remove function before, but there’s nothing much to talk about. By passing the ID of a document into this remove function, we can remove that document from the collection.

Because of this, users can now remove players from the list by first selecting them and then clicking the “Remove Player” button.

Summary

In this chapter, we’ve learned that:

  • By using the submit event type, we can trigger the execution of code when a form is submitted.
  • When working with a form, the submit is more appropriate than the click event, since forms can be submitted in ways other than clicking a button.
  • Browsers attach default behavior to forms, which interferes with our code. This behavior can be disabled with the preventDefault function.
  • When form fields have a name attribute, that name can be used to then grab the value of that form field from within our application logic.
  • By passing the ID of a Mongo document into a remove function, we can remove that document from a collection.

To gain a deeper understanding of Meteor:

  • Create an alert that asks users whether or not they really want to remove a player from the list after they click the “Remove Player” button.
  • Add a “Score” field to the “Add Player” form, and allow users to define a score for a player when they’re being added to the list.

To browse the project’s code in its current state, click here.