Lesson 22 Vue Components More

Mark Harder, 22 March 2019

Vue Components More

Because we are working with .vue files, we use a Module System for registration of our components. You can find details at the vue site https://vuejs.org/v2/guide/components-registration.html

  • We need to register each of the components we want to use in each component that uses them. In our lesson 21 example, we registered AwardCard.vue inside App.vue.
  • Quick note: In lesson 21 we registered AwardCard in our App.vue component and then used it as <award-card> in our template section. This worked because Vue.js automatically registered the hyphenated version of our component selector award-card.
  • If we register our component without specifying a tag name, we get both the hyphenated and original name, like in our lesson 21 example.
      components: { 
          AwardCard
      },
    
    • both of these work
        <AwardCard v-for="award in awards" v-bind:key="award.id" v-bind:award="award"/>  
        <award-card v-for="award in awards" v-bind:key="award.id" v-bind:award="award"/>  
      
  • If we specify our components name, then only the specified component tag name works.
      components: { 
          'award-card', AwardCard
      },
    
    • only the specified name works
        <award-card v-for="award in awards" v-bind:key="award.id" v-bind:award="award"/>  
      

We also learned in lesson 21 that there is a <style> section in each vue component.

  • Any CSS we place in the <style> section will apply to all CSS on the page.
  • If we want to apply our CSS only to the component, we need to add the attribute scoped to the tag like this <style scoped>. Scoped is another way of saying: limit the scope of the CSS only to this component.

We setup props for our components that were of type: String and type: Object.

  • We also set them to be required: true, which is called validation. Validation just means that our code checked to make sure the properties passed into a component are valid according to the rules we have setup.
  • There is another value that you can set instead of required, it is default. We can use default to do the opposite of required. When a prop on a component doesn’t have a prop set, then the prop will instead of causing an error (if required: true is used) it will be filled with the default value. Here is an example for a prop of type String.
          props: {
              title: {
                  type: String,
                  default: 'Active Martial Arts'
              }
          }
    
  • For a prop of type Object we need to return a function, that returns the object, like we did for data in components.
          props: {
              title: {
                  type: Object,
                  default: function() {
                      return {
                          title: 'Active Martial Arts';
                      }
                  }
              }
          }
    

Passing Data from Child Component to Parent

You can send data from a child component to a parent component by means of Vue’s built-in $emit() method. The first parameter of $emit is the event that should be listened for in the parent component. The second (optional) parameter is the data value to pass. Think of emit as an emitter (def: object that puts out something.)

  • Unlike components and props, event names don’t provide any automatic case transformation. Instead, the name of an emitted event must exactly match the name used to listen to that event. For these reasons, we recommend you always use kebab-case for event names.
          this.$emit('child-event');
    
    • In the parent component we need to add an event listener to our template reference to the child tag like this:
        <child-component v-on:child-event="doSomething"></child-component>
      
    • The doSomething would call a local method. We can also pass data as the second argument of $emit
        this.$emit('child-event', this.dataName);  
      
    • And bind a local data item to the returned event in our parent component like this. Note: we are setting a local data object equal to $event which is the returned event object/variable.
        <child-component v-on:child-event="localData = $event"></child-component>
      

Component Data Flow Diagram

Page Layout using Components (Sample App)

In lesson 6 we learned about HTML layout elements

HTML Layout Elements

  • <header> - Defines a header for a document or a section
  • <nav> - Defines a container for navigation links
  • <section> - Defines a section in a document
  • <article> - Defines an independent self-contained article
  • <aside> - Defines content aside from the content (like a sidebar)
  • <footer> - Defines a footer for a document or a section
  • <details> - Defines additional details
  • <summary> - Defines a heading for the <details> element

HTML Layout Using Flexbox/CSS

  • Copy lesson21 into a new folder lesson22, don’t copy the nodes_modules and .git folders.
  • re-initialize git and push our initial code (same as lesson21) to github
  • HTML in our App.vue template section
      <div id="app">
          <header>
              <h2>My Awards</h2>
          <img id="logo" alt="Active Martial Arts Logo" src="./assets/ActiveLogo.png">
          </header>
            
          <section>
          <nav>
              <ul>
              <li><a href="#">My Awards</a></li>
              <li><a href="#">My Next Award</a></li>
              <li><a href="#">Studio Website</a></li>
              </ul>
          </nav>
            
          <article>
              <h1>My Awards</h1>
              <ul>
              <li class="card">
                  <img src="./assets/clouds-fight-jumping-62376.jpg" style="width:100%">
                  <div class="container">
                      <h4><b>High Kick</b></h4> 
                      <p>Kick over your head 10 times in under 10 seconds.</p> 
                  </div>
              </li>
              </ul>
          </article>
          </section>
            
          <footer>
          <p>copyright Active Martial Arts</p>
          </footer>
      </div>
    
  • In our App.vue style section
          * {
          box-sizing: border-box;
          }
    
          body {
          font-family: Arial, Helvetica, sans-serif;
          }
    
          /* Style the header */
          header {
          background-color: #666;
          padding: 30px;
          text-align: center;
          font-size: 35px;
          color: white;
          }
    
          /* Container for flexboxes */
          section {
          display: -webkit-flex;
          display: flex;
          }
    
          /* Style the navigation menu */
          nav {
          -webkit-flex: 1;
          -ms-flex: 1;
          flex: 1;
          background: #ccc;
          padding: 20px;
          }
    
          /* Style the list inside the menu */
          nav ul {
          list-style-type: none;
          padding: 0;
          }
    
          /* Style the content */
          article {
          -webkit-flex: 3;
          -ms-flex: 3;
          flex: 3;
          background-color: #f1f1f1;
          padding: 10px;
          }
    
          /* Style the footer */
          footer {
          background-color: #777;
          padding: 10px;
          text-align: center;
          color: white;
          }
    
          /* Responsive layout - makes the menu and the content (inside the section) sit on top of each other instead of next to each other */
          @media (max-width: 600px) {
          section {
              -webkit-flex-direction: column;
              flex-direction: column;
          }
          }
    
  • What does this layout look like Layout wide
  • Now Shrink the screen width and you will see the navigation section move above article.
    Layout Narrow

