2

https://codepen.io/AnonymousCaptain/pen/eYZZOyO

I have made this, but I need my "{{data.body}}" to be interpreted as HTML.

I am a total newbie and I hope someone out there will be able to help me out.

So far I have banged my head against the digital wall looking at the directive called v-html... but I'm not sure if that's what I'm supposed to use.

I have also seen people do something like:

Vue.component("app", { template: HTML

https://www.digitalocean.com/community/tutorials/vuejs-raw-html-binding This might be what I need?

Please point me towards a solution :)

new Vue({
  el: '#magiccardapp',
  data: {
    message: 'Hello',
    tabs: {
      'Title 1': {
        subtitle: 'some text here',
        title: 'Awesome Title',
        body: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab nam alias architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
        img_1: 'https://placedog.net/320/180/?random',
        img_2: 'https://placedog.net/320/180/?random'
      },
      'Title 2': {
        subtitle: 'some cool text here',
        title: 'This is great',
        body: 'Lorem ipsasdfasdfasd alias architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
        img_1: 'https://placedog.net/320/180/',
        img_2: 'https://placedog.net/320/180/'
      },
      'Title 3': {
        subtitle: 'some epic text here',
        title: 'Look I\'m a title!',
        body: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab nam aliasdfasdfaas architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
        img_1: 'https://cdn.mos.cms.futurecdn.net/QjuZKXnkLQgsYsL98uhL9X-320-80.jpg',
        img_2: 'https://aldf.org/wp-content/uploads/2018/06/sad-dog-1846066_1920-320x180.jpg'
      },
      'Title 4': {
        subtitle: 'some other text here',
        title: 'LOREM IPSUM?',
        body: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab nam alias architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
        img_1: 'https://cdn.mos.cms.futurecdn.net/QjuZKXnkLQgsYsL98uhL9X-320-80.jpg',
        img_2: 'https://aldf.org/wp-content/uploads/2018/06/sad-dog-1846066_1920-320x180.jpg'
      },
    },
    activeTab: 'Title 1',
  },
  computed:{
    tabContent() {
      return this.tabs[this.activeTab];
    },
  },
  methods: {
    setTabActive(tab) {
      this.activeTab = tab; 
    }
  },
  components:{
    'TabContent': {
      props: {
        data: Object,
      },
    }
  },
})
/*tabs*/

magiccarddeck {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-gap: 1rem 1rem;
  align-items: center;
  max-width: 1000px;
  padding: 1rem 1rem;
  font-family: "Open Sans",
        sans-serif;
  text-align: center;
}

magiccard:hover {
  transform: scale(1.05);
  border-radius: 0.25rem;
  overflow: hidden;
  transition: 1s;
  -webkit-transition: 1s;
}

.line {
    height: 2px;
    width: 100%;
    margin: auto;
    background-color: #c38e3d;
}

magiccard {
    background: ghostwhite;
    border-radius: 0.25rem;
    overflow: hidden;
    transition: 1s;
    -webkit-transition: 1s;
    cursor: pointer;
    height: 100%;
    color: black;
    border: 0.3rem solid #c38e3d;
    height: max-content;
    filter: brightness(50%);
}

magiccard:hover {
  filter: brightness(100%);
}

magiccard.active {
  cursor: unset;
  transform: scale(1.05);
  filter: brightness(100%);
}

magiccard h3 {
  justify-content: center;
  letter-spacing: 4px;
  margin: 0px;
  color: black;
  font-weight: 400;
  font-size: 20px;
  line-height: 90%;
  padding-left: 0%;
  padding-top: 3%;
  padding-bottom: 3%;
  border-radius: 20px;
}

magiccard p {
  line-height: 1.6;
}




/* BOTTOM */

section {
    font-family: "Open Sans",
        sans-serif;
    font-weight: 800;
    color: ghostwhite;
    font-size: 12px;
}

#workshopgrid {
    padding: 1rem 1rem;
    max-width: 1000px;
    justify-self: center;
}

workshopbox {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    border: 0.3rem solid #c38e3d;
    border-radius: 0.5rem;
    background: ghostwhite;
    padding: 1rem 2rem;
    grid-template-areas:
        'text'
        'img';
}

@media (min-width: 800px) {
    workshopbox {
        grid-template-columns: 3fr 1fr;
        grid-template-areas:
            'text img';
    }

    workshopdescription {
        border-right-style: groove;
        padding-right: 2%;
    }

    workshopimg {
        padding-left: 1rem;
    }
}

