How to access a function variable as a data property in Vue - vue.js

I have this accordion with three arrows. When you click on the arrow it should transform 180deg. I want to have a function that takes the parameter set in the function and tells the data property to change to zero.
I've tried
let vm = this;
vm.$data.arrowOne = 0;
And this[arrow] = 0,
And this.arrow = 0
And it's not working.
Here is my code.
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li>
<a #click="rotate(arrowOne)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowOne + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowTwo)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowTwo + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
<li>
<a #click="rotate(arrowThree)" class="uk-accordion-title" href="#">Headline
<img :style="{ transform: 'rotate('+ arrowThree + 'deg)'}" src="IMGSRC"></a>
<div class="uk-accordion-content">
<p>TEXT</p>
</div>
</li>
</ul>
</div>
And VUE
new Vue({
el: '.uk-accordion-ekstra',
data: {
arrowOne: 180,
arrowTwo: 180,
arrowThree: 180
},
methods: {
arrow: function(arrow) {
if(this.arrow = 0) {
return this.arrow = 180;
}
}
}
});
I have succesfully managed to do it with a switch. But it aren't as beautiful. And im unsure of what i don't get. So help is appreciated.

You have a structural and a scope problem.
You are referencing this.arrow in a method called arrow, and you pass an argument called arrow - try to differentiate with the names, or you can get messed up
You are repeating stuff that you don't need to. Vue is great for creating components for the smallest of elements - you can use it to make your code shorter, more readable and more effective.
The snippet below makes everything easier:
you have one arrow per component, and if you solve the rotation problem in one place, then it's solved everywhere, and the arrow referenced always connects to the component you edit
you can encapsulate your arrow rotation into one component - no need to "litter" the Vue instance with such small animation
I added #click.prevent that's like preventDefault(), so there won't be a jump to the top when you click an <a></a>
Vue.component('accordion', {
props: ['title', 'text'],
template: '<div><a #click.prevent="rotateArrow()" class="uk-accordion-title" href="#">{{ title }} <i class="fa fa-arrow-circle-o-up" :style="`transform: rotate(${rotation}deg)`"></i></a><div class="uk-accordion-content"><p>{{ text }}</p></div></div>',
data() {
return {
rotation: 0
}
},
methods: {
rotateArrow() {
this.rotation = !this.rotation ? 180 : 0
}
}
})
new Vue({
el: '.uk-accordion-ekstra',
data: {
accordionItems: [{
title: 'Headline 1',
text: 'Text 1'
},
{
title: 'Headline 2',
text: 'Text 2'
},
{
title: 'Headline 3',
text: 'Text 3'
},
]
}
});
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="uk-accordion-ekstra">
<ul uk-accordion="multiple: true">
<li v-for="accordionItem in accordionItems" :key="accordionItem.title">
<accordion :title="accordionItem.title" :text="accordionItem.text"></accordion>
</li>
</ul>
</div>

Related

GSAP animation not working with $ref in Nuxt

