ANGULAR JS – SHAPING UP (LEVEL 4)

1 Directives

1.1 Current Code

For this next section, I’ve had to remove myself from CodePen because I need multiple HTML files. This means I’ve cleaned up my files and learned that I forgot to close a ul tag. Here’s the current files:

HTML:

<!DOCTYPE html>
<html ng-app="store">
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link href="styles.css" rel="stylesheet">
</head>
<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>
                        </div>
                        <div class="panel">
                            <h4>Reviews</h4>
                            <div>
                                <ul>
                                    <li>Rating: {{ review.stars }}</li>
                                    <li>Review: {{ review.body }}</li>
                                    <li>Email: {{ review.author }}</li>        
                                </ul>
                            </div>

                            <form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewForm.$valid && reviewCtrl.addReview(product)" novalidate>
                                <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>
                                <div>reviewForm is {{reviewForm.$valid}} </div>
                                <input type="submit" value="Submit" />
                            </form>
                        </div>
                    </section>
                    <button ng-show="product.canPurchase"> Add to Cart   </button>
                </div>
            </div>
        </div>
    </div>

    <a href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js</a>
    <a href="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js">https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js</a>
    <a href="http://scripts.js">http://scripts.js</a>

    </body>
</html>

CSS:

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

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

JS:

(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);
            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
        }
    ];
})();

1.2 ng-include

So this bit of code:

<h1>{{product.name}}</h1>
                    <h2>{{product.price | currency}}</h2>

We’re going to switch it to:

<h3>
{{product.name}}
<em class="pull-right">{{product.price | currency}}</em>
</h3>

Which is much prettier. So let’s say that it’s highly likely that we’ll be using this bit of code multiple times on our website, on many pages, I mean. It’d be much easier if we pulled it out as an HTML snippet. So we’ll extract these lines and put them in a new file, product-title.html. Actually, just these lines:

{{product.name}}
<em class="pull-right">{{product.price | currency}}</em>

and we’ll leave the <h3> tags alone. To get them back into the <h3> tags, we’ll use the ng-include directive.

                    <h3 ng-include="'product-title.html'">
                    </h3>

The page looks exactly the same. It just loads differently. Be aware that we’re putting single quotes inside double quotes: “ ‘ product-title.html ‘ “ because ng-include expects a variable, and since instead we’re giving it a string, we have to put it in single quotes.

This works by doing an AJAX request to download product-title.html and place it into the page.

1.3 Custom Directives

So all that stuff we just discussed. Forget about it. Because you should use custom directives instead…I don’t know why we didn’t learn about those instead of filling our heads with this garbage. But anyway. This is what a custom directive would look like.

<product-title></product-title>

Cool I guess. It needs code behind it. So, back in JavaScript…first you have to build the directive like this:

app.directive('productTitle', function(){

    });

See product-title in the HTML becomes productTitle in the JS. Also, it’s worth noting that directives are camelCase while controllers capitalize every word. Building it out:

app.directive('productTitle', function(){
        return {
            restrict: 'E',
            templateUrl: 'product-title.html'
        };
    });

restrict: 'E' is a confusing way of saying we’re making an element. E for Element. The templateUrl is clear. And now the HTML looks like this:

<h3><product-title></product-title></h3>

And that’s all there is to it.

1.4 Attribute vs Element Directives

An attribute directive would look like this:

<h3 product-title></h3>

Element directives should be used for components. Attribute directives should be used for mixin sorts of things like tooltips. A tooltip would be like ‘here’s some extra code to do something here with’ as opposed to an element which would be like ‘here’s this thing, bonk!’

If it was an attribute we’d use restrict: 'A' instead of E.

1.5 The big idea

The point is that you should be able to look at someone’s HTML and understand the page’s functionality rather than only its structure.

2 Practice

2.1 Refactoring the Description Tab

First, refactoring the Description tab. We remove the code from the Description tab and place it in a new file.