@media (max-width: 799px) {
    workshopbox {}

    workshopdescription {
        border-bottom-style: groove;
    }

    workshopimg {
        padding-top: 1rem;
    }
}



.workshoptext {
    grid-area: 1 / 1 / 2 / 2;
    grid-area: text;
    margin: 0px;
    color: black;
    font-weight: 400;
    font-size: 15px;
    line-height: 100%;
    padding-top: 3%;
    padding-bottom: 2%;
}

workshopdescription {
    grid-area: 1 / 1 / 2 / 2;
    grid-area: text;
    margin: 0px;
    color: black;
    font-weight: 400;
    font-size: 15px;
    line-height: 100%;
    padding-top: 3%;
    padding-bottom: 2%;
}

workshopdescription h1 {
    line-height: initial;
}

workshopimg {
    grid-area: 1 / 2 / 2 / 3;
    grid-area: img;
    display: grid;
    grid-gap: 1rem;
    color: black;
    align-content: center;
    /* border-left: 1px solid; */
    justify-content: center;
}

/*transitions*/

.fade-enter-active > *,
.fade-leave-active > * {
  transition-duration: 200ms;
  transition-property: opacity, transform;
  transition-timing-function: cubic-bezier(0.6, 0.15, 0.35, 0.8);
}

.fade-enter > *,
.fade-leave-to > * {
  opacity: 0;
  transform: translateY(40px);
}

.fade-enter-active > *:nth-child(2) {
  transition-delay: 100ms;
}
.fade-enter-active > *:nth-child(3) {
  transition-delay: 200ms;
}

.fade-leave-active > *:nth-child(1) {
  transition-delay: 200ms;
}
.fade-leave-active > *:nth-child(2) {
  transition-delay: 100ms;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="magiccardapp">

  <magiccarddeck>
    <magiccard v-for="(tab, tabName) in tabs" :key="tabName" @click="setTabActive(tabName)" :class="{'active': tabName === activeTab}">

      <h3 class="tab-copy">{{ tabName }}</h3>
      <div class="line"></div>
 {{tab.subtitle}}
    </magiccard>
  </magiccarddeck>
  
  <section id="workshopgrid">
    <workshopbox>
      <workshopdescription>
        <transition name="fade" mode="out-in" appear :duration="500">
          <tab-content v-for="(tabContent, t) in tabs" :data="tabContent" :key="'content'+t" v-if="t === activeTab" inline-template>
            <div class="content">
              <h1>{{data.title}}</h1>
              <p>{{data.body}}</p>
            </div>
          </tab-content>
        </transition>

      </workshopdescription>
      <tab-content v-for="(tabContent, t) in tabs" :data="tabContent" :key="'workshopimg'+t" v-if="t === activeTab" inline-template>
      <workshopimg>
        <img :src="data.img_1">
        <img :src="data.img_2">
      </workshopimg>
        </tab-content>
    </workshopbox>
  </section>

</main>

2 Answers 2

3

You can use v-html this way:

<p v-html="data.body"></p>

It will render html inside the <p> tag.

Sign up to request clarification or add additional context in comments.

2 Comments

As was just commented above, this is apparently insecure? I am looking for a way to just write out a huge load of HTML TAGS within my javascript. I need this for formatting of text (adding classes etc) and especially, I need this for adding bullet lists.
You could use a sanitizer with a list of whitelisted tags or attributes (github.com/apostrophecms/sanitize-html) or render markdown (github.com/miaolz123/vue-markdown) to have support for basic formatting (bold, italic, lists, links, images etc.).
1

If you want to use some text as html in vue, you can use v-html

Although there will be a warning to not use v-html because using this will make your app prone to XSS attacks.

This warning will always be there and is there for a good reason. To prevent XSS attacks you just need to sanitize your html string that you are inserting raw. This sanitation process will strip anything that could lead to script execution from HTML, thus preventing XSS attacks and so you can safely inject complete sets of HTML nodes into your DOM using v-html.

DOMPurify, which has been reviewed by various security experts, is a suitable library for sanitizing your raw html.

4 Comments

I'd rather learn how to do this the proper secure way :) Please do assist me Sensei!
Whether I have "{{data.body}}" or "<p>{{data.body}}</p> in my HTML... I still have the same problem of actually wanting perhaps multiple paragraphs with additional classes, bulletted lists... I hope you understand what I mean.
Edited my answer, hope this helps :)
Both of these answers solved my problem. Looks like zuchmanski needs a boost of points and I hope you're okay with me accepting his answer. I will further research the use of sanitizers :) Too bad I can only accept 1 answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.