I am having an issue with a GSAP animation in a Nuxt build. The animation is to start when the
overlay navigation panel is brought into view with v-if="isMenuOpen". The nav works and shows as expected, but I want to animate the different items within the nav. The problem is GSAP isn't finding the items to animate. I've tried different approaches:
using TL.fromTo('.nav__panel-nav ul li a', { but this returns the error GSAP target .nav__panel-nav ul li a not found.
using TL.fromTo(this.$refs.li, { on both the components and wrapping HTML li element, which returns the error GSAP target undefined not found.
I'm new to working with Vue/Nuxt so i'm not sure o the best way to rectify the issue. Any direction on how to get this working, or a better approach would be appreciated.
<template>
<transition name="card">
<div class="nav" v-if="isMenuOpen">
<div class="nav__panel">
<div class="nav__panel-inner">
<div class="nav__panel-contact">
<div class="h-3">Contact Us</div>
<ul>
<li>
xxx
</li>
</ul>
<div class="nav__panel-social">
<SocialLinks />
</div>
</div>
</div>
</div>
<div class="nav__panel">
<div class="nav__panel-inner">
<nav class="nav__panel-nav">
<ul>
<li ref="li">
<NuxtLink to="/" ref="test">Home</NuxtLink>
</li>
<li>
<NuxtLink to="/about-us">About Us</NuxtLink>
</li>
<li>
<NuxtLink to="/our-expertise">Our Expertise</NuxtLink>
</li>
<li>
<NuxtLink to="/contact-us">Contact Us</NuxtLink>
</li>
</ul>
</nav>
</div>
</div>
</div>
</transition>
</template>
<script>
import { gsap } from "gsap";
export default {
computed: {
isMenuOpen() {
return this.$store.getters['navState'];
}
},
watch: {
isMenuOpen(val) {
if (val) {
document.getElementsByTagName('body')[0].classList.add('no-scroll');
this.animateNavIn();
} else {
document.getElementsByTagName('body')[0].classList.remove('no-scroll');
this.animateNavOut();
}
}
},
methods: {
animateNavIn() {
// https://blog.logrocket.com/animating-vue-with-greensock/
console.log('animate in');
const TL = gsap.timeline();
// TL.fromTo('.nav__panel-nav ul li a', {
TL.fromTo(this.$refs.li, {
duration: 1,
autoAlpha: 1,
y: '15px',
stagger: 0.05,
ease: "Power2.out"
},
{
autoAlpha: 1,
y: 0,
})
.to('.nav__panel-contact .h-3, li, .social__title, .social__links', {
duration: 1,
autoAlpha: 1,
y: 0,
stagger: 0.05,
ease: "Power2.out"
}, '-=.9');
},
animateNavOut() {
console.log('animate out');
// const TL = gsap.timeline();
// TL.to('.nav__panel-nav ul li a', {
// duration: 1,
// autoAlpha: 1,
// y: 0,
// stagger: 0.05,
// ease: "Power2.out"
// });
}
},
}
</script>
I've worked out what part of the issue is. The problem was with using v-if to render the nav. By using this, the elements I was trying to target weren't in the DOM so they couldn't be found. My solution was to use v-show instead. The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.
I am now able to target the HTML a tags using GSAP. I'm still not sure why $refs wouldn't work though...

Removing specific object from array keeps removing last item

Here is what I have and I will explain it as much as I can:
I have a modal inside my HTML code as shown below:
<div id="favorites-modal-edit" class="modal">
<div class="modal-background"></div>
<div class="modal-card px-4">
<header class="modal-card-head">
<p class="modal-card-title">Favorites</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div id="favorites-modal-edit-wrapper" class="columns is-multiline buttons">
<favorites-edit-component v-for="(favorite, index) in favorites_list" :key="favorite.id" :favorite="favorite" />
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button" #click="addItem">
Add Item
</button>
</footer>
</div>
</div>
The id="favorites-modal-edit" is the Vue.js app, then I have the <favorites-edit-component /> vue.js component.
Here is the JS code that I have:
I have my favorites_list generated which is an array of objects as shown below:
const favorites_list = [
{
id: 1,
name: 'Horse',
url: 'www.example.com',
},
{
id: 2,
name: 'Sheep',
url: 'www.example2.com',
},
{
id: 3,
name: 'Octopus',
url: 'www.example2.com',
},
{
id: 4,
name: 'Deer',
url: 'www.example2.com',
},
{
id: 5,
name: 'Hamster',
url: 'www.example2.com',
},
];
Then, I have my vue.js component, which is the favorites-edit-component that takes in the #click="removeItem(this.index) which is coming back as undefined on the index.
Vue.component('favorites-edit-component', {
template: `
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ favorite.name }}</span>
<span class="icon is-small favorite-delete" #click="removeItem(this.index)">
<i class="fas fa-times"></i>
</span>
</button>
</div>
`,
props: {
favorite: Object
},
methods: {
removeItem: function(index) {
this.$parent.removeItem(index);
},
}
});
Then I have the vue.js app that is the parent as shown below:
new Vue({
el: '#favorites-modal-edit',
// Return the data in a function instead of a single object
data: function() {
return {
favorites_list
};
},
methods: {
addItem: function() {
console.log('Added item');
},
removeItem: function(index) {
console.log(index);
console.log(this.favorites_list);
this.favorites_list.splice(this.favorites_list.indexOf(index), 1);
},
},
});
The problem:
For some reason, each time I go to delete a item from the list, it's deleting the last item in the list and I don't know why it's doing it, check out what is happening:
This is the guide that I am following:
How to remove an item from an array in Vue.js
The item keeps coming back as undefined each time the remoteItem() function is triggered as shown below:
All help is appreciated!
There is an error in your favorites-edit-component template, actually in vue template, when you want to use prop, data, computed, mehods,..., dont't use this
=> there is an error here: #click="removeItem(this.index)"
=> in addition, where is index declared ? data ? prop ?
you're calling this.$parent.removeItem(index); then in removeItem you're doing this.favorites_list.splice(this.favorites_list.indexOf(index), 1); this means that you want to remove the value equal to index in you array no the value positioned at the index
=> this.favorites_list[index] != this.favorites_list[this.favorites_list.indexOf(index)]
In addition, I would suggest you to modify the favorites-edit-component component to use event so it can be more reusable:
favorites-edit-component:
<template>
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ favorite.name }}</span>
<span class="icon is-small favorite-delete" #click="$emit('removeItem', favorite.id)">
<i class="fas fa-times"></i>
</span>
</button>
</div>
</template>
and in the parent component:
<template>
...
<div id="favorites-modal-edit-wrapper" class="columns is-multiline buttons">
<favorites-edit-component
v-for="favorite in favorites_list"
:key="favorite.id"
:favorite="favorite"
#removeItem="removeItem($event)"
/>
</div>
...
</template>
<script>
export default {
data: function () {
return {
favorites_list: [],
};
},
methods: {
...
removeItem(id) {
this.favorites_list = this.favorites_list.filter((favorite) => favorite.id !== id);
}
...
},
};
I would restructure your code a bit.
In your favorites-edit-component
change your removeItem method to be
removeItem() {
this.$emit('delete');
},
Then, where you are using your component (in the template of the parent)
Add an event catcher to catch the emitted "delete" event from the child.
<favorites-edit-component v-for="(favorite, index) in favorites_list" :key="favorite.id" :favorite="favorite" #delete="removeItem(index)"/>
The problem you have right now, is that you are trying to refer to "this.index" inside your child component, but the child component does not know what index it is being rendered as, unless you specifically pass it down to the child as a prop.
Also, if you pass the index down as a prop, you must refer to it as "index" and not "this.index" while in the template.

Vuejs multiple active buttons

I'm trying to create a list where every list item contains a button and i want a user to be able to click multiple button. I'm generating my list like so:
<ul>
<li v-for="item in items" :key="item.id">
<button type="button">{{item.title}}</button>
</li>
</ul>
but the problem with my code is whenever a user click a button, it turns the rest of the buttons to "unclicked". been trying to play with the focus and active stats but even with just css i cant get to enable multiple select .
i did manage to change the look of the current selected button:
button:focus {
outline: none;
background-color: #6acddf;
color: #fff;
}
any idea how can i allow multiple buttons to be clicked?
to make things a bit clearer, i am going to create an AJAX call later and pass the item.id of each item where it's button is clicked
I would much rather avoid changing the data structure if possible
Well you have to store somewhere that you clicked on the clicked item.
If you can't edit the items array then you can always create a new one, like isClicked where you store those values.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'baz'
}
],
isClicked: []
},
beforeMount() {
// set all values to false
this.items.forEach((item, index) => this.$set(this.isClicked, index, false))
},
methods: {
clicked(index) {
// toggle the active class
this.$set(this.isClicked, index, !this.isClicked[index])
}
}
})
.active {
background: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index">
<button #click="clicked(index)" :class="{'active': isClicked[index]}">{{item.title}}</button>
</div>
</div>
Or you can use vuex for storing those values.
However you can just use Vues event to manipulate the classList property, like:
new Vue({
el: '#app',
data: {
items: [1, 2, 3, 4, 5, 6, 7]
}
})
.active {
color: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-for="i in items" #click="e => e.target.classList.toggle('active')">{{ i }}</button>
</div>
But it doesn't feel right, IMHO.
Also you can use cookies or localStorage to store those states. So it's really up to you.
Use id attribute for list items to make it unique.
<ul>
<li v-for="item in items" :key="item.id" :id="item.id">
<button type="button" #click="doThis">{{item.title}}</button>
</li>
</ul>
new Vue({
el: '#app',
data: {},
methods: {
doThis() {
// Use this to access current object
}
}
});

Vue alternate classes in v-for

I have an array (history) that is being pushed two items every time a button is pressed. Those two items will need to have different css styles when printed.
HTML
<ul class="info">
<li :class="{ 'style-one' : toggle, 'style-two' : toggle }" v-for="item in history">{{item}}</li>
</ul>
JS (Vue)
methods: {
attack: function() {
this.history.unshift(this.playerDamaged);
this.history.unshift(this.monsterDamaged);
}
The problem with this is there is no way to change the truthiness of toggle during the loop. Is there a better way to approach this?
SOLUTION 1 :
You can use this code :
<ul class="info">
<li v-for="item in history" :key="item"
:class="{ 'style-one' : item.isPlayer, 'style-two' : !item.isPlayer }"
>
{{ item.text }}
</li>
</ul>
methods: {
attack: function() {
this.history.unshift({ text: this.playerDamaged, isPlayer: true });
this.history.unshift({ text: this.monsterDamaged, isPlayer: false });
}
UPDATED - SOLUTION 2 [No use of objects] :
You can use an other solution with no objects :
<ul class="info">
<li v-for="(item, index) in history" :key="item"
:class="'style-' + ((index % numberOfPlayers) + 1)"
>
{{ item }}
</li>
</ul>
//This part don't have to deal with Array of Objects :
methods: {
attack: function() {
this.history.unshift( this.playerDamaged );
this.history.unshift( this.monsterDamaged );
},
computed: {
numberOfPlayers: function() {
return 2;
}
}
If you want to add a player (ex: monster 2) you have to update the computed numberOfPlayers to 3 (or better : listOfPlayers.length if you have) and create a class ".style-3".
Code example :
new Vue({
el: "#app",
data: function() {
return {
myArray: ['player attack', 'monster attack','player attack', 'monster attack']
}
},
computed: {
numberOfPlayers: function() {
return 2;
}
}
});
.style-1 {
color: blue;
}
.style-2 {
color: red;
}
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<div v-for="(item, index) in myArray" :key="item"
:class="'style-' + ((index % numberOfPlayers) + 1)"
>
{{ item }}
</div>
</div>

displaying models on different div ember.js

In Ember.js, what is the best way to display a models properties?
Say my model is 'products' so I have a product list, when I click on an item on that list I want the details displayed in another div and not override that view.
How can I do this, below if my code.
<script type="text/x-handlebars">
{{ view App.ListReleasesView }}
</script>
<script type="text/x-handlebars">
{{ view App.ReleaseDataView }}
</script>
App.ListReleasesView = Ember.View.extend({
templateName: 'app/templates/releases/list',
releasesBinding: 'App.releasesController',
showNew: function() {
this.set('isNewVisible', true);
},
hideNew: function() {
this.set('isNewVisible', false);
},
refreshListing: function() {
App.releasesController.findAll();
}
});
App.selectedReleaseController = Ember.Object.create({
release: null
});
list template:
<ul>
{{#each releases}}
{{view App.ShowReleaseView releaseBinding="this"}}
{{/each}}
</ul>
{{#if isNewVisible}}
{{view App.NewReleaseView}}
{{/if}}
<div class="commands">
<a href="#" {{action "showNew"}}>New Release</a>
<a href="#" {{action "refreshListing"}}>Refresh Listing</a>
</div>
App.ShowReleaseView = Ember.View.extend({
templateName: 'app/templates/releases/show',
//classNames: ['show-release'],
classNameBindings: ['isSelected'],
// tagName: 'tr',
doubleClick: function() {
// this.showEdit();
// this.showRelease();
var release = this.get("release");
App.selectedReleaseController.set("release", release);
},
isSelected: function(){
var selectedItem = App.selectedReleaseController.get("release");
release = this.get("content");
if (release === selectedItem) {return true; }
}.property('App.selectedReleaseController.release')
show template:
{{#if App.selectedReleaseController.release}}
{{view App.ReleaseDataView}}
{{else}}
{{release.name}}
{{/if}}
App.ReleaseDataView = Ember.View.extend({
templateName: 'app/templates/releases/releaseData',
releaseBinding: 'App.selectedReleaseController.release',
// classNames: ['release'],
});
release template:
{{#if isSelected}}
<div class="name">
{{editable "release.name"}} {{editable "release.description"}}
</div>
{{/if}}
You'll want to have a simple controller whose job will be managing the selection state.
App.selectedReleaseController = Ember.Object.create({
content: null
});
Then you'll have another view, for the details, which is bound to that controller.
{{view App.ReleaseDetailsView releaseBinding="App.selectedReleaseController.content"}}