tempusfugit (128) [Avatar] Offline
#1
Re: source code. The chapter folders still contain js/vue.js and js/vue.min.js (v2.2.0) even though index.html references https://unpkg.com/vue instead (currently at v2.4.4)

Page 88(92):
5.1.1 Adding how many are left with v-if
However, for the sake of simplicity, using an expression like this will work.
Now while I get the desire to keep things simple for the sake of discussion, I argue that it is always preferable to use a well-named function instead of an expression, i.e.:
          <span class="inventory-message"
                v-if="hasLowInventory(product)">
            Only {{ projectedInventory(product) }} left!
          </span>
or later
          <span class="inventory-message"
                v-if="hasNoInventory(product)">All Out!
          </span>
          <span class="inventory-message"
                v-else-if="hasLowInventory(product)">
            Only {{ projectedInventory(product) }} left!
          </span>
          <span class="inventory-message"
                v-else>Buy Now!
          </span>
with the Vue instance methods
     projectedInventory(product) {
       return product.availableInventory - this.cartCount(product.id);
     },
     hasLowInventory(product) {
       return this.projectedInventory(product) < 5
     },
     hasNoInventory(product) {
       return this.projectedInventory(product) < 1
     },

  • It keeps your UI logic from getting scattered throughout the markup.
  • It keeps you from duplicating code that represents the same concept (e.g. "projected" or "estimated" inventory for a particular product adjusted by the current cart cointents).
  • It hides details that are likely to change in the future. For example the limit for "low inventory" could easily depend on the individual product or product category (or something else entirely).

  • just use v-if, unless you have a specific reason to use v-show.
    Based on Evan's desciption I go with
  • Use v-if if it is followed by v-else or v-else-if
  • Use v-show if (a) it is more likely to be shown/rendered in most cases OR (b) if it is likely that the visibility of the element will be changed more than once during the lifetime of the page.
  • Otherwise use v-if.

  • With a low inventory indicator it is likely that it won't be needed at all and if it is needed it is unlikely that it will be needed to be toggled more than once (i.e. typically it just suddenly needs to appear) - so v-if is the right choice.

    Page 91(95):
    5.1.2 Adding more messaging with v-else and v-else-if
    Working with conditionals
    If I understand Controlling Reusable Elements with key correctly it might be worthwhile to mention that similar elements between related v-if, v-else-if, and v-else fragments will be reused, so there isn't necessarily the teardown and creation penalty associated with a toggled standalone v-if fragment.

    5.2.1 Adding a star rating with v-for range
    When using v-for ranges, our source data array is an integer.
    This really doesn't work - and the documentation is quite specific. There is no array anywhere. An integer is simply interpreted as the upper (inclusive) limit "n" of a range that in other programming languages would be represented as "1..n" (i.e. it includes the upper limit and does not include 0). So in
              <div class="rating">
                <span v-for="i in 5" >?</span>
              </div>
    

    i will successively iterate through the values 1 through 5.

    Page 94(98):
    Listing 5.6 Adding a method to check if class should be added – Chapter 05/check.js
    Seems a simple comparison should be sufficient:
         isRatingActive(product, n) {
           return product.rating >= n;
         },
    
    and
              <div class="rating">
                <span v-for="i in 5"
                      v-bind:class="{'rating-active': isRatingActive(product, i)}">?
                </span>
              </div>
    
    Regarding parameter order - while in this case it may not matter it is usually a good habit to choose an order that seems most reasonable with bind.
  • const f = isRatingActive.bind(null, someProduct); // for isRating(product,n)
  • const g = isRatingActive.bind(null, 1); // for isRatingActive(n,product)
  • The function g(product) can be used with any product to get "isRatingActive" for the first star.
    The function f(n) can be used for someProduct to get "isRatingActive" for any one of the stars.
    Ultimately f seems like the more useful option so isRating(product,n) seems to have the more useful parameter order.

    Make sure to add the quotes around rating-active.
    "... which isn't a valid JavaScript identifier (while any valid JavaScript string can be a property name)."
    Anybody following the upcoming documentation link is going to be faced with
      <div class="static"
           v-bind:class="{ active: isActive, 'text-danger': hasError }">
      </div>
    
    where "active" isn't quoted while "'text-danger'" is (hypen aka "minus" while commonly used in CSS class names cannot be part of a valid JavaScript identifier).


    Listing 5.8 First two products in products.json file – Chapter 05/products.json
    The listing shows five products.


    Page 96(100):
    5.2.3 Adding more than one product, setting up our products
    $ npm install http-server -g
    OK - up to this point we've been kind of been following a "jQuery-era" style of web development. Shifting gears now - in the middle of this chapter could turn out to be a bit of a speed bump for readers who only have a background in "jQuery-style" ("I have no idea what a JavaScript module is", "just using script and link tags is so much easier") web development (or less). Given that right now we just want to support displaying multiple products it may be a good idea to put off the whole dealing with the npm/Node.js ecosystem to the beginning of the chapter that is going to deal with vue-cli anyway - at which point the whole concept of "modules" and "bundling" has to be tackled anyway.

    It's possible to move forward by using a (bit of a dirty) trick - (ab)using script tags as data containers. e.g.:
    // ...
    
        </div>
    
      </main>
    
    </div>
    <script id="initData" type="application/json">
      {
        "products":[
          {
            "id": 1001,
            "title": "Cat Food, 25lb bag",
            "description": "A 25 pound bag of <em>irresistible</em>, organic goodness for your cat.",
    // ...
            "price": 4999,
            "image": "assets/images/laser-pointer.jpg",
            "availableInventory": 25,
            "rating": 1
          }
        ]
      }
    </script>
    <script type="application/javascript">
     var APP_LOG_LIFECYCLE_EVENTS = true;
     var webstore = new Vue({
    
    // ...
    
    Because the type attribute is neither a JavaScript MIME type (e.g. application/javascript) nor "module" the browser never processes the text between the script tags. The content can later be processed in the created lifecycle hook.
       created: function() {
         const elem = document.getElementById('initData');
         const initData = JSON.parse(elem.textContent);
         this.products = initData.products;
       },
    

    Then there is the matter of (all too convenient, yet) "mindless" global installations. I prefer:
    $ npm init -y
    $ npm i -D http-server

    Adapt package.json to:
      "scripts": {
        "start": "./node_modules/http-server/bin/http-server -p 8000"
      },  
    
    $ npm run start
    And then test http://localhost:8000/products.json in the browser.

    FYI: npm - Shorthands and Other CLI Niceties

    Page 97(101):
    5.2.4 Importing products from product.json
    Per Axios documentation this object has a data property. We then assign this property to this.products.
    "We copy the response.data.products reference to this.products (this refers to the Vue instance.)"
    ... as per Listing 5.10.
    Add the new products property and then replace the product property because we no longer need it.
    "Replace the obsolete product property with the new products property."

    Page 98(102):
    Listing 5.11 Delete product property, add products – Chapter 05/product-delete.js
    The products property should be set to an empty array - not a empty object - because in listing 5.10 the products property in the JSON object is an array.

    Page 99(103):
    5.2.5 Refactoring our app with the v-for directive
    Listing 5.13 Fixing CSS for bootstrap – Chapter 05/bootstrap-fix-v-if.html
    The div in the listing shows a product class which is not present in chapter-05/index.html. Going by chapter-05/assets/css/app.css the product class is only meant to style the product images.

    Listing 5.14 Add the v-for directive for products – Chapter 05/v-for-product.html
  • Should likely be <div v-for="product in products" :key="product.id"> see v-for key: "It is recommended to provide a key with v-for whenever possible".
  • Also needs to point out the addition of the product class to the "product.image" img element.


  • Page 101(105):
    Listing 5.17 Update canAddToCart and add cartCount method – Chapter 05/update-carts.js
    Given that an arrow function was already used on page 97 for the onFulfilled handler on the axios promise, we might as well keep going. In terms of "modern JS" the following definition may be preferable:
         cartCount(id) {
           const countOnId = (count, prodId) => prodId === id ? count + 1 : count;
           return this.cart.reduce(countOnId, 0);
         }
    
    Now your typical JS would just drop in the anonymous function as an inline argument - my personal preference is to actually name things - hopefully to promote clarity (without resorting to comments).
    MDN: Array.prototype.reduce()
    http://kangax.github.io/compat-table/es5/#test-Array.prototype.reduce

    Page 104(108):
    5.3 Sorting Records
    Unfortunately, it’s not an easy task to sort an object in JavaScript, so we’ll use JavaScript to convert the object to an array.
    Listing 5.8 shows a JSON object with a products property that already is an array. In listing 5.10 the reference to that array is copied over to this.products - so products is already referring to an array (of objects).
            let products = this.products.slice();
    
    Essentially copies the original array to prevent it from being modified later by sort which mutates the array in place.

    Listing 5.22 sortedProducts computed property – Chapter 05/sort-products-comp.js
    In the case that the products array is empty sortedProducts doesn't return any value - it should return an empty array.
         sortedProducts() {
           if(this.products.length < 1) return [];
    
           // sort it
           const byTitle = (productA, productB) => {
             const a = productA.title.toUpperCase();
             const b = productB.title.toUpperCase();
             return (a < b ? -1 : (a > b ? 1 : 0));
           };
    
           return this.products.slice().sort(byTitle);
         } // shallow copy original before sorting it
    


    Page 105(109):
    5.4 Summary
    It can be used to loop through components, objects, arrays, methods, and even computed properties. Any type of expression can be used to loop through items.
    "It can be used to iterate over a range of positive integers (i.e. starts at 1), array elements, or object property values and keys to replicate HTML markup, Vue templates or Vue components."
    Erik Hanchett (17) [Avatar] Offline
    #2
    Thanks again for the suggestion! I'll be using this when I go back over chapter 5 smilie

    -Erik