<h4>Description</h4>
<blockquote>{{product.description}}</blockquote>

And then we can do an ng-include in the index like this:

          <div>  
          </div>

2.2 Creating an Element Directive

Yeah, screw that! We’re making custom elements.

  app.directive("productDescription", function(){
    return {
      restrict: 'E',
      templateUrl: 'product-description.html'
    };
  });

And the HTML:

          <product-description ng-show="tab.isSet(1)">
          </product-description>

2.3 Creating an Attribute Directive

We did it.

2.4 A custom Directive With a Controller

There’s this gigantic section comprising the bulk of our example:

<section ng-controller="PanelController as panel">
                        <ul class="nav nav-tabs">
                            <li class="nav-item">
                                <a ng-click="panel.selectTab(1)" ng-class="{active: panel.isSelected(1)}"  class="nav-link" href="#">Description</a></li>
                            <li class="nav-item">
                                <a ng-click="panel.selectTab(2)" class="nav-link" ng-class="{active:panel.isSelected(2)}" href="#">Specifications</a></li>
                            <li class="nav-item">
                                <a ng-click="panel.selectTab(3)" ng-class="{active: panel.isSelected(3)}"  class="nav-link" href="#">Reviews</a></li>
                        </ul>

                        <product-description class="panel" ng-show="panel.isSelected(1)">
                        </product-description>

                        <div class="panel">
                        </div>

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

                            <form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewForm.$valid && reviewCtrl.addReview(product)" novalidate>
                                <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>
                                <div>reviewForm is {{reviewForm.$valid}} </div>
                                <input type="submit" value="Submit" />
                            </form>
                        </div>
                    </section>

We’re removing what’s inside the section tags it to its own file, product-panels.html so that now in the index, what was there before becomes…

    <section ng-controller="PanelController as panel">
    </section>

and which we change to a custom directive like this…

    <product-panels ng-controller="PanelController as panel">
    </product-panels>

Now we’ll write the directive.

    app.directive('product-panels', function(){
       return{
           restrict: 'E',
           templateUrl: 'product-panels.html'
       }; 
    });

Now, we’ll need to move the controller into the directive. Why? I don’t know. They don’t explain why it can’t just read it from outside the directive, but okay. It requires use of the controller keyword.

    app.directive('product-panels', function(){
       return{
           restrict: 'E',
           templateUrl: 'product-panels.html',
           controller: function(){
               // stuff
           }
       }; 
    });

And now, to make it so we don’t have to include the ng-controller="PanelController as panel" in the HTML:

    app.directive('product-panels', function(){
       return{
           restrict: 'E',
           templateUrl: 'product-panels.html',
           controller: function(){
               // stuff
           },
           controllerAs: 'panels'
       }; 
    });

So now the HTML only needs to be:

                    <product-panels>
                    </product-panels>

And this is what the final JS looks like:

    app.directive('product-panels', function () {
        return {
            restrict: 'E',
            templateUrl: 'product-panels.html',
            controller: 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;
                };
            },
            controllerAs: 'panels'
        }; 
    });

IT DOESN’T WORK. We’ll step through the challenges to see if we can find what the code should actually look like as we figure out things.

3 Challenges

Turn the Product Tabs section into a directive. Okay. I did it, but while I got it working on their site, I couldn’t get it working locally. So I began going through their files and saw that there’s many differences, from names to functionality. So…here’s all the files, including ones they don’t have but that they walked me through creating.

3.1 styles.css

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

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

3.2 scripts.js

