Vue.js: conflict v-html and a function - javascript

Here is the template of the component for a notification:
<template>
<div>
<li class="g-line-height-1_2">
<router-link :to="linkFromNotification(item)"
#click.native="readNotification(item)"
v-html="item.message"
:class="activeNotification">
<br>
<span class="g-font-size-12 g-color-gray-dark-v5">
{{ getTime }}
</span>
</router-link>
</li>
<li>
<hr class="g-my-0">
</li>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
export default {
props: ['item'],
computed: {
...mapGetters([
'getLastNotifications',
'getNotifications',
]),
activeNotification() {
if (this.item.viewed === true) {
return 'nav-link g-bg-gray-light-v5--hover g-px-20 g-py-10 u-link-v5'
} else {
return 'nav-link g-bg-primary-opacity-x--hover g-bg-primary-opacity-x2 g-px-20 g-py-10 u-link-v5'
}
},
getTime() {
moment.locale('ru');
return moment(this.item.created_at, 'YYYY-MM-DD HH:mm:ss Z').fromNow();
},
},
methods: {
...mapActions([
'notifications',
'readNotification'
]),
linkFromNotification(item) {
if (item.notification_type === 'user_subscribed') {
return {name: 'person', params: {id: item.object_id}}
} else if (['comment_created', 'answer_selected', 'answer_created'].includes(item.notification_type)) {
// TODO: link must be constructed with hash
return `/posts/${item.object_id}#${item.anchor}`;
} else if (item.notification_type === 'user_coauthored') {
return {name: 'show_post', params: {id: item.object_id}}
}
}
}
}
</script>
Every notification gets a text from a server using v-html. Now I can see the text of the notification but I cannot see the time of it. It seems like my function getTime is overlapped by something in the router-link. When I try to put <span> with the function getTime under the router-link, the notification time is displayed but my page layout is getting collapsed. Please help!

v-html sets the content of the element it is attached to. Any content inside the containing tags will be overwritten. To add HTML to existing content, make a <span> where you want the HTML inserted and put your v-html on that span.

Related

Create anchor tags dynamically in Vue.JS

I have a JS-object and I need to be able to create html elements out of it and render it in Vue.JS. My solution until now was to get the object, create the HTML-elements as strings out of it and then just add it to the template. However, although this shows the elements correctly, the anchor tags are not clickable.
<template>
<div>
<template v-if="elementData">
<ul>
<li v-for="(value, key) in elementData" :key="key">
<div v-html='value'></div>
</li>
</ul>
</template>
</div>
</template>
<script>
const about = [
[
'This is normal text ',
{
text: 'im a link',
link: 'https://www.stack-overflow.com',
},
', is not bad ',
],
[
'More text and text',
],
];
export default {
data() {
return {
elementData: [],
};
},
mounted() {
this.setData();
},
methods: {
setData() {
this.elementData = about.map((paragraph) => {
let pElement = '<p>';
paragraph.forEach((part) => {
if (typeof part === 'object') {
const link = `<a target="_" :href="${ part.link }">${ part.text }</a>`;
pElement = pElement.concat(link);
} else pElement = pElement.concat(part);
});
pElement.concat('</p>');
return pElement;
});
},
},
};
</script>
The problem probably comes from me not creating the actual html-elements (like when using document.createElement('div') with vanilla JS). However, I don't know how to do this Vue.JS.
Instead of manipulating the DOM manually, you can use Vue's conditional rendering to achieve the same goal. Take a look at this solution below:
<template>
<div>
<template v-if="elementData">
<ul>
<li v-for="(paragraph, key) in elementData" :key="`paragraph-${key}`">
<span v-for="(part, partKey) in paragraph" :key="`part-${partKey}`">
<!-- Render an anchor tag if the part is a link -->
<template v-if="isLink(part)"
><a :href="part.link">{{ part.text }}</a></template
>
<!-- Else, just put output the part -->
<template v-else>{{ part }}</template>
</span>
</li>
</ul>
</template>
</div>
</template>
<script>
const about = [
[
"This is normal text ",
{
text: "im a link",
link: "https://www.stackoverflow.com",
},
", is not bad ",
],
["More text and text"],
];
export default {
...
data() {
return {
elementData: [],
};
},
mounted() {
this.setData();
},
methods: {
isLink(e) {
// You can change this condition if you like
return e && typeof e === "object" && e.link && e.text;
},
setData() {
this.elementData = about;
},
},
};
</script>
Notice how we just created elements based on conditions. We render an anchor tag if the part is an object, and has the link and text attribute. Else, we just display the string.
See live demo

Vue.js - How to dynamically bind v-model to route parameters based on state

I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"

In Vue2 tag input, trying to display tag name from tag object in autocomplete

