Lesson Monday

Now that we have a constructor in place to create Address objects, let's add user interface logic to collect address details for each new contact we create. Additionally, we'll also add code to display a contact's addresses when their name is clicked.

Updating HTML

To accomplish this, we'll need the following HTML elements:

  1. Form input fields to collect the data for the properties defined in our Address constructor: Street, city, and state.
  2. An "Another Address" button, so our users may add more than one address to a contact.
  3. A place to display a contact's addresses in our show-contact div on the right side of the screen.

Here are the HTML updates to add these three elements:

address-book.html
<!DOCTYPE html>
<html>
  <head>
    <link href="css/bootstrap.css" rel="stylesheet" type="text/css">
    <link href="css/styles.css" rel="stylesheet" type="text/css">
    <script src="js/jquery-1.12.0.js"></script>
    <script src="js/scripts.js"></script>
    <title>Address book</title>
  </head>
  <body>
    <div class="container">
      <h1>Address book</h1>

      <div class="row">

        <div class="col-md-6">
          <h2>Add a contact:</h2>
          <form id="new-contact">
            <div class="form-group">
              <label for="new-first-name">First name</label>
              <input type="text" class="form-control" id="new-first-name">
            </div>
            <div class="form-group">
              <label for="new-last-name">Last name</label>
              <input type="text" class="form-control" id="new-last-name">
            </div>

            <div id="new-addresses">
              <div class="new-address">
                <div class="form-group">
                  <label for="new-street">Street</label>
                  <input type="text" class="form-control new-street">
                </div>
                <div class="form-group">
                  <label for="new-city">City</label>
                  <input type="text" class="form-control new-city">
                </div>
                <div class="form-group">
                  <label for="new-state">State</label>
                  <input type="text" class="form-control new-state">
                </div>
              </div>
            </div>

            <span class="btn btn-primary" id="add-address">Another address</span>

            <button type="submit" class="btn">Add</button>
          </form>

          <h2>Contacts:</h2>
          <ul id="contacts">

          </ul>
        </div>

        <div class="col-md-6">
          <div id="show-contact">
            <h2></h2>
            <p>First name: <span class="first-name"></span></p>
            <p>Last name: <span class="last-name"></span></p>
            <p>Addresses:</p>
            <ul id="addresses">

            </ul>
          </div>
        </div>

      </div>
    </div>
  </body>
</html>

Example GitHub addressBook Repo with Updated HTML

Now we have fields to collect address data for street, city and state and a place to display them. We have the button to allow a user to add multiple addresses but it does not currently have any jQuery listeners to do anything when the button is clicked...

JQuery Click Listener

Let's incorporate jQuery to show new fields for another address when the user clicks the "Another Address" button we've just added.

When the form first loads, it will display one set of address form fields, like this:

address-book-new-contact-form

Then, when they click the button labelled "Another address", a second set of address form fields will be added, like so:

address-book-new-contact-form-additional-address-fields

To do this, we'll add the following code to the $(document).ready callback function. Note that it should not reside in the form submit listener callback function. This is because the button must be functional before we submit the form; after all, if the user wants to add two different addresses to a Contact, they'll need to be able to hit the "Another Address" button to receive more address form fields before submitting the form to create the new Contact.

