vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
208.01k stars 33.69k forks source link

Reintroduce Triple Curly Braces {{{ }}} as a v-html alternative #7431

Closed pxwee5 closed 6 years ago

pxwee5 commented 6 years ago

What problem does this feature solve?

At the moment, whenever we use v-html, we are introducing another wrapping element on top of the element that we want to include in the template.

For eg. <div v-html="paragraph"></div> will generate the following code:

<div>
  <p>This is a cool paragraph</p>
</div>

However if we Inline these paragraphs as such:

<div v-html="paragraph1"></div>
<div v-html="paragraph2"></div>
<div v-html="paragraph3"></div>

<!--will generate the following: -->

<div>
  <p>This is the first paragraph</p>
</div>
<div>
  <p>This is the second paragraph</p>
</div>
<div>
  <p>This is the third paragraph</p>
</div>

This has introduced too many unnecessary wrapper elements surrounding our <p> element. And we lose the ability to style the paragraphs (in different colours and whatnot) with css nth-child

div:nth-child(1) { color: red }
div:nth-child(2) { color: blue }
div:nth-child(3) { color: green }

Not only this, the paragraph margin that we set via p + p { margin-top: 10px } will fail as well because they stopped having relationship as soon as the divs are introduced. PS: The reason I used p + p is so that margin is applied in between the set of paragraphs only and not before or after.

What does the proposed API look like?

Ideally the usage would be like this:

<div>
  {{{ paragraph1 }}}
  {{{ paragraph2 }}}
  {{{ paragraph3 }}}
</div>

and should generate the following:

<div>
  <p>This is the first paragraph</p>
  <p>This is the second paragraph</p>
  <p>This is the third paragraph</p>
</div>

Result is much cleaner, highly intuitive and easy to read. Most importantly, we regain the ability to style the paragraphs anyway we want with this method.

LinusBorg commented 6 years ago

This doesn't play well with the virtual dom/ render function approach we have ya pen Vue 2.0.

It would require major changes to underlying architecture, which is something we likely don't want to do at this point, in my opinion.

Out of curiosity: what kind of app are you building that relies heavily on inserting html like that?

In my perception this is an escape hatch for some edge cars but should not be something one replies on regularly. But that's just my experience.

pxwee5 commented 6 years ago

I'm using prismic headless CMS's prismic-dom library. It returns field values in HTML. Of course the above is just an example that I use to illustrate how we can make use of the triple curly braces.

prismic-dom returns the field values from the CMS as HTML string. My frontend components will structure the values in such a way. e.g.

<div>
  <div v-html="titleHTML"></div>
  <div v-html="imageHTML"></div>
  <div v-html="contentHTML"></div>
</div>

This is just a basic example but you can see that all the wrapping divs are actually redundant. It makes the title, image and content hard to style because each containing element becomes independent and you can't do h2 + img or img + p.

I'd love to know if there's a better way to approach this though.

Akryum commented 6 years ago

Why not joining all the potential HTML together?

<div v-html="titleHTML + imageHTML + contentHTML"></div>
zsalzbank commented 6 years ago

Maybe you can make v-html work with <template> tags?

LinusBorg commented 6 years ago

No, v-html won't work on <template>

LinusBorg commented 6 years ago

A (admittedly hacky and less performant) way would be to render Vue instances with each pievce of HTML, extract the vnodes, and use a dnymaically created functional component to display them:

https://jsfiddle.net/Linusborg/mfqjk5hm/

Ugly, but works :D

Edit: updated fiddle to be more reusable.

pxwee5 commented 6 years ago

Thanks LinusBorg and Akryum. Both of you are really helpful. I think for now I will go with Akryum's solution and see where that takes me. It definitely works with the example shown above.

I'll report back if it gets tricky in a more sophisticated use case.

bhushan commented 5 years ago

i cant render thread body with html with vue inline template thread body has {{ somecontent }}

this is the laravel blade where i am putting my all html from database {!! $thread->body !!}

error is

ReferenceError: somecontent is not defined

Chrisimir commented 4 years ago

As Akryum suggested you can use v-html="titleHTML + imageHTML + contentHTML" to concat the HTML elements and and use ${variableToRender} to use component rendered elements, so for @bhushangaykawad issue, you should be able to do something like <div v-html="variableWhatever + `<img src='${require('~/assets/whatever-asset')}'/>` + $t('translated.content.one')">

sheriffderek commented 4 years ago

Why not joining all the potential HTML together?

<div v-html="titleHTML + imageHTML + contentHTML"></div>

This solves it - IF you just have a block of CMS stuff - but in many cases I still have different data coming in that need to adhere to adjacent selector rules: https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator - and the first element inside the v-html element needs to be directly next to the one before it.

I put a related topic in the forum: https://forum.vuejs.org/t/raw-html-without-a-parent-element-via-v-html/87160

I'm excited to try @LinusBorg 's solution. Seems like that'll give me what I need. The content in my project is complex - the performance shouldn't matter that much. It's just a complicated article / style-wise. : ) update - ^ solution worked great!

staszek998 commented 3 years ago

Hi!

Since I didn't find any ready-to-use solution, I took @LinusBorg's fiddle and wrapped it within a tiny Vue component - <VHTML>. I hope someone will find it helpful 🙂

jakob-e commented 3 years ago

Here is my two cents take on a (Vue 3) component using directives to unwrap content.

Please note! createContextualFragment allows script execution* - why you would probably want to add some sort of sanitation

import { defineComponent } from 'vue';

//
//  v-html component 
//  <v-html :html="HTML_STRING" />
// 
export default defineComponent({
  name: 'VHtml',
  props: {
    html: {
      type: String,
      required: true
    }
  },
  directives: {
    swap: {
      mounted(el, binding) {

        // createContextualFragment allows script execution 
        // why you would probably want to sanitize the html
        // e.g. using https://github.com/cure53/DOMPurify 
        let safe = DOMPurify.sanitize(binding.value)

        let frag = document.createRange()
          .createContextualFragment(safe)
        el.replaceWith(frag)
      }
    }
  },
  template: '<div v-swap="html"></div>'
})

*

const script = '<script>alert("You may not want this")<\/script>'
<v-html :html="script" />
jez9999 commented 1 year ago

This doesn't play well with the virtual dom/ render function approach we have ya pen Vue 2.0.

It would require major changes to underlying architecture, which is something we likely don't want to do at this point, in my opinion.

Out of curiosity: what kind of app are you building that relies heavily on inserting html like that?

In my perception this is an escape hatch for some edge cars but should not be something one replies on regularly. But that's just my experience.

The fact that the Vue project prioritize how convenient markup is for your internal implementation over having nicer-looking templates says a lot, and really makes me want to use React in the future.

troth-banko commented 1 year ago

Here's an example of where it would be helpful to have {{{ }}} or similar:

<div class="mt-2 pt-2">
    {{ __('<span class="font-bold">Content Items</span> is an all-encompassing term to define WordPress posts, pages and other content that is added to a <span class="font-semibold italic">Section</span>.') }}
</div>

__() is an i18n function, in this example. I believe the only way to make this work, would be to declare this entire line as a variable so that v-html could make use of it - which is a bit of a pain.