cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <div id="app">
  <h1>Vue.js Dynamic Components with Props, Events, Slots and Keep Alive</h1>
  <p>Each button loads a different component, dynamically.</p>
  <p>In the Home component, you may uncomment the 'keep-alive' component to see how things change with the 'ChangeMessage' component.</p>
  
  <nav class="mainNav">
  </nav>
  <!-- route outlet -->
  <!-- component matched by the route will render here -->
  <section class="mainBody">
    <router-view></router-view>
  </section>
  
</div>
            
          
!
            
              #app {
  max-width: 800px;
  margin: auto;
}

code {
  background: #f2f2f2;
  padding: 0 0.5rem;
}

.mainBody {
  border-top: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
  margin: 1rem 0;
  padding: 1rem 0;
}

nav.mainNav {
  > * {
    padding: 0 0.75rem;
    text-decoration: none;
  }
  
  > *:nth-last-child(n+2) {
    border-right: 1px solid #aaa;
  }
}

.namedSlot,
.slotData {
  margin: 2rem 0;
  background: red;
  color: white;
}

.namedSlot {
  background: blue;
}

.btn {
  background-color: lightgreen;
  border: 1px solid #ddd;
  border-radius: 3px;
  padding: .25rem 2rem;
  
  &:hover,
  &:focus {
    background-color: green;
    color: white;
    cursor: pointer;
  }
}

.wrap {
  display: flex;
  align-items: center;
  padding: 1rem;
}

.left,
.right {
  flex: 1;
}
            
          
!
            
              const NoData = {
  template: `<div>This component ignores the data completely. <p>But there are slots!</p><slot></slot> <slot name="namedSlot"></slot></div>`
  // In this component, I just ignore the props completely
}

const DefaultMessage = {
  template: `<div>This component will show the default msg: <div>{{parentData.msg}}</div>`,
  // this component won't have posts like the Async Component, so we just ignore it
  props: ['parentData']
}

const CustomMessage = {
  template: `<div>This component shows a custom msg: <div>{{parentData.msg}}</div>`,
  // this component won't have posts like the Async Component, so we just ignore it
  props: ['parentData']
}

const Async = {
  template: `
      <div>
        <h2>Posts</h2>
        <p>{{parentData.msg}}</p>
        <section v-if="parentData.posts.length > 0">
          <ul>
            <li class="postInfo" v-for="post in parentData.posts">
              <div class="postInfo__title">
                <strong>Title:</strong> {{post.title}}
              </div>
            </li>
          </ul>
        </section>
      </div>
  `,
  props: ['parentData']
}

/* Children should only affect parent properties via an EVENT (this.$emit) */
const ChangeMessage = {
  template: `
    <div>
      <p>Type here to change the message from the child component via an event.</p>
      <div><input type="text" v-model="message" @input="updateDateParentMessage" /></div>
    </div>
  `,
  data() {
    return {
      // initialize our message with the prop from the parent.
      message: this.parentData.msg ? this.parentData.msg : '' 
    }
  },
  props: ['parentData'],
  /* Need to watch parentData.msg if we want to continue
  to update this.message when the parent updates the msg */
  watch: {
    'parentData.msg': function (msg) {
      this.message = msg  
    }
  },
  methods: {
    updateDateParentMessage() {
      this.$emit('messageChanged', this.message)
    }
  }
}


const Home = {
  template: `
    <section>
      <div class="wrap">
        <div class="right">
          <p><strong>Change the current component's message from the Home (parent) component:</strong></p>
          <div><input type="text" v-model="dataForChild.msg" /></div>
          <p><strong>Important!</strong> We do not change these props from the child components. You must use events for this.</p>
        </div>
      </div>
      <div class="controls">
        <button @click="activateComponent('NoData')">No Data</button>
        <button @click="activateComponent('DefaultMessage')">DefaultMessage</button>
        
        <button @click="activateComponent('CustomMessage', {posts: [], msg: 'This is component two'})">CustomMessage</button>

        <button @click="getPosts">Async First</button>
        <button @click="activateComponent('ChangeMessage', {msg: 'This message will be changed'})">Change Msg from Child</button>
        <button @click="deactivateComponent">Clear</button>
      </div>
      <div class="wrap">
        
        <div class="right">
          <h2>Current Component - {{currentComponent ? currentComponent : 'None'}}</h2>

          <!-- ATTN: Uncomment the keep-alive component to see what happens 
              when you change the message in ChangeMessage component and toggle
              back and forth from another component. -->
          
          <!-- <keep-alive> -->
            <component :is="currentComponent" 
                       :parentData="dataForChild" 
                        v-on:messageChanged="updateMessage">
              <div class="slotData">This is a default slot</div>
              <div slot="namedSlot" class="namedSlot">This is a NAMED slot</div>
              <div slot="namedSlot" class="namedSlot"><p>Here we pass in the message via a slot rather than as a prop:</p>{{dataForChild.msg}}</div>
            </component>
          <!-- </keep-alive> -->
        </div>
      </div>
    </section>
  `,
  data() {
    return {
      currentComponent: false,
      /* You don't NEED to put msg and posts here, but
      I prefer it. It helps me keep track of what info
      my dynamic components need. */
      dataForChild: {
        // All components:
        msg: '', 
        
        // Async Component only
        posts: [] 
      }
    }
  },
  methods: {
    /**
     * Set the current component and the data it requires 
     *
     * @param {string} component The name of the component
     * @param {object} data The data object that will be passed to the child component
     */
    activateComponent(component, data = { posts: [], msg: 'This is a default msg.'}) {
      
      this.dataForChild = data
      
      this.currentComponent = component
    },
    deactivateComponent() {
      this.dataForChild.msg = ''
      this.currentComponent = false
    },
    /* Hold off on loading the component until some async data is retrieved */
    getPosts() {
      axios.get('https://codepen.io/patrickodacre/pen/WOEXOX.js')
        .then( resp => {
        const posts = resp.data.slice(0, 10) // get first 10 posts only.

        // activate the component ONLY when we have our results
        this.activateComponent('Async', {posts, msg: `Here are your posts.`})
      })
    },
    /** 
     * Update the message from the child
     *
     * @listens event:messageChanged
     * @param {string} newMessage The new message from the child component
     */
    updateMessage(newMessage) {
      this.dataForChild.msg = newMessage
    }
  },
  // must wire up your child components here
  components: {
    NoData,
    CustomMessage,
    DefaultMessage,
    Async,
    ChangeMessage
  }
} 


const routes = [
  { path: '/', name: 'home', component: Home}
]

const router = new VueRouter({
  routes
})

const app = new Vue({
  router
}).$mount("#app")

            
          
!
999px
Loading ..................

Console