js/scripts.js
$(document).ready(function() {
  $("#add-address").click(function() {
    $("#new-addresses").append('<div class="new-address">' +
                                 '<div class="form-group">' +
                                   '<label for="new-street">Street</label>' +
                                   '<input type="text" class="form-control new-street">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-city">City</label>' +
                                   '<input type="text" class="form-control new-city">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-state">State</label>' +
                                   '<input type="text" class="form-control new-state">' +
                                 '</div>' +
                               '</div>');
  });
...

When appending a large amount of HTML with jQuery, we'll break it into smaller strings on different lines, using the + operator to concatenate them, as you see above. This makes it more readable than if it was all on a single line. For further readability, we keep the spacing and indentation the same as our other HTML.

Example GitHub addressBook repo with jQuery to add additional address fields

jQuery Form Submit Listener

Next we'll add jQuery logic to our form submit listener in order to collect and utilize the address information provided by the user. Previously, when the form was submitted, we collected the first and last name and created a Contact object with that data. Now we must also loop through the address form fields to collect that information, create Address objects, and push them onto the Contact object's addresses property.

The code to accomplish this can be seen below. It should reside after the line that creates a new Contact. (However, note that you will not yet see the results of this, because we haven't added code to display the address information yet. We'll do this momentarily!)

js/scripts.js
...
$(".new-address").each(function() {
  var inputtedStreet = $(this).find("input.new-street").val();
  var inputtedCity = $(this).find("input.new-city").val();
  var inputtedState = $(this).find("input.new-state").val();
  var newAddress = new Address(inputtedStreet, inputtedCity, inputtedState);
  newContact.addresses.push(newAddress);
});
...

Here, we've created a loop that cycles through each DOM element with the class new-address. Much like looping through all elements of an array with the forEach() method, as we learned last week, we can look through all elements of a given jQuery class with the each() method. However, instead of taking a parameter that each element is assigned to, we use the this keyword to refer to the current element. (That is, when we use .forEach() we say something like kittens.forEach(function(kitten){ .... We have the opportunity to provide a parameter that each individual element of the array will use as a variable name. "kitten", in this case. When using each() in jQuery we cannot provide a custom parameter in this fashion, and instead must use this, as seen in the code above.)

We also are using the find() method in the code above, which looks through all child elements of the provided element for any other elements that match the criteria provided as an argument. There's a children() method, too, but children() will only traverse down a single level, whereas find() will look through children, their children, and so on. Since our inputs are nested within form-group <div>s, we need to traverse down two levels. Therefore, we use find() instead of children().

GitHub addressBook repo with jQuery to Create Address Objects

Displaying Addresses

Finally, we need to display each of a Contact's addresses alongside their name, like this:

display-addresses-with-contact

We'll do this in a <ul> with an id of addresses. To accomplish this, we'll place the following code inside the .contact click listener:

js/scripts.js
...
$("ul#addresses").text("");
newContact.addresses.forEach(function(address) {
  $("ul#addresses").append("<li>" + address.street + ", " + address.city + " " + address.state + "</li>");
});
...

GitHub addressBook Repo with jQuery to Display Addresses

Phew! That was a lot to take in!

For reference, here is the complete jQuery code that incorporates all changes we've made so far. (It also includes 3 lines at the end to clear the 3 address fields after the form is submitted)

js/scripts.js
...
$(document).ready(function() {

  $("#add-address").click(function() {
    $("#new-addresses").append('<div class="new-address">' +
                                 '<div class="form-group">' +
                                   '<label for="new-street">Street</label>' +
                                   '<input type="text" class="form-control new-street">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-city">City</label>' +
                                   '<input type="text" class="form-control new-city">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-state">State</label>' +
                                   '<input type="text" class="form-control new-state">' +
                                 '</div>' +
                               '</div>');
  });

  $("form#new-contact").submit(function(event) {
    event.preventDefault();

    var inputtedFirstName = $("input#new-first-name").val();
    var inputtedLastName = $("input#new-last-name").val();
    var newContact = new Contact(inputtedFirstName, inputtedLastName);

    $(".new-address").each(function() {
      var inputtedStreet = $(this).find("input.new-street").val();
      var inputtedCity = $(this).find("input.new-city").val();
      var inputtedState = $(this).find("input.new-state").val();
      var newAddress = new Address(inputtedStreet, inputtedCity, inputtedState)
      newContact.addresses.push(newAddress)
    });

    $("ul#contacts").append("<li><span class='contact'>" + newContact.fullName() + "</span></li>");

    $(".contact").last().click(function() {
      $("#show-contact").show();
      $("#show-contact h2").text(newContact.fullName());
      $(".first-name").text(newContact.firstName);
      $(".last-name").text(newContact.lastName);
      $("ul#addresses").text("");
      newContact.addresses.forEach(function(address) {
        $("ul#addresses").append("<li>" + address.street + ", " + address.city + " " + address.state + "</li>");
      });
    });

    $("input#new-first-name").val("");
    $("input#new-last-name").val("");
    $("input.new-street").val("");
    $("input.new-city").val("");
    $("input.new-state").val("");

  });
});


GitHub addressBook Repo with Code to Clear Address Fields

Methods


  • each(): Similar to looping through all elements of an array with the forEach() method, we can look through all elementsof a given jQuery class with the each() method. Instead of taking a parameter that each element is assigned to, we use the this keyword to refer to the current element. (For example kittens.forEach(function(kitten){ .... assigns each element in the array the variable name "kitten". When using each() in jQuery we cannot provide a custom parameter in this fashion.)

Example:

...
$(".new-address").each(function() {
  var inputtedStreet = $(this).find("input.new-street").val();
  var inputtedCity = $(this).find("input.new-city").val();
  var inputtedState = $(this).find("input.new-state").val();
  var newAddress = new Address(inputtedStreet, inputtedCity, inputtedState);
  newContact.addresses.push(newAddress);
});
...

Tips


When appending a large amount of HTML with jQuery, you can break it into smaller strings on different lines, using the + operator to concatenate them, as seen below. This is far more readable than placing all HTML in a single line. We may also keep the spacing and indentation the same as our other HTML:

js/scripts.js
$(document).ready(function() {
  $("#add-address").click(function() {
    $("#new-addresses").append('<div class="new-address">' +
                                 '<div class="form-group">' +
                                   '<label for="new-street">Street</label>' +
                                   '<input type="text" class="form-control new-street">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-city">City</label>' +
                                   '<input type="text" class="form-control new-city">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-state">State</label>' +
                                   '<input type="text" class="form-control new-state">' +
                                 '</div>' +
                               '</div>');
  });
...

Examples


address-book.html
<!DOCTYPE html>
<html>
  <head>
    <link href="css/bootstrap.css" rel="stylesheet" type="text/css">
    <link href="css/styles.css" rel="stylesheet" type="text/css">
    <script src="js/jquery-1.12.0.js"></script>
    <script src="js/scripts.js"></script>
    <title>Address book</title>
  </head>
  <body>
    <div class="container">
      <h1>Address book</h1>

      <div class="row">

        <div class="col-md-6">
          <h2>Add a contact:</h2>
          <form id="new-contact">
            <div class="form-group">
              <label for="new-first-name">First name</label>
              <input type="text" class="form-control" id="new-first-name">
            </div>
            <div class="form-group">
              <label for="new-last-name">Last name</label>
              <input type="text" class="form-control" id="new-last-name">
            </div>

            <div id="new-addresses">
              <div class="new-address">
                <div class="form-group">
                  <label for="new-street">Street</label>
                  <input type="text" class="form-control new-street">
                </div>
                <div class="form-group">
                  <label for="new-city">City</label>
                  <input type="text" class="form-control new-city">
                </div>
                <div class="form-group">
                  <label for="new-state">State</label>
                  <input type="text" class="form-control new-state">
                </div>
              </div>
            </div>

            <span class="btn btn-primary" id="add-address">Another address</span>

            <button type="submit" class="btn">Add</button>
          </form>

          <h2>Contacts:</h2>
          <ul id="contacts">

          </ul>
        </div>

        <div class="col-md-6">
          <div id="show-contact">
            <h2></h2>
            <p>First name: <span class="first-name"></span></p>
            <p>Last name: <span class="last-name"></span></p>
            <p>Addresses:</p>
            <ul id="addresses">

            </ul>
          </div>
        </div>

      </div>
    </div>
  </body>
</html>
js/scripts.js
...
$(document).ready(function() {

  $("#add-address").click(function() {
    $("#new-addresses").append('<div class="new-address">' +
                                 '<div class="form-group">' +
                                   '<label for="new-street">Street</label>' +
                                   '<input type="text" class="form-control new-street">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-city">City</label>' +
                                   '<input type="text" class="form-control new-city">' +
                                 '</div>' +
                                 '<div class="form-group">' +
                                   '<label for="new-state">State</label>' +
                                   '<input type="text" class="form-control new-state">' +
                                 '</div>' +
                               '</div>');
  });

  $("form#new-contact").submit(function(event) {
    event.preventDefault();

    var inputtedFirstName = $("input#new-first-name").val();
    var inputtedLastName = $("input#new-last-name").val();
    var newContact = new Contact(inputtedFirstName, inputtedLastName);

    $(".new-address").each(function() {
      var inputtedStreet = $(this).find("input.new-street").val();
      var inputtedCity = $(this).find("input.new-city").val();
      var inputtedState = $(this).find("input.new-state").val();
      var newAddress = new Address(inputtedStreet, inputtedCity, inputtedState)
      newContact.addresses.push(newAddress)
    });

    $("ul#contacts").append("<li><span class='contact'>" + newContact.fullName() + "</span></li>");

    $(".contact").last().click(function() {
      $("#show-contact").show();
      $("#show-contact h2").text(newContact.fullName());
      $(".first-name").text(newContact.firstName);
      $(".last-name").text(newContact.lastName);
      $("ul#addresses").text("");
      newContact.addresses.forEach(function(address) {
        $("ul#addresses").append("<li>" + address.street + ", " + address.city + " " + address.state + "</li>");
      });
    });

    $("input#new-first-name").val("");
    $("input#new-last-name").val("");
    $("input.new-street").val("");
    $("input.new-city").val("");
    $("input.new-state").val("");

  });
});