Lesson Monday

In the last lesson we used event delegation to target dynamically created li elements. Next, let's create code to show a contact's detail info. The area to display a contact's details is already present in our index.html, it looks like this:

index.html
...
<div id="show-contact">
  <p>First Name: <span class="first-name"></span></p>
  <p>Last Name: <span class="last-name"></span></p>
  <p>Phone Number: <span class="phone-number"></span></p>
  <div id="buttons">
  </div>
</div>
...

Next we'll write a function that can locate the contact that was clicked, and insert the relevant details into this area of the DOM using jQuery. We'll start by modifying our existing attachContactListeners() function:

scripts.js
...

function attachContactListeners() {
  $("ul#contacts").on("click", "li", function() {
    showContact(this.id);     // <--- This is new!
  });
};

...
  • We've replaced our console.log() with a showContact() method. We haven't written this method yet. But this illustrates how we'll make code modular immediately. The function above is only concerned with adding event listeners, hence its name attachContactListeners(). It doesn't and shouldn't care about showing a contact's details. This is why we create a separate showContact() function. It avoids bloating the attachContactListeners() function with unrelated code.

  • In the code snippet above, this refers to the li, which has a specific ID. In other words, we are passing a contact's unique ID into this function. Remember, we must pass the contact into the showContact() function; otherwise, it won't know which contact we are talking about.

Let's write our showContact() function to handle this now. Because it's mentioned in attachContactListeners(), let's define it above attachContactListeners(), and below displayContactDetails():

scripts.js
...

function showContact(contactId) {
  var contact = addressBook.findContact(contactId);
  $("#show-contact").show();
  $(".first-name").html(contact.firstName);
  $(".last-name").html(contact.lastName);
  $(".phone-number").html(contact.phoneNumber);
  var buttons = $("#buttons");
  buttons.empty();
  buttons.append("<button class='deleteButton' id=" +  + contact.id + ">Delete</button>");
}

