I have a component on my parent page like so:
Detail.vue
Template:
<feature-list :description="description"></feature-list>
Script:
axios.get(`my/end/point`)
.then(res => {
this.description = res.data.product.description
})
.catch(function (error) {
console.log('error', error)
})
Features.vue
Template:
<component :is="subcomponent" :height="overallHeight" :width="overallWidth" :length="overallLength"></component>
Script:
components: {
subcomponent: {
props: ['overallHeight', 'overallWidth', 'overallLength'],
/* template: this.description */
}
}
Expected return value from this.description:
<el-row :gutter="30">
<el-col :sm="24">
<ul class="dimensions d-flex">
<li class="dimension">
<img src="/length.jpg" alt="">
<h4 class="title">Length</h4>
<p class="base-copy">{{length}}</p>
</li>
<li class="dimension">
<img src="/height.jpg" alt="">
<h4 class="title">Height</h4>
<p class="base-copy">{{height}}</p>
</li>
<li class="dimension">
<img src="/width.jpg" alt="">
<h4 class="title">Width</h4>
<p class="base-copy">{{width}}</p>
</li>
</ul>
</el-col>
</el-row>
How can I send the template to the component? and render the values (lenth, width and height)
Something like this, only difference being, the template comes from an ajax call.
After some digging, I've found a way to solve this, the idea is to pass the template and the props as an object to the is prop.
The code will look like this,
<component
:is="description && { template: `<div>${description}</div>`, props: ['height', 'width', 'length'] }"
:height="overallheight"
:width="overallwidth"
:length="overalllength">
</component>
Related
I am trying to understand how to emit an event from a child component to the parent that scrolls to the correct section via a ref or id. So for example the navigation menu is a child component with links. when I click a link, the corresponding section in the parent scrolls into view. I am coming from React so I am a bit thrown off. How do I pass parameters up to scroll to correct ref or id? basically the way anchorlinks act.
Here is what I got so far.. I am not sure I should be using links , I might need buttons since i'm not navigating pages.
Navigation.vue (Child)
<template>
<div class="navigation">
<img class="logo" alt="Vue logo" src="../assets/logo.png" />
<nav>
<ul>
<li>
<a #click="goSection" href="/">Contact</a>
</li>
<li>
Projects
</li>
<li>
Skills
</li>
<li>
Education
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
name: "Navigation",
//I want to emit an event in the parent where the page section scrolls in to view//
methods: {
goSection() {
this.$emit("go-section", this.value);
},
},
};
</script>
App.vue (Parent)
<template>
<div id="app">
<Navigation #go-section="goToSection" />
<section class="section" ref="projects">
<p>Projects</p>
</section>
<section class="section" ref="skills">
<p>Skills</p>
</section>
<section class="section" ref="contact">
<p>Contacts</p>
</section>
</div>
</template>
<script>
import Navigation from "./components/Navigation.vue";
export default {
name: "App",
components: {
Navigation,
},
methods: {
goToSection(event, value) {
var element = this.$refs[(event, value)];
var top = element.offsetTop;
window.scrollTo(0, top);
},
},
};
</script>
You can use a tag but prevent default link behavior, and pass ref to your goSection method :
Vue.component('navigation', {
template: `
<div class="navigation">
<img class="logo" alt="Vue logo" src="../assets/logo.png" />
<nav>
<ul>
<li>
<a #click.prevent="goSection('contact')" href="/">Contact</a>
</li>
<li>
Projects
</li>
<li>
Skills
</li>
<li>
Education
</li>
</ul>
</nav>
</div>
`,
methods: {
goSection(val) {
this.$emit("go-section", val);
},
},
})
new Vue({
el: '#demo',
methods: {
goToSection(value) {
const top = this.$refs[(value)].offsetTop;
window.scrollTo({
top: top,
left: 0,
behavior: 'smooth'
});
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<navigation #go-section="goToSection"></navigation>
<section class="section" ref="projects">
<p>Projects</p>
</section>
<section class="section" ref="skills">
<p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p>
</section>
<section class="section" ref="contact">
<p>Contacts</p>
</section>
</div>
You can emit an event with parameter (section ref for example) in the child component:
<a #click="$emit('go-section', 'contact')" href="/">Contact</a>
<a #click="$emit('go-section', 'projects')" href="/">Projects</a>
And then use this parameter in the parent's listener method
// template
<Navigation #go-section="goToSection" />
// js
goToSection(sectionRef) {
var element = this.$refs[sectionRef];
var top = element.offsetTop;
window.scrollTo(0, top);
},
Currently, I'm working with Vue v2.x.x. I have an array:
sectionTitles = ['Technology', 'Data', 'Poverty and Research', ...]
and I have jobsData that looks like this:
[{'title': 'Software Engineer', mainTag: 'Data', ...}...]
I want to display <li> in an <ul> when the sectionTitle matches the job.mainTag.
I was reading in the Vue docs that you shouldn't combine v-if with v-for, so I created a computed method to be able to filter the jobs. Here is what I did so far:
window.onload = function () {
var app = new Vue({
delimiters: ['${', '}'],
el: '#app',
data: {
jobs: jobsData,
sectionTitles: ['Data','Poverty Research Unit', 'Technology']
},
computed: {
matchingTitles: function (sectionTitle) {
return this.jobs.filter(function (job, sectionTitle) {
job.mainTag === sectionTitle;
})
}
}
})
}
<div id="app">
<template v-for="title in sectionTitles">
<h4 class="h3">{{ title }}</h4>
<ul class="list-none p-0 color-mid-background" id="jobs-list">
<li class="py-1 px-2" v-for="job in matchingTitles(title)">
<a :href="`${job.url}`">
${job.title}
</a>
</li>
</ul>
</template>
</div>
So basically I want to only display <li> when the sectionTitle (for example Data) matches the job.mainTag. How can I go about achieving this in Vue?
Change your computed method to just a method. Then change your filter to return a value. Also for displaying in Vue you want to use {{....}} not ${...}
new Vue({
el: '#app',
data: {
jobs: [{'title': 'Software Engineer', mainTag: 'Data'}],
sectionTitles: ['Data','Poverty Research Unit', 'Technology']
},
methods: {
matchingTitles: function (sectionTitle) {
return this.jobs.filter ((job)=>{
return job.mainTag === sectionTitle;
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="title in sectionTitles">
<h4 class="h3">{{ title }}</h4>
<ul class="list-none p-0 color-mid-background" id="jobs-list">
<li class="py-1 px-2" v-for="job in matchingTitles(title)">
<a :href="job.url">
{{job.title}}
</a>
</li>
</ul>
</template>
</div>
#depperm's answer works well (+1), but I'll offer a more render-efficient alternative. Computed properties are cached, so you could avoid the work of matchingTitles() on re-render. In addition, it might be easier to comprehend the template alone without having to jump to the implementation of matchingTitles().
I recommend computing the entire list to be iterated, mapping sectionTitles to the appropriate iterator object:
computed: {
items() {
return this.sectionTitles.map(title => ({
title,
jobs: this.jobs.filter(job => job.mainTag === title)
}))
}
}
Then, you'd update the references in your template to use this new computed prop:
<template v-for="item in 👉items👈">
<h4>{{ item.title }}</h4>
<ul>
<li v-for="job in 👉item.jobs👈">
<a :href="job.url">
{{ job.title }}
</a>
</li>
</ul>
</template>
new Vue({
el: '#app',
data: {
jobs: [{'title': 'Software Engineer', mainTag: 'Data'}],
sectionTitles: ['Data','Poverty Research Unit', 'Technology']
},
computed: {
items() {
return this.sectionTitles.map(title => ({
title,
jobs: this.jobs.filter(job => job.mainTag === title)
}))
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<template v-for="item in items">
<h4 class="h3">{{ item.title }}</h4>
<ul class="list-none p-0 color-mid-background" id="jobs-list">
<li class="py-1 px-2" v-for="job in item.jobs">
<a :href="job.url">
{{ job.title }}
</a>
</li>
</ul>
</template>
</div>
I'm using 2 components in my page, one for a comment and inside the comment component is the comment reply component, I did it this way to add some functionality that I needed but now I don't know how to get the buttons in the components to do the stuff they did before I turned them into components. For example the edit function opens a dialog that is in the page where the comment component is located but now it doesn't do anything, same with the reply functionality.
How can I get this to work, or is there a way to keep those methods in the main page and not have to put them in the components? I've never made my own component before so I'm not sure how to do this.
This is the Comment component code, the editComment and openReply are methods from the main page
<template>
<div class="comment">
<el-card shadow="never" v-if="comment.parent_id === null">
<el-row v-if="comment.deleted === 0">
<el-row style="margin-bottom: 15px">
<el-col :span="20" v-if="forum.anonymous === 0">
<p class="comment-user-name">{{comment.user.name}} dice: </p>
</el-col>
<el-col :span="20" v-if="forum.anonymous === 1">
<p class="comment-user-name">Anónimo dice: </p>
</el-col>
<el-col :span="4">
<div style="text-align: end"
v-if="comment.user_id === $page.auth.user.auth.user.id">
<div v-if="canEdit" class="btn-link-edit action-button"
#click="editComment(comment)">
<i class="fas fa-pencil-alt"></i>
</div>
<div class="btn-link-delete action-button"
#click="remove(comment.id)">
<i class="fas fa-trash"></i>
</div>
<div class="btn-link-preview action-button"
#click="openReply(comment)">
<i class="fas fa-reply"></i>
</div>
</div>
<div style="text-align: end"
v-if="comment.user_id !== $page.auth.user.auth.user.id">
<div class="btn-link-preview action-button"
#click="openReply(comment)">
<i class="fas fa-reply"></i>
</div>
</div>
</el-col>
</el-row>
<el-row style="margin-top: 10px">
<p class="comment-comment">{{comment.comment}}</p>
</el-row>
<el-row class="fa-pull-right pb-1">
<p class="comment-user-time ">{{formatDate(comment.comment_time)}}</p>
</el-row>
</el-row>
<el-row v-if="comment.deleted === 1">
<el-row style="margin-top: 15px">
<p class="comment-comment">{{comment.comment}}</p>
</el-row>
<el-row class="fa-pull-right pb-1">
<p class="comment-user-time ">{{formatDate(comment.comment_time)}}</p>
</el-row>
</el-row>
<el-row style="margin-left: 30px">
<div v-for="reply in comment.replies">
<ReplyComponent :reply="reply" :forum="forum"/>
</div>
</el-row>
</el-card>
</div>
</template>
<script>
import moment from "moment";
import ReplyComponent from "./ReplyComponent";
export default {
name: "Comment",
props: {
comment: Object,
forum: Object,
},
components: {
ReplyComponent
},
data() {
return {
canEdit: Boolean,
interval: null,
mode: '',
form: {
comment: '',
},
};
},
methods: {
openReply(row) {
this.dialogReplyVisible = true;
this.parent = row;
},
editComment(item) {
this.mode = 'Editar';
this.form = _.cloneDeep(item);
this.dialogFormVisible = true;
},
checkTime() {
var minutes = moment().diff(moment(this.comment.comment_time), 'minutes');
if (minutes >= 30) {
this.canEdit = false;
// here you could also already clear the interval, since it won't change
} else if (minutes <= 29) {
this.canEdit = true;
}
}
},
created() {
this.checkTime();
this.interval = setInterval(() => {
this.checkTime();
}, 10000);
},
beforeDestroy() {
clearInterval(this.interval);
}
};
</script>
If I understood your questions correctly, you want to communicate (pass data) from Child components to parent component (page).
The way we can achieve this is by $emitting events from Child components and have the parent component respond to these events.
So, in your editComment and openReply methods you would fire events like this:
openReply(row) {
this.$emit('openReply', {any payload that you would want pass to parent});
},
editComment(item) {
this.$emit('editComment', {any payload that you would want pass to parent}
},
And, in your parent component / page you would subscribe and handle those events. Pseudo-code below:
<Comment v-on:openReply="handleOpenReply"
v-on:editCommnet="handleEditComment"/>
Further Reading:
Passing Data to Child Components With Props
Listening to Child Components Events
I'm new in VueJS and I'm try to do something like this
I have 3 components, siderbar ; siderbar-item and sidebar-drop
siderbar.vue is a wrapper when you can put siderbar-item and sidebar-drop, and inside sidebar-drop you can put sidebar-item too
for example:
<sidebar>
<sidebar-item url="myurl" title="Dashboard" icon="fas fa-tachometer-alt" badge=""></sidebar-item>
<sidebar-drop title="News" icon="fas fa-tachometer-alt">
<sidebar-item url="myurl_news" title="News list" icon="fas fa-tachometer-alt" badge=""></sidebar-item>
<sidebar-item url="myurl_news_add" title="Add News" icon="fas fa-tachometer-alt" badge=""></sidebar-item>
</sidebar-drop>
<sidebar-drop title="Categories" icon="fas fa-tachometer-alt">
<sidebar-item url="myurl_categories" title="Categories List" icon="fas fa-tachometer-alt" badge=""></sidebar-item>
</sidebar-drop>
</sidebar>
sidebar.vue is very simple:
<aside>
<slot></slot>
</aside>
sidebar-item.vue
<template>
<ul class="menu-list" >
<li>
<a :href="url" :class="{'is-active': is_active }">
<span class="icon"><i v-if="icon" :class="icon"></i></span>
{{ title }}
<span v-if="badge" class="button badge is-info is-small is-pulled-right "> {{ badge }}</span>
</a>
</li>
</ul>
</template>
<script>
export default {
props: ['title', 'url', 'icon', 'badge'],
data() {
return {
is_active: this.$current_url === this.url
}
}
}
</script>
and sidebar-drop.vue
<template>
<ul class="menu-list" v-if="title">
<li #click="openMenu" :class="{'is-open': is_open}">
<a class="">
<span v-if="icon" class="icon"><i class="fas fa-tachometer-alt"></i></span>
{{ title }}
<span class="badge is-pulled-right" style=""> <i class="fas fa-chevron-left"></i> </span>
</a>
<slot></slot>
</li>
</ul>
</template>
<script>
export default {
props: ['title', 'icon'],
data() {
return {
is_open: false,
}
},
methods: {
openMenu(){
this.is_open = !this.is_open;
},
}
}
</script>
It works great, but I have a little problem. If you notice, sidebar-item has a data called "is_active", it only put a CSS class that this item is active. Great.
Now I'm trying to get into sidebar-drop if a least one of his children has data "is-active" in true, change the is_open sidebar-drop parent data to true.
Example: I click "News" item.
Ul li News is active... works!
Ul li parent drop is open... don't work
Is possible make that?
Thanks everyone!
Update:
I put it in Fiddler to more clear:
https://jsfiddle.net/h35qy2zs/
I do in sidebar-item a random is_active.
If any child of sidebar-drop (sidebar-item's) data is_active:true, sidebar-drop data is_open is true too.
So, it show in color yellow and open
Result:
But now, both are closed at refresh page.
My data function is like below
data() {
return {
message: ''
}
},
I have method like below
adddress () {
for (let value of Object.values(response.body.errors)) {
this.message += '<li>'+ value +'</li>'
}
},
I am trying to display message like below
<template v-if="message">
<div class="ui red message">
<ul>
{{ message }}
</ul>
</div>
</template>
I am getting output like below
Use raw HTML (v-html):
<template v-if="message">
<div class="ui red message">
<ul v-html="message"></ul>
</div>
</template>