如何将HTML模板作为道具传递给Vue组件

时间:2019-06-08 12:30:17

标签: javascript html vue.js

我有一个textarea组件,其中包括html tag,并且我想在此组件中以编辑方式获取html。我使用Laravel生成html。

<template>
    <div>
        <textarea
                  :value="content"
                  :name="name"
                  :id="id">
              <slot></slot>
        </textarea>
    </div>
</template>

在刀片页面中,我曾经使用过此组件:

<my-component>
   <p class="textbox">hello world</p>
</my-component>

当我将此组件放入页面时,在文本区域中显示标签<slot></slot>。我该怎么办?您有我需要的解决方案吗?

谢谢

4 个答案:

答案 0 :(得分:9)

<textarea>组件被Vue渲染器视为静态组件,因此将它们放入DOM后,它们根本不会改变(因此,如果您检查DOM,就会看到{{1 }}放在<slot></slot>中。

但是,即使它们确实发生了变化,也无济于事。只是因为<textarea>中的HTML元素没有成为其价值。您必须设置TextArea element<textarea>属性才能使其正常工作。

无论如何,不​​要绝望。这是可行的,克服上述问题所需要做的就是发挥一个小的辅助组件的作用。

有很多方法可以实现这一目标,如下所示。它们在本质上有不同,您希望原始组件的模板是这样。

解决方案:将value更改为<textarea>组件

您组件的模板现在将变为:

<textarea-slot>

如您所见,除了将<template> <div> <textarea-slot v-model="myContent" :name="name" :id="id"> <slot></slot> </textarea-slot> </div> </template> 替换为<textarea>外,什么都没有改变。这足以克服Vue对<textarea-slot>进行的静态处理。 <textarea>的完整实现在下面的演示中。

替代解决方案:保留<textarea-slot>,但通过<textarea>组件获取<slot>的HTML

解决方案是创建一个帮助程序组件(以下称为<vnode-to-html>),该组件会将您广告位的VNodes转换为HTML字符串。然后,您可以将此类HTML字符串设置为vnode-to-html中的value。您组件的模板现在将变为:

<textarea>

在两种选择中...

<template> <div> <vnode-to-html :vnode="$slots.default" @html="valForMyTextArea = $event" /> <textarea :value="valForMyTextArea" :name="name" :id="id"> </textarea> </div> </template> 的用法保持不变:

my-component


完整的演示:

<my-component>
   <p class="textbox">hello world</p>
</my-component>
Vue.component('my-component', {
  props: ["content", "name", "id"],
  template: `
      <div>
          <textarea-slot
                    v-model="myContent"
                    :name="name"
                    :id="id">
                <slot></slot>
          </textarea-slot>
          
          
          <vnode-to-html :vnode="$slots.default" @html="valueForMyTextArea = $event" />
          <textarea
                    :value="valueForMyTextArea"
                    :name="name"
                    :id="id">
          </textarea>
      </div>
  `,
  data() { return {valueForMyTextArea: '', myContent: null} }
});

Vue.component('textarea-slot', {
  props: ["value", "name", "id"],
  render: function(createElement) {
    return createElement("textarea",
      {attrs: {id: this.$props.id, name: this.$props.name}, on: {...this.$listeners, input: (e) => this.$emit('input', e.target.value)}, domProps: {"value": this.$props.value}},
      [createElement("template", {ref: "slotHtmlRef"}, this.$slots.default)]
    );
  },
  data() { return {defaultSlotHtml: null} },
  mounted() {
    this.$emit('input', [...this.$refs.slotHtmlRef.childNodes].map(n => n.outerHTML).join('\n'))
  }
});

Vue.component('vnode-to-html', {
  props: ['vnode'],
  render(createElement) {
    return createElement("template", [this.vnode]);
  },
  mounted() {
    this.$emit('html', [...this.$el.childNodes].map(n => n.outerHTML).join('\n'));
  }
});

new Vue({
  el: '#app'
})

故障:

  • Vue将<script src="https://unpkg.com/vue"></script> <div id="app"> <my-component> <p class="textbox">hell o world1</p> <p class="textbox">hello world2</p> </my-component> </div>解析为VNode s,并使它们在<slot>属性中可用。自然,默认插槽位于this.$slots.SLOTNAME中。
  • 因此,在运行时中,您可以使用通过this.$slots.default传递的内容(作为<slot>中的VNode)。现在的挑战变成了如何将这些VNode转换为HTML字符串?。这是一个复杂的still open, issue,将来可能会获得不同的解决方案,但是,即使有,{ {3}}。
  • 以上两个解决方案(this.$slots.defaulttemplate-slot)都使用Vue的render函数将VNode渲染到DOM,然后拾取渲染的HTML。
  • 由于提供的插槽可能具有任意HTML,因此我们将VNode渲染到it will most likely take a while中,该{Node}不执行任何vnode-to-html标签。
  • 两种解决方案之间的区别在于它们如何“处理”从渲染函数生成的HTML。
    • <script>作为事件返回,应由父(vnode-to-html)拾取,该父事件使用传递的值来设置my-component属性,该属性将设置为{{1 }}中的data
    • :value向父级声明自己为textarea。这是一种更清洁的解决方案,但需要更多注意,因为您必须指定要传递给textarea-slot内部创建的<textarea>的属性。


包装和现成的替代方案

但是,重要的是要知道,Vue在将声明的<textarea>解析为textarea-slot时会剥离一些格式信息,例如顶级组件之间的空格。同样,它会剥离<template>标签(因为它们不安全)。这些是使用<slot>(无论是否显示)的解决方案所固有的警告。所以要注意。

Vue的典型富文本编辑器通过使用<script>(或<slot>)属性将代码传递到组件中来完全解决此问题。

众所周知的例子包括:

他们在他们的网站上都有非常好的文档(如上链接),所以在这里重复它们对我没有多大用处,但仅作为示例,请看codemirror如何使用v-model道具来传递代码:

value

这就是他们的做法。当然,如果value(带有警告)适合您的用例,则也可以使用它们。

答案 1 :(得分:5)

简短的回答是不可能

您的广告位位于 textarea 标签内。 Textare标签只能在其框上显示文本内容。

因此,在需要一种“ HTML编辑模式”的情况下,您可能需要使用WYSIWYG编辑器,我建议您可以将CKEditor用于VueJS,甚至可以直接编辑HTML代码。

https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/vuejs.html

您的HTML

<div id="app">
    <ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
</div>

您的组件

const app = new Vue( {
    el: '#app',
    data: {
        editor: ClassicEditor,
        editorData: '<p>Editable Content HTML</p>',
        editorConfig: {
            // The configuration of the editor.
        }
    }
} );

答案 2 :(得分:0)

您需要定义一个插槽。检查official guide上带有插槽的组件和内容分发。还建议您阅读this blog的使用方法

答案 3 :(得分:0)

如果您要编写自己的内容编辑器,可以将 div 与contenteditable =“ true”属性一起使用,而不要使用 textarea 。之后,您可以编写文本装饰方法... 带有laravel的生成的html存储在 myhtml 中,并在vue组件中使用它。

示例:我还上传到了codeandbox [Simple Vue Editor]

<template>
  <div>
    <button @click="getEditorCotent">Get Content</button>
    <button @click="setBold">Bold</button>
    <button @click="setItalic">Italic</button>
    <button @click="setUnderline">Underline</button>
    <button @click="setContent">Clear</button>
    <div class="myeditor" ref="myeditor" contenteditable v-html="myhtml" @input="onInput"></div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  data: () => {
    return {
      myhtml:
        "<h1>Simple editor</h1><p  style='color:red'>in vue</p><p>Hello world</p>" // from laravel server via axios call
    };
  },
  methods: {
    onInput(e) {
      // handle user input
      // e.target.innerHTML
    },
    getEditorCotent() {
      console.log(this.$refs.myeditor.innerHTML);
    },
    setBold() {
      document.execCommand("bold");
    },
    setItalic() {
      document.execCommand("italic");
    },
    setUnderline() {
      document.execCommand("underline");
    },
    setContent() {
      // that way set your html content
      this.myhtml = "<b>You cleared the editor content</b>";
    }
    // PS. Good luck!
  }
};
</script>

<style scoped>
.myeditor {
  /* text-align: left; */
  border: 2px solid gray;
  padding: 5px;
  margin: 20px;
  width: 100%;
  min-height: 50px;
}
</style>