π 2.1.0.8 Address Book: Objects Within Objects
In a real world application, we'd save our address book's Contact
s in a database. However, we aren't working with databases yet. Instead, we'll create a mock database (a fake database) and store its data inside a global variable.
As we discussed in Variable Scope, we want to avoid global variables wherever possible. So why are we going to use one here?
Well, one of the biggest problems with global variables is that they never fall out of scope β and their values persist throughout an application. Generally, this is a recipe for bugs. However, we want the values in a database to persist and be available all throughout an application. What is the point of a database if we can't retrieve data from it? That's why we're using a global variable here β to better imitate what a database actually does.
Take note that a mock database wouldn't actually be useful in the real world so we wouldn't use a global variable like this in a real world application, either. At this point, you might wonder why we don't just jump into using databases then. Well, they're pretty complicated! For now, we will stay focused on core JavaScript concepts. It will still be a while before we start working with actual databases.
Also, just because we are using a global variable to mock a database doesn't mean you should start adding global variables throughout your code. For the next several sections, here is a guideline: if your variable is meant to represent a potential database, a global variable is fine. Otherwise, avoid them if possible. Most of the projects we do throughout Intermediate JavaScript will not need to mock a database, so think very carefully about whether or not you need to add this functionality as you build your projects.
AddressBook
Constructorβ
Much like our Contact
s, our AddressBook
will be a JavaScript object. But instead of containing properties like firstName
or lastName
, it will contain a list of Contact
objects, similar to how the previous lesson depicted objects being saved within other objects.
To do this we'll need an AddressBook
constructor. Let's add the following new constructor to the top of scripts.js
:
function AddressBook() {
this.contacts = {};
}
function Contact(firstName, lastName, phoneNumber) {
this.firstName = firstName;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
}
Contact.prototype.fullName = function() {
return this.firstName + " " + this.lastName;
};
AddressBook
objects contain a single property: An empty object called contacts
. This is where we'll store entries in our address book. Each entry will be a Contact
object. As we can see, we'll be storing objects within an object β all of the Contact
objects will be stored in the contacts
property, an object within the AddressBook
object.
If we wanted to, we could build out our application to have many instances of AddressBook
s, each with their own Contact
s. We could also include an owner
property that gives information about the owner of the AddressBook
. Or, we could add a lastModified
timestamp that tells us when the AddressBook
was last modified. However, we will keep this simple with just one contacts
property and one instance of AddressBook
.
We'll also add comments showing where AddressBook
and Contact
logic will go in scripts.js
. This will make it easier to follow along with the lessons.
// Business Logic for AddressBook ---------
function AddressBook() {
this.contacts = {};
}
// 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;
};
Adding a Method to the AddressBook
Prototypeβ
AddressBook
s can only do one thing right now: store a list of contacts in key-value pairs. Let's define a few prototypes for our AddressBook
objects to give them more functionality.
Adding Contact
s to the AddressBook
β
We'll create a prototype method to add new Contact
s to an AddressBook
. This will go right below the AddressBook
constructor:
// Business Logic for AddressBook ---------
function AddressBook() {
this.contacts = {};
}
AddressBook.prototype.addContact = function(contact) {
this.contacts[contact.firstName] = contact;
};
// 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;
};
Our new
AddressBook.prototype.addContact()
method takes aContact
object as an argument. We can tell because the parameter is namedcontact
, which indicates that the method expects aContact
object.this.contacts
is the address book property where we're storing all of ourContact
objects.this
represents the instance of the address book, so when we writethis.contacts
, it means we're accessing thecontacts
property of the address book instance.With
this.contacts[contact.firstName] = contact;
, we are creating a new key in the address book'scontacts
property, and assigning it a value:- The key
contact.firstName
will be set to the contact's first name. Here we need to use bracket notation to create the key, becausecontact.firstName
is a variable. - The value we assign to the new key with
= contact;
is theContact
object that we pass into the method. - Generally, a contact in a real database will have a unique ID to locate it. Soon, we'll refactor our code to do this. For now, we're using the
Contact
object'sfirstName
property as an ID.
- The key
That's all it takes for us to add a new Contact
object to our AddressBook
!
Let's try it out. We can copy/paste the contents of scripts.js
into the DevTools console, and enter each of the following five lines:
> let addressBook = new AddressBook();
> let contact = new Contact("Ada", "Lovelace", "503-555-0100");
> let contact2 = new Contact("Grace", "Hopper", "503-555-0199");
> addressBook.addContact(contact);
> addressBook.addContact(contact2);
Let's walk through what each of these lines is doing:
- We create an
AddressBook
object. - We create a new
Contact
object with afirstName
of"Ada"
, saved to the variable namecontact
. - We create another new
Contact
object, this time with afirstName
of"Grace"
, saved to the variable namecontact2
. - We add the first
Contact
object to ourAddressBook
, using our newAddressBook.prototype.addContact()
method. - We add the second
Contact
object to theAddressBook
using the same new method.
Viewing Contact
s in the AddressBook
β
If we then run the following in the console, we can see the contents of our AddressBook
:
> addressBook;
AddressBookΒ {contacts: {β¦}}
We can see that the addressBook
is an object that contains another object called contacts
. To access these contacts, we can do the following:
> addressBook.contacts;
{Ada: Contact, Grace: Contact}
Both of our contacts are there! But how do we access them? Well, each object has a key. The first one has a key of Ada
while the second has a key of Grace
. So we can access them like this:
> addressBook.contacts["Ada"];
ContactΒ {firstName: "Ada", lastName: "Lovelace", phoneNumber: "503-555-0100"}
We can do the same for addressBook.contacts["Grace"]
.
Note that we cannot do the following:
> addressBook.contacts[Ada];
We'll get the following error if we do:
Uncaught ReferenceError: Ada is not defined
This is because JavaScript is reading this as a variable, not a string β and we haven't defined an Ada
variable. Instead, if the key is a string, we need to write it as a string.
If we wanted to get even more specific information about Ada β for instance, her phone number β we can do so like this:
> addressBook.contacts["Ada"].phoneNumber;
"503-555-0100"
We just need to identify the property we want the value of β in this case, it's the phoneNumber
property. Sometimes objects can be very deeply nested. No matter how deeply nested an object or property is, we can keep drilling down further until we retrieve it. We will cover this further in a future lesson.
In the next lesson, we'll add a property to help us assign IDs to each contact.