...
  • We start by utilizing our findContact() method. (Remember that we are cheating a bit and that addressBook is global in scope; that's why we can use it here.)

  • We show the hidden #show-contact content with the contact's full information.

  • Notice we save our selector in a variable and it saves us some overhead; since we need to access buttons twice, it makes sense to just query the DOM once instead of multiple times.

Now we can click on an <li> and see that contact's detail info appear in the DOM!

Delete Functionality

Notice the code above also appends a button to delete the contact being displayed. Let's add this functionality next. We'll modify attachContactListeners() and use event delegation again:

scripts.js
...

function attachContactListeners() {
  $("ul#contacts").on("click", "li", function() {
    showContact(this.id);
  });
  // Code below here is new!
  $("#buttons").on("click", ".deleteButton", function() {
    addressBook.deleteContact(this.id);
    $("#show-contact").hide();
    displayContactDetails(addressBook);
  });
};

...

Now when a user clicks on the delete button, the contact will be deleted. The contact detail will be hidden (since the contact has been deleted) and we'll call displayContactDetails(addressBook) to refresh the list of contacts. We could refactor this code into a separate function as well, but we'll leave that to you.

More Improvements

Great! Our address book is fully functional. Let's add a few more easy user experience improvements before wrapping up.

Bootstrap Styles

First, let's utilize Bootstrap classes to add styling and a more organized layout to our page. This should be review:

index.html
<!DOCTYPE html>
<html>
  <head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet">
    <link href="css/styles.css" rel="stylesheet" type="text/css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="js/scripts.js"></script>
    <title>Address Book</title>
    <title></title>
  </head>
  <body>
    <div class="container">
      <h1>Address Book</h1>
      <div class="row">
        <div class="col-md-6">
          <hr>
          <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 class="form-group">
              <label for="new-phone-number">Phone Number</label>
              <input type="text"  class="form-control" id="new-phone-number">
            </div>
            <button type="submit" class="btn-primary">Add</button>
          </form>
          <hr>
          <h2>Contacts:</h2>
          <ul id="contacts">
          </ul>
        </div>
        <div class="col-md-6">
          <div id="show-contact">
            <p>First Name: <span class="first-name"></span></p>
            <p>Last Name: <span class="last-name"></span></p>
            <p>Phone Number: <span class="phone-number"></span></p>
            <div id="buttons">
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Empty Form Fields

Let's also make sure to empty out our form fields after submission:

scripts.js
...
$(document).ready(function() {
  attachContactListeners();
  $("form#new-contact").submit(function(event) {
    event.preventDefault();
    var inputtedFirstName = $("input#new-first-name").val();
    var inputtedLastName = $("input#new-last-name").val();
    var inputtedPhoneNumber = $("input#new-phone-number").val();

    // The next three lines are new:
    $("input#new-first-name").val("");
    $("input#new-last-name").val("");
    $("input#new-phone-number").val("");

    var newContact = new Contact(inputtedFirstName, inputtedLastName, inputtedPhoneNumber);
    addressBook.addContact(newContact);
    displayContactDetails(addressBook);
  })
})

The entire updated scripts.js file looks like this:

scripts.js
// Business Logic for AddressBook ---------
function AddressBook() {
  this.contacts = [],
  this.currentId = 0
}

AddressBook.prototype.addContact = function(contact) {
  contact.id = this.assignId();
  this.contacts.push(contact);
}

AddressBook.prototype.assignId = function() {
  this.currentId += 1;
  return this.currentId;
}

AddressBook.prototype.findContact = function(id) {
  for (var i=0; i< this.contacts.length; i++) {
    if (this.contacts[i]) {
      if (this.contacts[i].id == id) {
        return this.contacts[i];
      }
    }
  };
  return false;
}

AddressBook.prototype.deleteContact = function(id) {
  for (var i=0; i< this.contacts.length; i++) {
    if (this.contacts[i]) {
      if (this.contacts[i].id == id) {
        delete this.contacts[i];
        return true;
      }
    }
  };
  return false;
}

// Business Logic for Contacts ---------
function Contact(firstName, lastName, phoneNumber) {
  this.firstName = firstName,
  this.lastName = lastName,
  this.phoneNumber = phoneNumber
}

Contact.prototype.fullName = function() {
  return this.firstName + " " + this.lastName;
}

// User Interface Logic ---------
var addressBook = new AddressBook();

function displayContactDetails(addressBookToDisplay) {
  var contactsList = $("ul#contacts");
  var htmlForContactInfo = "";
  addressBookToDisplay.contacts.forEach(function(contact) {
    htmlForContactInfo += "<li id=" + contact.id + ">" + contact.firstName + " " + contact.lastName + "</li>";
  });
  contactsList.html(htmlForContactInfo);
};

function showContact(contactId) {
  var contact = addressBook.findContact(contactId);
  $("#show-contact").show();
  $(".first-name").html(contact.firstName);
  $(".last-name").html(contact.lastName);
  $(".phone-number").html(contact.phoneNumber);
  var buttons = $("#buttons");
  buttons.empty();
  buttons.append("<button class='deleteButton' id=" +  + contact.id + ">Delete</button>");
}

function attachContactListeners() {
  $("ul#contacts").on("click", "li", function() {
    showContact(this.id);
  });
  $("#buttons").on("click", ".deleteButton", function() {
    addressBook.deleteContact(this.id);
    $("#show-contact").hide();
    displayContactDetails(addressBook);
  });
};

$(document).ready(function() {
  attachContactListeners();
  $("form#new-contact").submit(function(event) {
    event.preventDefault();
    var inputtedFirstName = $("input#new-first-name").val();
    var inputtedLastName = $("input#new-last-name").val();
    var inputtedPhoneNumber = $("input#new-phone-number").val();
    $("input#new-first-name").val("");
    $("input#new-last-name").val("");
    $("input#new-phone-number").val("");
    var newContact = new Contact(inputtedFirstName, inputtedLastName, inputtedPhoneNumber);
    addressBook.addContact(newContact);
    displayContactDetails(addressBook);
  })
})

Example GitHub Repo for the Address Book

Examples


Create a UI function to show a contact in the DOM:

function showContact(id) {
  var contact = addressBook.findContact(id);
  $("#show-contact").show();
  $(".first-name").html(contact.firstName);
  $(".last-name").html(contact.lastName);
  $(".address").html(contact.phoneNumber);
  var buttons = $("#buttons");
  buttons.empty();
  buttons.append("<button class='deleteButton' id=" +  + contact.id + ">Delete</button>");
}

Example GitHub Repo for the Address Book