Render email HTML in a VueJS component without impacting the global styles - html

I'm drafting a VueJS app which allows users to insert an email HTML template, do some processing and download the processed template afterwards.
I have two components and trying to render the HTML via the HTMLViewer component with v-html function as follows:
Home:
<template>
<div>
<mdb-container>
<mdb-row>
<mdb-col>
<div class="form-group">
<mdb-input
type="textarea"
label="Paste your HTML here:"
outline
:rows="25"
v-model="emailTemplate"
/>
</div>
</mdb-col>
<mdb-col>
<HTMLViewer :emailTemplate="emailTemplate" />
</mdb-col>
</mdb-row>
<mdb-row class="ml-1">
<mdb-btn outline="default">Next</mdb-btn>
<mdb-btn outline="danger" #click="clearEmailTemplate">Clear</mdb-btn>
</mdb-row>
</mdb-container>
</div>
</template>
<script>
import { mdbContainer, mdbRow, mdbCol, mdbInput, mdbBtn } from 'mdbvue';
import HTMLViewer from '../components/HTMLViewer';
export default {
name: 'HomePage',
components: {
mdbContainer,
mdbRow,
mdbCol,
mdbInput,
mdbBtn,
HTMLViewer
},
data() {
return {
emailTemplate: '',
};
},
methods: {
clearEmailTemplate() {
this.emailTemplate = null;
this.iframeVisible = false;
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
HTMLViewer:
<template>
<div v-html="emailTemplate">
</div>
</template>
<script>
import { mdbContainer, mdbRow, mdbCol, mdbInput, mdbBtn } from 'mdbvue';
export default {
name: 'HTMLViewer',
components: {
mdbContainer,
mdbRow,
mdbCol,
mdbInput,
mdbBtn
},
props:['emailTemplate'],
data() {
return {
};
},
};
</script>
<style scoped>
</style>
The problem I'm facing is the fact that the email template CSS styles overwrite the ones from the parent component (Home).
I tried to use iFrame which solves this issue but also creates a lot of different problems related to interacting with the HTML template, therefore I would like to avoid iframe as much as possible.
My question is - is there a good way to make sure the inserted email template HTML does not affect the global styles?

Related

How can I get data from another Vue file?

As the code show below, A.vue file has element data return some number values
<template></template>
<script>
export default {
data(){
return{
element: [
{
number:'11'
}
{
number:'22'
}
]
}
}
}
</script>
Now I want to get element.length from A.vue to B.vue. Is there a way to do that? I saw a solution with button click but i dont want to use button to pass data.
B.vue file
<template>
<div>I want to get element.length here</div>
</template>
You can simply achieve it by passing prop (which contains the length of the element array) from A.vue component to B.vue component. Here is the live demo :
Vue.component('bcomponent', {
// declare the props
props: ['length'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<div>Element length: {{ length }}</div>',
});
var app = new Vue({
el: '#app',
data: {
element: [{
number: '11'
}, {
number: '22'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Component A -->
<div id="app">
<BComponent :length="element.length">
</BComponent>
</div>
If it's possible, just pass the data as a prop from B to A, this way you can implement any logic on the data.
If it's not, you should use vuex for data storage, so any component can access it.

Vue component using the <code> does not re-render on change of prop

I am making a code snippet that shows the code of a html element.
The snippet is suppose to change on press of buttons on other component.
The text displayed on snippet is passed as prop to the code snippet component.
<template>
<pre class="language-html" id="code-section">
<code>{{sourcecode}}</code>
</pre>
</template>
<script>
export default {
name: "CodeSnippest",
props: {
sourcecode: String,
}
}
The problem is that the display of the component does not change while the prop is changing (checked on Vue devtools).
It might be the fact that code tag contains several spans, but I'm not sure.
The actual text is not directly in code tag, but in span inside it
Here's the use of the snippet component:
<template>
<div>
<v-row class="my-5">
...
<code-snippest **:sourcecode="code"** />
</div>
</template>
<script>
import CodeSnippest from "./CodeSnippest.vue";
export default {
name: "PlayGround",
components: {
CodeSnippest,
},
data() {
return {
baseTag: "button",
baseClass: "btn",
additionalClass: "outline",
additionalColor: "red",
outline: false,
staticc: false,
text: "button",
colors: ["red", "sunset", "sea", "white", "whale"],
};
},
computed: {
code() {
return `
<${this.baseTag} class="${this.baseClass} ${
this.outline ? this.additionalClass : "bg"
}-${this.additionalColor} ${this.staticc ? "static" : ""}">${
this.text
}</${this.baseTag}>
`;
},
},
};
</script>
The data changes by buttons and checkboxes, the computed is 100% changing.
Any ideas?
Thank you!

How to include inline .svg in Nuxt application

I want to do a common thing - include dynamically an svg HTML in Vue Nuxt application which I will able to style. To do this, I created a component but instead of image, I get a text data:image/svg+xhtml....
How to make it work?
<template>
<div v-html="src"></div>
</template>
<script>
export default {
name: 'Icon',
props: {
name: {
type: String,
required: true
}
},
computed: {
src() {
const src = require(`assets/icons/${this.name}.svg`)
return src
}
}
}
</script>
It seems like #nuxtjs/svg will do what you're trying to do. After installing it, try:
<template>
<div v-html="src"></div>
</template>
<script>
export default {
name: 'Icon',
props: {
name: {
type: String,
required: true
}
},
computed: {
src() {
const src = require(`assets/icons/${this.name}.svg?raw`)
return src
}
}
}
</script>
It's not easy and I tried a bunch of different solutions that "seemed" like they should work.
My understanding is that it comes down to how Webpack deals with "image" assets and using them inline vs stand-alone sort of changes the definition.
I've got a library of SVG icons that needed code access (eg change colors after run time) so I had to have the SVG inline. Here's where I landed, It's not ideal but it works.
Create a new component with a prop slot for SVG name. Inside that component add all your svg's as code. Wrap each in a v-if.
<template>
<div class="svgIcon">
<svg v-if="icon == 'name1'">...</svg>
<svg v-if="icon == 'name2'">...</svg>
<svg v-if="icon == 'name3'">...</svg>
<svg v-if="icon == 'name4'">...</svg>
</div>
</template>
<script>
export default {
props: ['icon']
}
</script>
Then use the component by filling the name you want in the slot
<template>
<div>
<svgIcons :icon="name1"></svgIcons>
</div>
</template>
<script>
import svgIcons from '../path/to/iconsComponent.vue'
export default {
components: { svgIcons },
...
}
</script>

How to use slots inside of template components in HTML

I'm trying to use slots inside of a Vue component to display different titles more easily. However, when I try to replace the slot with data, regardless of the relative positioning in the markup, the slot only uses it's fallback option.
It's my understanding that the template to be used goes first, with a label, then slots are put in and given a "name," with fallback text between the opening and closing slot tags, like so:
<template id="somename-template>
<slot name="attrname>Some Fallback</slot>
</template>
Then data is stored as such:
<somename>
<span slot="attrname">Real text</slot>
</somename>
I have tried repositioning the both above and below the script, and above and below the , however no combination provides the expected results.
My actual code:
<body>
<template id="comp-dem-template">
<header-component></header-component>
</template>
<script>
customElements.define('comp-dem',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('comp-dem-template').content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
});
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
<comp-dem>
<span slot="pagetitle">
Images
</span>
</comp-dem>
</body>
The markup should look like:
<h1>Images</h1>
However, instead looks like:
<h1>Page Title Fallback</h1>
I can tell it's probably a super simple thing that I'm doing wrong (or it's the wrong tool for the job), but even looking at other working examples, I can't tell what that exactly is.
It's not quite clear to me what you're trying to accomplish. You're passing the <span slot="pagetitle">Images</span> to <comp-dem> but the <comp-dem> component doesn't have a slot - it's the <header-component> that has a slot. Why do you need to wrap a component in a component?
For the code to work, the slot needs to be passed like so:
<body>
<template id="comp-dem-template">
<header-component>
<span slot="pagetitle">
Images
</span>
</header-component>
</template>
<script>
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
</body>
Or, if you insist on using <comp-dem>, I think you might need to do the following:
<body>
<template id="comp-dem-template">
<header-component>
<span slot="pagetitle">
<slot name="pagetitle"><slot>
</span>
</header-component>
</template>
<script>
customElements.define('comp-dem',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('comp-dem-template').content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
});
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
<comp-dem>
<span slot="pagetitle">
Images
</span>
</comp-dem>
</body>

Exporting VueJS-rendered HTML with checkbox fails to preserve checked state

My component template contains the following checkbox code:
<div ref="htmlData">
<input
type="checkbox"
class="mycb"
:id="uniqID"
:disabled="disabled"
v-model="cbvalue"
>
</div>
(parts removed for simplicity).
I need to create a PDF out of this template (on server). This is what i'm doing in the code:
methods : {
save () {
let saveData = {
'html': this.$refs.htmlData.innerHTML
};
this.$http.post('/api/save',saveData);
}
}
However, the saved HTML doesn't contain checkbox state, so it always saves an unchecked checkbox.
Here's a slightly modified jsfiddle.
My question is: how can I capture the checkbox state in the rendered HTML?
I tried adding :checked="cbvalue" prop - no luck
It looks like there's no way to bind the checked attribute of an input; Vue does everything through the property. (For reference, the property is the internal state, the attribute is what shows up in the HTML.)
To get the attribute to reflect the property, you can add a little directive.
var demo = new Vue({
el: '#demo',
data: () => ({
val: false
}),
methods: {
save() {
console.log(this.$refs.main.innerHTML);
}
},
directives: {
explicitChecked: {
update(el) {
if (el.checked) {
el.setAttribute('checked', 'checked');
} else {
el.removeAttribute('checked');
}
}
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<button #click="save">save</button>
<div ref="main">
<input type="checkbox" v-model="val" v-explicit-checked>
</div>
</div>