Using Vue Components Lets Rebuild our Layout

Let’s now breakdown our page parts into separate components, in separate component files.

  1. In the App.vue component template section of our scaffolding, lets add custom component tags for our sections.
  2. Crete a new AppHeader.vue file in the components directory.
    1. Use scaffold to create the three sections template, script, and style
    2. Inside template copy all the header tag code from App.vue into the template section of AppHeader.vue
    3. Replace the My Awards text with ``
    4. Add a prop for title in the script section. Make it required.
            export default {
                props: {
                    title: {
                        type: String,
                        required: true
                    }
                }
            }
      
    5. The AppHeader.vue component file is one directory deeper then App.vue, so we need to add another period . to the beginning of our src="./assets/ActiveLogo.png" becomes src="../assets/ActiveLogo.png"
    6. Change style tag to add the attribute scoped like this <style scoped>
    7. Move our CSS for the header from App.vue to AppHeader.vue. It has a comment section called /* Style the header */
        <style scoped>
        header {
            background-color: #666;
            padding: 10px;
            text-align: center;
            font-size: 4vw;
            color: white;
            }
      
        #logo {
            max-width: 100%;
        }
        </style>
      
    8. Replace the header tag code section in App.vue with a call to the new component <app-header title="My Awards"></app-header> and use the custom prop title to pass in our title.
    9. In App.vue in the script section under the import AwardCard add a new import for the new header component import AppHeader from "./components/AppHeader.vue";
    10. Also add a reference to component in export default { components: } as AppHeader
           components: { 
               AwardCard,
               AppHeader,
               AppFooter
           },
      
  3. Create another component file named: AppFooter.vue, follow the same procedure as for AppHeader above, except it should look like this.
         <template>
             <footer class="container">
                 <p></p>
             </footer>
         </template>
    
         <script>
         export default {
             props: {
                 title: {
                     type: String,
                     required: true
                 }
             }
         }
         </script>
    
         <style scoped>
             footer {
                 background-color: #777;
                 padding: 10px;
                 text-align: center;
                 color: white;
             }
         </style>
    
  4. Create another component file named: AppNav.vue, follow similar procedures as for AppHeader above, except it should look like this.
         <template>
             <nav>
                 <ul>
                     <li><a href="#">My Awards</a></li>
                     <li><a href="#">My Next Award</a></li>
                     <li><a href="#">Studio Website</a></li>
                 </ul>
             </nav>
         </template>
    
         <script>
         export default {
    
         }
         </script>
    
         <style scoped>
             nav {
                 -webkit-flex: 1;
                 -ms-flex: 1;
                 flex: 1;
                 background: #ccc;
                 padding: 20px;
             }
    
             /* Style the list inside the menu */
             nav ul {
                 list-style-type: none;
                 padding: 0;
             }
         </style>
    
    • Replace the <nav> section in App.vue with <app-nav></app-nav>
    • Next lesson we will learn how to navigate our application using vue router, so for now these navigation items don’t work.
  5. Back in App.vue we need to switch from a single <li> to what we had in lesson 21.
         <ul class="AwardsList">
         <award-card 
             v-for="award in awards" 
             v-bind:key="award.id" 
             v-bind:award="award" />
         </ul>
    

    Here is what our final project should look like when you run npm run serve
    Lesson22 Final Render Narrow



Assignment due for discussion next class and checked into GitHub by the Monday after that

  • Follow along with the lesson and create the lesson22 app and check it into your github account under repo named lesson22.
  • Write up a few paragraphs of explanation of what you want to do for your final project (Project Plan). Then break it down into multiple steps, these are your milestones. Think of them as versions. The first milestone should be be version 0.1, then 0.2 … with 1.0 being the final delivery milestone. Keep your 1.0 goal attainable. You can also define beyond 1.0, for example 1.1 for minor updates and 2.0 for a major upgrade.
  • Submit a draft of your project plan by Slack.