I have a Vue component with a tag input where I make an ajax call to the db to retrieve suggestions as the user is typing. I am using #johmun/vue-tags-input for this. Everything works fine except that instead of the autocomplete listing options including only the tag attribute of the Tag model, it includes the entire object.
I want to list only the tag attribute in the view, but I want to reference the array of entire Tag objects when it comes time to create the association with the user.
This what the current dropdown look like in the browser:
Here is my input component removing the irrelevant parts, so it meets SO's size constraints:
<template>
<div >
<b-container class="mt-8 pb-5">
<b-row class="justify-content-center">
<b-col lg="5" md="7">
<form>
...
<div v-if="step === 3">
<h2><strong>What topics are you interested in?</strong> (e.g tag1, tag2, etc...)</h2>
<h2>A few popular ones:
<button #click.prevent="addToTags(item)" class="btn btn-sm btn-success" v-for="item in existingTags.slice(0, 3)" :key="item.id">
{{ item.tag }}
</button>
</h2>
<vue-tags-input
v-model="tag"
v-on:keyup.native="getTags"
:tags="tags"
:autocomplete-items="filteredItems"
:autocomplete-min-length=3
#tags-changed="confirmedTags"
/>
</div>
...
</form>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import VueTagsInput from '#johmun/vue-tags-input';
import UsersService from '#/services/UsersService'
import TagsService from '#/services/TagsService'
import TagRelationsService from '#/services/TagRelationsService'
export default {
name: 'UserOnboard',
data() {
return {
tag: '',
tags: [],
...
}
};
},
components: {
VueTagsInput
},
computed: {
filteredItems() {
return this.existingTags.filter(i => {
return i.tag.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
});
},
...
user() {
return this.$store.state.auth.user
},
existingTags() {
return this.$store.state.tags.existingTags
}
},
...
methods:{
...
},
addToTags(newTag) {
if (!this.tags.includes(newTag)) {
this.tags.push(newTag)
}
// on button click add appropriate tag to tags array
// console.log('tag array is: ',tags)
},
confirmedTags(event) {
this.tags=event
console.log(event)
},
...
getTags() { //debounce need to be inside conditional
console.log('gettin tags')
// if (this.tag.length >2) {
this.$store.dispatch('debounceTags', this.tag)
// }
}
}
}
</script>
Also, here is the debounceTags method which runs via vuex:
import TagsService from '#/services/TagsService'
import { debounce } from "lodash";
export const state = {
existingTags: []
}
export const mutations = {
setTags (state, tags) {
state.existingTags = tags
}
}
export const actions = {
debounceTags: debounce(({ dispatch }, data) => {
console.log("Inside debounced function.");
dispatch("getTags" ,data);
}, 300),
async getTags ({ commit }, data) {
await TagsService.getTags(data)
.then(function (response) {
console.log('before setting tags this is resp.data: ', response)
commit('setTags', response);
});
}
}
export const getters = {
}

Float transparent PNG over text

I am looking to add semi-transparent stamp type image over a particular part of the text.
We are using vue to build our new website.
I've not really found anything about how to do this in vue.
I'm looking to place the image over the 4th line of the code shown below. Basically what it does is display the 'Report accepted by:' text along with the person who accepted the report taken from the database. I'd like to display that with an overlapping image that resembles a stamp of approval.
<template>
<div class="clearfix">
<span>Report accepted by:</span>
<span v-if="report_info.accepted && report_info.accepted_by !== null">{{ memberById(report_info.accepted_by).callsign }}</span>
<button
v-if="isAdmin"
class="float-right"
v-on:click="acceptRejectReport"
>{{ acceptButtonText }}</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex"
export default {
name: "ReportApprovalComp",
mounted () {
this.checkAdmin();
},
data () {
return {
isAdmin: false
}
},
computed: {
acceptButtonText() {
if(this.report_info.accepted){
return "Revoke report acceptance";
} else {
return "Approve report";
}
},
...mapState("missionStore", {
report_info: state => state.report,
}),
...mapGetters("missionStore", [
"memberById"
])
},
methods: {
checkAdmin: async function () {
this.isAdmin = await this.$auth.isAdmin(this.$options.name);
},
acceptRejectReport: async function () {
this.$store.dispatch('missionStore/acceptRejectReport',
{
caller: this.$options.name,
member_id: await this.$auth.getUserId(),
});
}
}
}
</script>
<style scoped>
</style>
already has the logic ... you just need to actually provide your img element as the last child inside that span. Your span will need to have css position:relative and your img needs css position:absolute;top:0;right:0; ... you might need display:inline-block on your span
<span v-if="report_info.accepted && report_info.accepted_by !== null"
style="position:relative;display:inline-block;">
{{ memberById(report_info.accepted_by).callsign }}
<img style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
width:100%;height:auto;" src="web path to img file"/>
</span>

Vue2: replace slot contents with compiled component based on regex?

I have a component Tooltip that adds some extra functionality.
I want to define a wrapper component ApplyTooltips, which has a default slot and replaces all instances of [[<any text here>]] with <Tooltip text="<any text here>"/> (e.g. very similar to a Markdown parser)
I am basing my approach on this fiddle: https://jsfiddle.net/Herteby/pL13vda2/
template
<template>
<div>
<div class="tooltip-wrapper">
<slot>
</slot>
<template v-for="node, n in parsed">
<a v-if="n % 2">{{node}}</a>
<template v-else>{{node}}</template>
</template>
</div>
</div>
</template>
script
export default {
name: 'ApplyTooltips',
data: () => ({
el: null,
}),
computed: {
text() {
return this.el ? this.el.innerHTML : ''
// return this.$slots.default[0].text
},
re() {
return /\[\[(.+?)\]\]/gi // captures [[words]]
},
parsed() {
return this.text.split(this.re) // every other value in this list will be a captured pattern
}
},
mounted() {
this.el = this.$el
// console.log(this.$slots.default[0].elm)
}
}

Categories

Resources