(function () {
    var app = angular.module('gemStore', [ ]);

    app.controller('GalleryController', function(){
        this.current = 0;
        this.setCurrent = function(imageNumber){
            this.current = imageNumber || 0;
        };
    });

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

    app.directive('productTabs',function(){
           return{
               restrict: 'E',
               templateUrl: 'product-tabs.html',
               controller: function() {
                 this.tab = 1;

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

                this.setTab = function(setTab) {
                  this.tab = setTab;
                };
               },
               controllerAs: 'tab'
           }; 
    });

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

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

    app.directive('productDescription', function () {
        return {
            restrict: 'E',
            templateUrl: 'product-description.html'
        }; 
    });

    app.directive('productReviews', function(){
        return {
            restrict: 'E',
            templateUrl: 'product-reviews.html'
        };
    });

    app.directive('productSpecs', function () {
        return {
            restrict: 'A',
            templateUrl: 'product-specs.html'
        } 
    });

    app.directive('productTitle', function () {
        return {
            restrict: 'E',
            templateUrl: 'product-title.html'
        };
    });

    app.directive('product-panels', function () {
        return {
            restrict: 'E',
            templateUrl: 'product-panels.html',
            controller: 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;
                };
            },
            controllerAs: 'panels'
        }; 
    });


    var gems = [
    {
      name: 'Azurite',
      description: "Some gems have hidden qualities beyond their luster, beyond their shine... Azurite is one of those gems.",
      shine: 8,
      price: 110.50,
      rarity: 7,
      color: '#CCC',
      faces: 14,
      images: [
        "images/gem-02.gif",
        "images/gem-05.gif",
        "images/gem-09.gif"
      ],
      reviews: [{
        stars: 5,
        body: "I love this gem!",
        author: "joe@example.org"
      }, {
        stars: 1,
        body: "This gem sucks.",
        author: "tim@example.org"
      }]
    }, {
      name: 'Bloodstone',
      description: "Origin of the Bloodstone is unknown, hence its low value. It has a very high shine and 12 sides, however.",
      shine: 9,
      price: 22.90,
      rarity: 6,
      color: '#EEE',
      faces: 12,
      images: [
        "images/gem-01.gif",
        "images/gem-03.gif",
        "images/gem-04.gif"
      ],
      reviews: [{
        stars: 3,
        body: "I think this gem was just OK, could honestly use more shine, IMO.",
        author: "JimmyDean@example.org"
      }, {
        stars: 4,
        body: "Any gem with 12 faces is for me!",
        author: "gemsRock@example.org"
      }]
      }, {
        name: 'Zircon',
        description: "Zircon is our most coveted and sought after gem. You will pay much to be the proud owner of this gorgeous and high shine gem.",
        shine: 70,
        price: 1100,
        rarity: 2,
        color: '#000',
        faces: 6,
        images: [
          "images/gem-06.gif",
          "images/gem-07.gif",
          "images/gem-08.gif"
        ],
        reviews: [{
          stars: 1,
          body: "This gem is WAY too expensive for its rarity value.",
          author: "turtleguyy@example.org"
        }, {
          stars: 1,
          body: "BBW: High Shine != High Quality.",
          author: "LouisW407@example.org"
        }, {
          stars: 1,
          body: "Don't waste your rubles!",
          author: "nat@example.org"
        }]
    }
  ];
})();

3.3 index.html

<!DOCTYPE html>
<html ng-app="gemStore">
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link href="styles.css" rel="stylesheet">
    <a href="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js">https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js</a>
    <a href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js</a>
    <a href="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js">https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js</a>
    <a href="http://scripts.js">http://scripts.js</a>

</head>
<body ng-controller="StoreController as store">
    <!--  Store Header  -->
    <header>
      <h1 class="text-center">Flatlander Crafted Gems</h1>
      <h2 class="text-center">– an Angular store –</h2>
    </header>

    <!--Products Container-->
    <div class="list-group">
        <!--Product Container-->
        <div class="list-group-item">
            <h3></h3>

            <!-- Image Gallery  -->
            <div>
                <div class="img-wrap">
                    <img />
                </div>
                <ul class="img-thumbnails clearfix">
                    <li class="small-image pull-left thumbnail" ng-repeat="image in product.images">
                        <img ng-src="{{image}}" />
                    </li>
                </ul>
            </div>

            <!--Product Tabs-->
            <product-tabs></product-tabs>
        </div>
    </div>

