I have a sidebar child component with buttons that change the tab:
<script type="text/x-template" id="employee-sidebar">
<div class="sidebar full-height col-" data-color="purple">
<div class="sidebar-wrapper" id="dash-board">
<ul class="nav">
<li v-bind:class="{ active: activeLink == 'dashboard' }">
<a href="#dashboard" #click="changeTab('dashboard')">
<i class="material-icons">dashboard</i>
<p>Dashboard</p>
</a>
</li>
<li v-bind:class="{ active: activeLink == 'team' }">
<a href="#radio" #click="changeTab('team')">
<i class="fa fa-users"></i>
<p>Team</p>
</a>
</li>
<li class="text-center"><button class="btn btn-primary clear-filter">Clear</button></li>
</ul>
</div>
</div>
</script>
When I call #click="changeTab('dashboard')" it calls a function in the child component and emits a changeTab event:
Vue.component('employee-sidebar', {
template: '#employee-sidebar',
props: {},
data: function() {
return {
activeLink: 'dashboard'
}
},
methods: {
changeTab: function(tab) {
// Always see this console log
console.log(tab)
this.activeLink = tab
this.$emit('changeTab', tab)
}
}
})
When I call the component in the parent I listen for this event:
<employee-sidebar v-on:changeTab="changeTheTab(activeLink)"></employee-sidebar>
But the changeTheTab() function is never called in the parent:
changeTheTab: function(tab) {
// Never see this console log
console.log(tab)
this.activeLink = tab
}
What am I doing wrong?
Thanks to #wostex for the lowercase tip, I was able to update my component to the following:
Vue.component('employee-sidebar', {
template: '#employee-sidebar',
props: {},
data: function() {
return {
activeLink: 'dashboard'
}
},
methods: {
changeTab: function(tab) {
console.log(tab)
this.activeLink = tab
this.$emit('change_tab', tab)
}
}
})
and in the parent:
<employee-sidebar v-on:change_tab="(tab) => activeLink = tab"></employee-sidebar>
Related
How can I show/hide the closest div by clicking a button in vue?
lets say I have a list of items, each with some hidden details
<ul>
<li v-for="item in items" :key="item.id">
<div>
<p>{{item.text}}</p>
<button #click="showDetails(item)">Show details</div>
<div class="details" :class="isVisible ? activeClass : 'hidden'">Some hidden details</div>
</div>
</li>
</ul>
Then I do
data() {
return {
items: [ // a bunch of item objects here]
isVisible: false,
activeClass: 'is-visible'
}
},
methods: {
showDetails(item) {
this.isVisible = item;
}
}
Right now, when I click on on of the "showDetails" buttons, all divs with class .details opens and get the .is-visible-class, but I just want the closest div to the item to be displayed. For some reason I think this is pretty simple, but I can't make it work.
How can I achieve that?
try this
<template>
<ul>
<li v-for="(item, i) in items" :key="item.id">
<div>
<p>{{item.text}}</p>
<button #click="showDetails(i)">Show details</button>
<div class="details" :class="i == active ? activeClass : 'hidden'">Some hidden details</div>
</div>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
items: [],
activeClass: 'is-visible',
active: null
};
},
methods: {
showDetails(i) {
this.active = i;
}
}
};
</script>
It would be clearer to create a new component for list item which would contain all logic itself. Something like:
// ListItem.vue
<template>
<div>
<p>{{text}}</p>
<button #click="toggleVisibility">Show details</button>
<div class="details" v-show="isVisible">Some hidden details</div>
</div>
</template>
<script>
props: {
text: String
},
data() {
return {
isVisible: false
}
},
methods: {
toggleVisibility() {
this.isVisible = !this.isVisible
}
}
</script>
and in your parent component:
<ul>
<li v-for="item in items" :text="item.text" :key="item.id" is="list-item" /></li>
</ul>
data() {
return {
items: [ // a bunch of item objects here]
}
}
Just store "isVisible" variable inside the "item"
<ul>
<li v-for="item in items" :key="item.id">
<div>
<p>{{item.text}}</p>
<button #click="showDetails(item)">Show details</div>
<div class="details" :class="item.isVisible ? activeClass : 'hidden'">Some hidden details</div>
</div>
</li>
</ul>
data() {
return {
items: [ // a bunch of item objects here]
isVisible: false,
activeClass: 'is-visible'
}
},
methods: {
showDetails(item) {
item.isVisible = !item.isVisible;
this.$forceUpdate();
}
}
I want that, clicking on a menu item, the active class is triggered only for that specific item and removed for the others, until now I wrote this:
<template>
<nav class="navbar">
<div class="navbar__brand">
<router-link to="/">Stock Trader</router-link>
</div>
<div class="navbar__menu">
<ul class="navbar__menu--list">
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
<router-link to="/portfolio">Portfolio</router-link>
</li>
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
<router-link to="/stocks">Stocks</router-link>
</li>
</ul>
</div>
<div class="navbar__menu--second">
<ul class="navbar__menu--list">
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
End Day
</li>
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
Save / Load
</li>
</ul>
</div>
</nav>
</template>
<script>
export default {
data(){
return{
isActive: false
}
}
}
</script>
now of course, when I click on one item the active class is inserted/removed for all the items, what is the best solution for making that only a specific item, on clicking on it, receives the active class?
You'll want some sort of identifier for each clickable item and set that to your data property. For example
data() {
return { active: null }
}
and in your list items (for example)
<li #click="active = 'portfolio'"
class="navbar__menu--item"
:class="{active:active === 'portfolio'}">
In this example, the identifier is "portfolio" but this could be anything, as long as you use a unique value per item.
You could keep an object of links you have and handle a click on each of items. E.g.
data() {
return {
links: [
{
title : 'Portfolio',
to : '/portfolio',
isActive : false,
location : 'first',
},
{
title : 'Stocks',
to : '/stocks',
isActive : false,
location : 'first',
},
{
title : 'End Day',
to : '#',
isActive : false,
location : 'second',
},
{
title : 'Save / Load',
to : '#',
isActive : false,
location : 'second',
},
]
};
},
methods: {
handleNavClick(item) {
this.links.forEach(el, () => {
el.isActive = false;
});
item.isActive = true;
}
},
I do this in vue3.
the template is:
<li
v-for="(title, index) in titles"
class="title"
:key="index"
:class="{ active: active === index }"
#click="updateActive(index)"
>
{{ title }}
</li>
and the script is
<script lang="ts" setup>
import { ref } from "vue"
const titles = ["title1","title2","title3"]
const active = ref(-1)
function updateActive(val: number) {
active.value = val
}
</script>
If you have several ul => use title instead of index. For example :
<ul>
<div>
Applicants
</div>
<li
v-for="(title, index) in applicantMenuTitles"
:key="index"
:class="{ active: active === title }"
#click="updateActive(title)"
>
{{ title }}
<div
v-if=" active === title "
class="cursor"
/>
</li>
</ul>
<ul>
<div>
Offices
</div>
<li
v-for="(title, index) in officeMenuTitles"
:key="index"
:class="{ active: active === title }"
#click="updateActive(title)"
>
{{ title }}
<div
v-if=" active === title "
class="cursor"
/>
</li>
</ul>
And on script :
...
const active = ref('')
function updateActive(title: string) {
active.value = title
}
I have a parent component in Vue.js which looks like this:
<template>
<ul class="list-group">
<li class="list-group-item" v-for="item in items">
<div class="row">
<div class="col-md-6">
{{ item.title }}
</div>
<div class="col-md-6 text-right">
<a href="#" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-pencil"></span>
</a>
<a href="#" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-link"></span>
</a>
<a href="#" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-remove"></span>
</a>
</div>
<div class="col-md-12">
<preview></preview>
</div>
</div>
</li>
</ul>
</template>
The script:
<script>
import Preview from './Preview.vue';
export default {
data() {
return {
items: '',
template: []
}
},
created() {
this.fetchItems();
this.$on('preview-build', function (child) {
console.log('new preview: ')
console.log(child)
})
},
components: {
Preview
},
methods: {
fetchItems: function () {
var resource = this.$resource('api/preview');
resource.get({}).then((response) => {
this.items = response.body.item;
}, (response) => {
console.log('Error fetching tasks');
}).bind(this);
},
}
}
</script>
The child component "preview" has a template-like structure, for example {{ item.title }} again. The preview is loaded correct but it's not rendered.
I really do not know if it is possible in Vue 2.0 but hopefully someone had the same problem and can help me here.
EDIT (thanks Patrick):
<template>
<textarea rows="20" class="form-control">
{{ template.content }}
</textarea>
</template>
<script>
export default {
data() {
return {
template: [],
}
},
created() {
this.fetchTemplate();
},
methods: {
fetchTemplate: function () {
var resource = this.$resource('api/preview');
resource.get({}).then((response) => {
this.template = response.body.template;
}, (response) => {
console.log('Error fetching template');
}).bind(this);
},
}
}
</script>
This is the Preview.vue content which is similar to the Item.vue content.
As a little explanation:
The template data comes from an database as predefined html content including the {{ item.title }} and some other placeholder. I want this to be rendered with the specific stuff coming from the item.
In Vue.js, components can't directly access data from their parent. If you want preview to be able to render {{ item.title }}, you'll have to pass item down to it as a prop. So in preview.vue, declare it like this:
export default {
...
props: ['item']
}
Then, in your parent component's template, you can v-bind that item prop to something from the parent's items array:
<li class="list-group-item" v-for="item in items">
...
<preview v-bind:item="item"></preview>
...
</li>
Now your preview component has an item that it can render in its template as if it were part of the data object.
I'm sure this one's gonna be extremely easy for you guys. I am trying to make a simple list of posts with the post titles always visible, and when you click a specific post in the list, you get the post's body. I used v-show for that. However, when I click a specific post, the bodies of all of the posts appear, instead of just the one that I clicked.
Here's the template:
<template>
<div class="container">
<h1>My Posts</h1>
<ul class="list-group">
<li class="list-group-item" v-for="post in list">
<div #click="changeShow">
<h4>{{ post.title }}</h4>
<p v-show="show">{{ post.body }}</p>
<span v-show="show" class="label label-primary">ID: {{ post.userId }}</span>
</div>
</li>
</ul>
</div>
And the logic:
<script>
export default{
data(){
return{
msg:'hello vue',
list: [],
show: false
}
},
ready(){
this.fetchPostList();
},
methods:{
fetchPostList: function () {
var root = 'http://jsonplaceholder.typicode.com';
this.$http.get(root + '/posts').then(function (response) {
this.list = response.data;
})
},
changeShow: function () {
this.show = !this.show;
}
}
}
There's a few ways to approach this depending on your needs.
Multiple Open
You can make each post it's own component, that way you can have show be tied to each individual post instead of all of them.
Vue.component('post', {
template: '#post-template',
props: {
post: Object,
},
data() {
return {
show: false,
}
},
methods: {
toggleShow() {
this.show = !this.show
},
},
})
Then you can have use it like this:
<post v-for="post in posts" :post="post"></post>
One Open
If you just want one open you can pass an id as a prop and show it based on that.
Vue.component('post', {
template: '#post-template',
props: {
post: Object,
selectedId: Number,
},
computed: {
show() {
return this.post.id === this.selectedId
},
},
})
Then you can do like
<post :selected-id="selectedId" :post="post" #click="selectedId = post.id"></post>
I cheated in a different way. Just added a show property to each post and toggled that.
new Vue({
el: 'body',
data: {
list: []
},
ready: function() {
this.fetchPostList()
},
methods: {
fetchPostList: function() {
setTimeout(function() {
this.list.push({
title: 'First Post',
body: 'This is the first Post',
userId: 'Joe',
show: false
});
this.list.push({
title: 'Second Post',
body: 'This is the second Post',
userId: 'Joe',
show: false
});
this.list.push({
title: 'Third Post',
body: 'This is the third Post',
userId: 'Joe',
show: false
});
}.bind(this), 2000);
},
changeShow: function(idx) {
this.list[idx].show = !this.list[idx].show;
}
}
});
<link href="//cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div class="container">
<h1>My Posts</h1>
<ul class="list-group">
<li class="list-group-item" v-for="post in list">
<div #click="changeShow($index)">
<h4>{{ post.title }}</h4>
<p v-show="post.show">{{ post.body }}</p>
<span v-show="post.show" class="label label-primary">ID: {{ post.userId }}</span>
</div>
</li>
</ul>
</div>
First of all, the method that I will give an example is not useful, but I am writing to show that there is such a way.
for example
<script>
export default {
data: {
isShow: false,
postUserId: null,
},
};
</script>
<template>
<div class="container">
<h1>My Posts</h1>
<ul class="list-group">
<li class="list-group-item" v-for="post in list">
<div #click="postUserId = post.userId; isShow = !isShow">
<h4>{{ post.title }}</h4>
<p v-show="show">{{ post.body }}</p>
<span v-show="isShow && postUserId == post.userId" class="label label-primary">ID: {{ post.userId }}</span>
</div>
</li>
</ul>
</div>
</template>
I have a login system with the following layout :
<template name="loginLayout">
<div class="container">
<div class="flat-form">
<ul class="tabs">
<li> <a id="login" class="{{#if isRouteActive 'login' }}active{{/if}}" href="{{ pathFor 'login' }}">Login</a> </li>
<li> <a id="registration" class="{{#if isRouteActive 'registration' }}active{{/if}}" href="{{ pathFor 'registration' }}">Register</a> </li>
<li> <a id="resetPassword" class="{{#if isRouteActive 'resetPassword' }}active{{/if}}" href="{{ pathFor 'resetPassword' }}">Reset Password</a> </li>
</ul>
{{> yield }}
</div>
</div> </template>
According to which link a user hits, meteor renders the correct template in the yield field.
This works fine, but when a user logs in, the private page is rendered within this layout which is wrong. I don't understand why this is happening, if I specify the layout only for the login routes.
Routes file :
Router.map( function () {
this.route( 'login',
{
path : '/login',
layoutTemplate: 'loginLayout',
yieldTemplate : 'login',
data : {
appName : "test",
welcomeMessage : "test."
}
}
);
this.route( 'registration',
{
path : '/registration',
layoutTemplate: 'loginLayout',
yieldTemplate:'registration',
data : {}
}
);
this.route( 'resetPassword',
{
path : '/resetPassword',
layoutTemplate: 'loginLayout',
yieldTemplate : 'resetPassword',
data : {}
}
);
this.route('library');
});
var mustBeSignedIn = function(pause) {
if (!(Meteor.user() || Meteor.loggingIn())) {
Router.go('login');
pause();
}
};
Router.onBeforeAction(mustBeSignedIn, {except: ['login', 'registration', 'resetPassword']});
Login.js
Template.login.events({
'submit #login-form' : function(e, t){
e.preventDefault();
// retrieve the input field values
var userName = t.find('#login-email').value
, password = t.find('#login-password').value;
Meteor.loginWithPassword(userName, password, function(err){
if (err) {
console.log("Error when loggin ");
console.log(err.reason);
} else {
console.log("user logged in");
/***** REDIRECT ******/
Router.go('library');
}
});
return false;
}
});
logi.js template :
<template name="login">
<div id="login" class="form-action">
<h1>Login on {{ appName }} </h1>
<p> {{ welcomeMessage }} </p>
<form id="login-form">
<ul>
<li> <input id="login-email" type="text" placeholder="Username" /></li>
<li> <input id="login-password" type="password" placeholder="Password" /></li>
<li> <input id="submit" type="submit" value="Login" class="button" /></li>
</ul>
</form>
</div>
</template>
I have also noticed that when I use the method "go" to redirect a user to a given route, the data field is reset. Whereas, when I write down the url every thing works fine.
Full source
The way it worked for me was to create an additional controller for my major layout and render my header and footer template again with and layoutTemplate, yieldTemplate and action: function(){}.
Here an example route:
Router.route('einstellungen', function() { // settings
this.render('Settings', {
to: 'interfaceContainer'
});
}, {
controller: 'InterfaceController'
});
And only after adding the following code, Router.go('/einstellungen'); works in a way that the entire layout plus yield layouts are rendered.
InterfaceController = RouteController.extend({
layoutTemplate: 'interfaceLayout',
yieldTemplates: {
'Header': {
to: 'interfaceHeader'
},
'Footer': {
to: 'interfaceFooter'
}
},
action: function() {
this.render('Header', {
to: 'interfaceHeader'
});
this.render('Footer', {
to: 'interfaceFooter'
});
}
});
I also added:
Router.configure({
layoutTemplate: 'interfaceLayout',
yieldTemplates: {
'Header': {
to: 'interfaceHeader'
},
'Footer': {
to: 'interfaceFooter'
}
}
});
But this does not seem to have any impact on Router.go();
See also:
https://forums.meteor.com/t/custom-layout-for-a-specific-route-not-displaying-with-iron-router/5758
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#creating-route-controllers