ANGULAR JS – SHAPING UP (LEVEL 3)

1 Forms and Models

http://campus.codeschool.com/courses/shaping-up-with-angularjs/level/3/section/1/video/1

I made it nearly to the end of this video without taking any notes—but now I’ve started over because I want to do right. Even if it’s really difficult to take notes on this section.

Basically…

A “Reviews” section comprises two components: the reviews that are already there, and a means to getting new reviews added.

For reviews already in the system, we’d be looking at something like this:

var gems = [
    {
      name: 'Dodecahedron',
      price: 2,
      description: ' . .dede . ',
      canPurchase: true,
      reviews: [
        {
          rating: "5 stars",
          review: "hooray!",
          email: "me@me.com" 
        }
      ]
    },

where you can see there’s a reviews array within this gem, which we’d then ng-repeat our way through displaying, something like this:

<div>
      <ul>
        <li>Rating: {{ review.rating }}</li>
        <li>Review: {{ review.review }}</li>
        <li>Email: {{ review.email }}</li>        
    </div>

Easy enough. But what about getting new reviews into the system?

<form name="reviewForm">
          <select>
            <option value="1">1 Star</option>
            <option value="2">2 Star</option>
          </select>
          <textarea></textarea>
          <label>by:</label>
          <input type="email" />
          <input type="submit" value="Submit" />
        </form>

Now—part of what we’d like to do here is that AS the user is working through the form, to show what they’re typing, selected, etc.

THAT’S WHERE I STARTED OVER.

1.1 So…

So, add this to the top of the form:

          <blockquote>
            <b>Stars: {{review.stars}}</b>
            {{review.body}}
            <cite>by: {{review.author}}</cite>
            </blockquote>

It’s showing a great screen shot of what we’re trying to build, so I’m going to re-order things and add some Bootstrap classes to make it more clear

<html ng-app="store">

  <body ng-controller="StoreController as store">
    <div class="container">
      <div class="row">
        <div class="col-12">
    <div class="card mt-3 mb-3 p-3">
      <h1>{{product.name}}</h1>
      <h2>{{product.price | currency}}</h2>
      <section>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Specifications</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Reviews</a></li>
  </ul>

  <div class="panel">
    <h4>Description</h4>
    <blockquote>{{product.description}}</blockquote>
  </div>
  <div class="panel">
    <h4>Specifications</h4>
    <blockquote></blockquote>  
  </div>
  <div class="panel">
    <h4>Reviews</h4>
    <div>
      <ul>
        <li>Rating: {{ review.rating }}</li>
        <li>Review: {{ review.review }}</li>
        <li>Email: {{ review.email }}</li>        
    </div>

      <form name="reviewForm">
          <blockquote>
            <b>Stars: {{review.stars}}</b>
            {{review.body}}
            <cite>by: {{review.author}}</cite>
            </blockquote>
        <div class="form-group">  

            1 Star
            2 Star

        </div>
        <div class="form-group">  

        </div>
        <div class="form-group">
        by:

        </div>
          <input type="submit" value="Submit" />
        </form>
  </div>
</section>
      <button ng-show="product.canPurchase"> Add to Cart   </button>
        </div></div></div></div>
  </body>
</html>

1.2 ng-model

Right, so back to this bit of code:

<blockquote>
            <b>Stars: {{review.stars}}</b>
            {{review.body}}
            <cite>by: {{review.author}}</cite>
</blockquote>

We don’t have a review thing already. That’s a placeholder for what’s coming next. The goal for this blockquote is to reflect what is currently being typed in the form. So we need to bind the textarea where we type the review to the review.body bit in the blockquote. This is done using ng-model. ng-model binds the form element value to the property.

Like this:

<select ng-model="review.stars">
    <option value="1">1 Star</option>
    <option value="2">2 Star</option>
</select>
…
<textarea ng-model="review.body" class="form-control"></textarea>
…
<input ng-model="review.author" class="form-control" type="email" />

This is all that’s required—now when you type in the form, it updates in the blockquote. Keep in mind, it doesn’t automatically show the email address as you type it—because since the type is set to email, it’s not displaying anything until it’s a valid email address.

2 Accepting Submissions

It’s good practice to initialize variables. Remember how we had review.author and how the review part of it came from nowhere? So, we should initialize it. And, again, we could use ng-init, but it’s better to initialize a variable in a controller. So we’re going to make another controller.

  app.controller('ReviewController', function(){
    this.review = {};
  });
  ```

It creates an empty object for the review. And then, in the HTML:

```html
      <form name="reviewForm" ng-controller="ReviewController as reviewCtrl">
        <blockquote>
            <b>Stars: {{review.stars}}</b>

People traditionally use Ctrl as part of their alias names for controllers. Now we need to update all the names that had been review.stars to reviewCtrl.review.stars.

2.1 ng-submit

Now we want to make the form do something on submit. ng-submit allows us to do something when the form is submitted. Like this:

      <form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewCtrl.addReview(product)">

Obviously, we need to write the function now. And also I had to change the property names of reviews because there were discrepancies. Here’s the whole code:

<html ng-app="store">

  <body ng-controller="StoreController as store">
    <div class="container">
      <div class="row">
        <div class="col-12">
    <div class="card mt-3 mb-3 p-3">
      <h1>{{product.name}}</h1>
      <h2>{{product.price | currency}}</h2>
      <section>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a class="nav-link" href="#">Description</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Specifications</a></li>
    <li class="nav-item">
      <a class="nav-link" href="#">Reviews</a></li>
  </ul>

  <div class="panel">
    <h4>Description</h4>
    <blockquote>{{product.description}}</blockquote>
  </div>
  <div class="panel">
    <h4>Specifications</h4>
    <blockquote></blockquote>  
  </div>
  <div class="panel">
    <h4>Reviews</h4>
    <div>
      <ul>
        <li>Rating: {{ review.stars }}</li>
        <li>Review: {{ review.body }}</li>
        <li>Email: {{ review.author }}</li>        
    </div>

      <form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewCtrl.addReview(product)">
        <blockquote>
            <b>Stars: {{reviewCtrl.review.stars}}</b>
          <div>{{reviewCtrl.review.body}}</div>
            <cite>by: {{reviewCtrl.review.author}}</cite>
        </blockquote>
        <div class="form-group">  

            1 Star
            2 Star

        </div>
        <div class="form-group">  

        </div>
        <div class="form-group">
        by:

        </div>
          <input type="submit" value="Submit" />
        </form>
  </div>
</section>
      <button ng-show="product.canPurchase"> Add to Cart   </button>
        </div></div></div></div>
  </body>
</html>
(function(){
  var app = angular.module('store', [ ]);

  app.controller('StoreController', function(){
    this.products = gems;
  });

  app.controller('ReviewController', function(){
    this.review = {};

    this.addReview = function(product){
      product.reviews.push(this.review);
    };
  });

  app.controller('PanelController', function(){
    this.tab = 1;

    this.selectTab = function(setTab){
      this.tab = setTab;
    };

    this.isSelected = function(checkTab){
      return this.tab === checkTab;
    };

    this.description = function(tab){
      return gems[tab].description;
    };
  });

  var gems = [
    {
      name: 'Dodecahedron',
      price: 2,
      description: ' . .dede . ',
      canPurchase: true,
      reviews: [
        {
          stars: "5 stars",
          body: "hooray!",
          author: "me@me.com" 
        }
      ]
    },
    {
      name: 'Pentagonal Gem',
      price: 5.95,
      description: " . .pente . ",
      canPurchase: false
    }
  ];
})();

And it works—except that it’s not clearing out the review form. How do we do that? Easy—set the review back to an empty object.

    this.addReview = function(product){
      product.reviews.push(this.review);
      this.review = {};
    };

Yay now it all works. For good measure, because I don’t think I understood this—it works like this: on submit, you pass the ENTIRE PRODUCT AS AN ARGUMENT into the addReview function. Once it’s there, you’re using that as the basis for finding the reviews section of that product, and just pushing the current review onto it, which is known as this.review.

3 Form Validations

I didn’t know we needed to do form validations anymore!

3.1 novalidate

The first thing we need to do is turn off the browser’s validation. That’s by using novalidate in the form tag.

      <form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewCtrl.addReview(product)" novalidate>

3.2 required

Second, we add required to any required fields.

<div class="form-group">  

            1 Star
            2 Star

        </div>
        <div class="form-group">  

        </div>
        <div class="form-group">
        by:

        </div>

3.3 $valid

Here’s some debugging code:

<div>reviewForm is {{reviewForm.$valid}} </div>

And we’d put it right before the submit button. If the form is all valid, it displays true, otherwise it displays false. What makes the form true? Every field is filled out. And also the email address field contains an email address.

How does that work? reviewForm is the form name. And $valid is a built-in property on the form. We don’t have to define it—it just works.

For the time being, the form can be submitted even if it’s invalid. How do we prevent an invalid form from being submitted? Like this:

<form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewForm.$valid && reviewCtrl.addReview(product)" novalidate>

If reviewForm.$valid isn’t true, then it won’t submit!

3.4 input classes

So how can we give a hint to the user about which part of their form is invalid?

Angular adds classes to the inputs to indicate their current status. ng-pristine and ng-dirty indicate whether or not the field has been touched. ng-valid and ng-invalid indicate validity. So we can add some CSS to show us which fields are valid and invalid.

.ng-dirty.ng-invalid {
  border-color: red;
}

.ng-dirty.ng-valid {
  border-color: green;
}

Why include ng-dirty? because we don’t want to add the border color until the user begins typing in the field.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s