<!--    <div class="container">-->
<!--        <div class="row">-->
<!--            <div class="col-12">-->
<!--                <div class="card mt-3 mb-3 p-3">-->



                     Add to Cart   
<!--                </div>-->-->
<!--            </div>-->
<!--        </div>-->
<!--    </div>-->

    </body>
</html>

3.4 product-title.html

{{product.name}}
<em class="pull-right">{{product.price | currency}}</em>

3.5 product-description.html

<h4>Description</h4>
<blockquote>{{product.description}}</blockquote>

3.6 product-specs.html

<h4>Specs</h4>
<ul class="list-unstyled">
  <li>
    <strong>Shine</strong>
    : {{product.shine}}</li>
  <li>
    <strong>Faces</strong>
    : {{product.faces}}</li>
  <li>
    <strong>Rarity</strong>
    : {{product.rarity}}</li>
  <li>
    <strong>Color</strong>
    : {{product.color}}</li>
</ul>

3.7 product-panels.html

<product-tabs></product-tabs>

<product-description class="panel" ng-show="panel.isSelected(1)">
</product-description>

<div class="panel">
</div>

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

    <form name="reviewForm" ng-controller="ReviewController as reviewCtrl" ng-submit="reviewForm.$valid && reviewCtrl.addReview(product)" novalidate>
        <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>
        <div>reviewForm is {{reviewForm.$valid}} </div>
        <input type="submit" value="Submit" />
    </form>
</div>

3.8 product-tabs.html

<section>
  <ul class="nav nav-pills">
            <li ng-class="{ active:tab.isSet(1) }">
              <a href ng-click="tab.setTab(1)">Description</a>
            </li>
            <li ng-class="{ active:tab.isSet(2) }">
              <a href ng-click="tab.setTab(2)">Specs</a>
            </li>
            <li ng-class="{ active:tab.isSet(3) }">
              <a href ng-click="tab.setTab(3)">Reviews</a>
            </li>
          </ul>

          <!--  Description Tab's Content  -->
          <div>
          </div>

          <!--  Spec Tab's Content  -->
          <div></div>

          <!--  Review Tab's Content  -->
          <product-reviews ng-show="tab.isSet(3)"></product-reviews> 
  </section>

3.9 product-reviews.html

<!--  Product Reviews List -->
<ul>
  <h4>Reviews</h4>
  <li ng-repeat="review in product.reviews">
    <blockquote>
      <strong>{{review.stars}} Stars</strong>
      {{review.body}}
      <cite class="clearfix">—{{review.author}}</cite>
    </blockquote>
  </li>
</ul>

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

  <!--  Live Preview -->
  <blockquote >
    <strong>{{reviewCtrl.review.stars}} Stars</strong>
    {{reviewCtrl.review.body}}
    <cite class="clearfix">—{{reviewCtrl.review.author}}</cite>
  </blockquote>

  <!--  Review Form -->
  <h4>Submit a Review</h4>
  <fieldset class="form-group">
    <select ng-model="reviewCtrl.review.stars" class="form-control" ng-options="stars for stars in [5,4,3,2,1]" title="Stars">
      <option value>Rate the Product</option>
    </select>
  </fieldset>
  <fieldset class="form-group">
    <textarea ng-model="reviewCtrl.review.body" class="form-control" placeholder="Write a short review of the product..." title="Review"></textarea>
  </fieldset>
  <fieldset class="form-group">
    <input ng-model="reviewCtrl.review.author" type="email" class="form-control" placeholder="jimmyDean@example.org" title="Email" />
  </fieldset>
  <fieldset class="form-group">
    <input type="submit" class="btn btn-primary pull-right" value="Submit Review" />
  </fieldset>
</form>

I had to make some other changes in the challenges…the differences make me unsure of how I’m doing in this course.

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