Vue.js second radio button is selected on app load - javascript

I have the following Quiz written using Vue.js where everything works like a charm...
except one thing, which I can't find a solution,
is it my code causing this bug? or I need approach it differently?
Update: now that I have added unique names for each element, as one of the answers suggest, The second menu item is always selected by default on page load... any hint on how to overcome this issue?
<script>
// Create a quiz object with a title and two questions.
// A question has one or more answer, and one or more is valid.
var quiz = {
"title":"Quizorama",
"questions":[
{
"text":"Lalala",
"audio":"TextTo-1-1.mp3",
"responses":[
{
"text":"Incorrect"
},
{
"text":"Incorrect"
},
{
"text":"Correct",
"correct":true
}
]
},
{
"text":"Something",
"audio":"57633709.mp3",
"responses":[
{
"text":"Correct",
"correct":true
},
{
"text":"Incorrect"
},
{
"text":"Incorrect"
}
]
},
{
"text":"Question",
"audio":"57633709.mp3",
"responses":[
{
"text":"Correct",
"correct":true
},
{
"text":"Incorrect"
},
{
"text":"Incorrect"
}
]
}
]
};
</script>
<div class="wrapper" id="page-wrapper">
<div class="centered-content " id="content" tabindex="-1">
<div class="row">
<main class="site-main" id="main">
<div id="app">
<h1>{{ quiz.title }}</h1>
<!-- index is used to check with current question index -->
<div v-for="(question, index) in quiz.questions">
<!-- Hide all questions, show only the one with index === to current question index -->
<transition name="slide-fade">
<div v-show="index === questionIndex">
<h2>{{ question.text }}</h2>
<audio width="450" controls :src="question.audio"></audio>
<ul>
<li v-for="response in question.responses"
v-bind:correctOrNot="response.correct"
v-bind:class="{ active: isActive }">
<label>
  <input type="radio"
v-bind:value="checkResponse(response.correct)"
v-bind:name="nameMethod(index ,response.text,
questionIndex)"
v-model="userResponses[index]"
> {{response.text}}
</label>
</li>
</ul>
<!-- The two navigation buttons -->
<!-- Note: prev is hidden on first question -->
<!-- <button v-if="questionIndex > 0" v-on:click="prev">
otra oportunidad?
</button> -->
<button v-on:click="next">
Next pleeeeease!
</button>
</div>
</transition>
</div>
<transition name="slide-fade">
<div v-show="questionIndex === quiz.questions.length">
<h3>
yer results are da following bro:
</h3>
<p class="puntaje">
{{ score() }}
</p>
</div>
</transition>
</div>
<script>
new Vue({
el: '#app',
data: {
quiz: quiz,
// Store current question index
questionIndex: 0,
// An array initialized with "false" values for each question
// It means: "did the user answered correctly to the question n?" "no".
userResponses: Array(quiz.questions.length).fill(false),
isActive: false,
},
// The view will trigger these methods on click
methods: {
checkResponse: function(response){
let checkResponseValue = response;
if (response == true){
return true;
} else {
return false;
}
},
nameMethod: function(index, responseText, questionIndex){
var index = index;
var questionIndexValue = questionIndex
var responseText = responseText;
var name = index + responseText+'_'+ questionIndexValue;
return name;
},
next: function() {
console.log(this);
this.isActive = true;
setTimeout(() => {
// move to next question
this.questionIndex++;
this.isActive = false;
}, 3000);
},
updateMessage: function () {
this.message = 'updated';
},
// Go to previous question
prev: function() {
this.questionIndex--;
},
editModal: function(id){
console.log(id);
},
// Return "true" count in userResponses
score: function() {
let scorePercent = Math.round(this.userResponses.filter(function(val) { return val }).length * 100 / this.questionIndex);
let newResult;
if(scorePercent == 0 ){
newResult = "you suck , not even one good response mate ";
return newResult
}
  if(scorePercent < 30){
newResult = scorePercent + "% Was Good, so you need to practice more mate";
return newResult
}
if(scorePercent < 70){
newResult = scorePercent + "% yar a ducking star but there is more to improve";
return newResult
}
if(scorePercent == 100){
newResult = scorePercent + "% you are a godlike creature made flesh";
return newResult
}
}
}
});
</script>
<style>
p.puntaje {
font-size: 25px;
color: #333333;
margin-bottom: 40px;
background: #fce373;
padding: 13px;
border-radius: 100px;
text-align: center;
}
main#main {
margin: auto;
}
#app h1 {
font-size: 66px;
font-weight: 900;
color: #b1b1b1;
}
#app h2 {
font-size: 125px;
color: #a282bb;
}
#app ul {
list-style: none;
padding: 0;
}
/* Enter and leave animations can use different */
/* durations and timing functions. */
.slide-fade-enter-active {
transition: all 0.5s ease;
transition-delay: 2s;
}
.slide-fade-leave-active {
transition: all 0.5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
transition: all 0.5s ease;
}
#app button {
background: #00BCD4;
width: 200px;
margin-bottom: 30px;
border-radius: 100px;
font-size: 17px !important;
padding: 7px 17px;
border: none;
}
#app li label {
font-size: 25px;
background: #fff2b6;
border-radius: 100px;
padding-left: 17px;
padding-right: 17px;
margin-top: 16px;
padding-top: 5px;
transition: all 0.5s ease;
}
#app li label:hover{
background: #fce372;
cursor:pointer;
transition: all 0.5s ease;
}
li.active label {
background: #ee734c !important; transition: all 0.5s ease;
}
li[correctOrNot="true"].active label {
background: #9ad18b !important; transition: all 0.5s ease;
}
.slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(50px);
opacity: 0;
transition: all 0.5s ease;
}
</style>

This is a simple HTML problem. You define the radio buttons with the response.correct value, which is undefined for the incorrect options and also identical for both (no matter if it is null or false).
Your output might be like this:
<input type="radio" name="index">Incorrect
<input type="radio" name="index">Incorrect
<input type="radio" name="index" value="true">Correct
If you have a group of radio buttons and several have the same value (or none), these are basical the same input. Try to define a unique value for each radio button with the same name.

Related

Vue 3 app bug: why is this method executed before any click event occurs?

I am building a quiz app with Vue 3 and Bootstrap 4.
I have this method for checking if the clicked answer is the (same as the) correct answer:
checkAnswer(answer) {
return answer == this.results[this.questionCount]["correct_answer"];
}
It should be executed upon clicking a list item, as seen below:
<li v-for="answer in answers" #click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
If the clicked answer is correct, the list item should be added the classes text-white bg-success, otherwise it should be added text-white bg-danger.
const quizApp = {
data() {
return {
questionCount: 0,
results: [
{
question: "The book "The Little Prince" was written by...",
correct_answer: "Antoine de Saint-Exupéry",
incorrect_answers: [
"Miguel de Cervantes Saavedra",
"Jane Austen",
"F. Scott Fitzgerald"
]
},
{
question:
"Which novel by John Grisham was conceived on a road trip to Florida while thinking about stolen books with his wife?",
correct_answer: "Camino Island",
incorrect_answers: ["Rogue Lawyer", "Gray Mountain", "The Litigators"]
},
{
question:
"In Terry Pratchett's Discworld novel 'Wyrd Sisters', which of these are not one of the three main witches?",
correct_answer: "Winny Hathersham",
incorrect_answers: [
"Granny Weatherwax",
"Nanny Ogg",
"Magrat Garlick"
]
}
]
};
},
methods: {
nextQuestion() {
if (this.questionCount < this.results.length - 1) {
this.questionCount++;
}
},
prevQuestion() {
if (this.questionCount >= 1) {
this.questionCount--;
}
},
checkAnswer(answer) {
// check if the clicked anwser is equal to the correct answer
return answer == this.results[this.questionCount]["correct_answer"];
},
shuffle(arr) {
var len = arr.length;
var d = len;
var array = [];
var k, i;
for (i = 0; i < d; i++) {
k = Math.floor(Math.random() * len);
array.push(arr[k]);
arr.splice(k, 1);
len = arr.length;
}
for (i = 0; i < d; i++) {
arr[i] = array[i];
}
return arr;
}
},
computed: {
answers() {
let incorrectAnswers = this.results[this.questionCount][
"incorrect_answers"
];
let correctAnswer = this.results[this.questionCount]["correct_answer"];
// return all answers, shuffled
return this.shuffle(incorrectAnswers.concat(correctAnswer));
}
}
};
Vue.createApp(quizApp).mount("#quiz_app");
#quiz_app {
height: 100vh;
}
.container {
flex: 1;
}
.quetions .card-header {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.quetions .card-footer {
padding-top: 0.7rem;
padding-bottom: 0.7rem;
}
.answers li {
cursor: pointer;
display: block;
padding: 7px 15px;
margin-bottom: 5px;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.1);
background: #fff;
}
.answers li:last-child {
margin-bottom: 0;
}
.answers li:hover {
background: #fafafa;
}
.pager {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
justify-content: space-between;
}
.pager li > a {
display: inline-block;
padding: 5px 10px;
text-align: center;
width: 100px;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 999px;
text-decoration: none !important;
color: #fff;
}
.pager li > a.disabled {
pointer-events: none;
background-color: #9d9d9d !important;
}
.logo {
width: 30px;
}
.nav-item {
width: 100%;
}
.card {
width: 100%;
}
#media (min-width: 768px) {
.nav-item {
width: auto;
}
.card {
width: 67%;
}
}
#media (min-width: 992px) {
.card {
width: 50%;
}
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue#next"></script>
<div id="quiz_app" class="container quetions d-flex align-items-center justify-content-center my-3">
<div v-if="results.length" class="card shadow-sm">
<div class="card-header bg-light h6">
{{results[questionCount]['question']}}
</div>
<div class="card-body">
<ul class="answers list-unstyled m-0">
<li v-for="answer in answers" #click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>
</ul>
</div>
<div class="card-footer bg-white">
<ul class="pager">
<li>Previous</li>
<li class="d-flex align-items-center text-secondary font-weight-bold small">Question {{questionCount + 1}} of {{results.length}}</li>
<li>Next</li>
</ul>
</div>
</div>
</div>
The problem
To my surprise, the checkAnswer(answer) method is executed before (and in the absence of) any click.
Question
What am I doing wrong?
UPDATED
checkAnswer() is invoked immediately if used outside a handler.
maybe this will help, when checkAnswer() is called, store the selected answer selectedAnswer and check if answer is correct isCorrect, and use these 2 states to compare the looped answers.
<li
v-for="answer in answers"
:key="answer"
#click="checkAnswer(answer)"
:class="{
'text-white bg-success' : (selectedAnswer === answer && isCorrect),
'text-white bg-danger' : (selectedAnswer === answer && !isCorrect)
}"
>
{{answer}}
</li>
data() {
return {
isCorrect: false,
selectedAnswer: ''
...
}
},
methods: {
checkAnswer(answer) {
// check if the clicked anwser is equal to the correct answer
this.selectedAnswer = answer
if (answer == this.results[this.questionCount]["correct_answer"]) {
this.isCorrect = true
} else {
this.isCorrect = false
}
},
}
https://jsfiddle.net/renzivan15/fw10q5og/12/
This is executing it before the click:
:class="{'text-white bg-success' : checkAnswer(answer)}".
You'll need to keep the state in a variable for each answer and update it within the method.
And as a side node, it is recommended to use :key for looped elements.
The issue is that the checkAnswer is referenced in the html template so it will execute immediately on render. You might want to try instead setting the class to some computed property instead of the function.
<li v-for="answer in answers" #click="checkAnswer(answer)" :class="{'text-white bg-success' : checkAnswer(answer)}">{{answer}}</li>

Vue.js transition on changing selected element from a list

I have a list of Profiles that open an "edit profile" screen. This screen slided in from the left. When I select a profile, if there is a screen already selected, I want it to slide out first, change the selected profile data and then slide in.
What happens now is: when I first select one element, the screen slides in. When I change the selected element, screen stays and don't slide out and back in.
Here is a gif to show how it's behaving now:
My code is:
Vue Method:
editProfile: function (index){
// this.editingProfile = false;
this.setProfile(index);
this.editingProfile = true;
}
Html View:
<transition name="fade" mode="out-in">
<div v-if="editingProfile" id="edit-profile">
<input placeholder="Profile Name" v-model="synced.profiles[synced.selectedProfile].name">
</div>
</transition>
CSS:
.fade-enter-active, .fade-leave-active {
transition: all .2s;
/* transform:translateX(0); */
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform:translateX(-100%);
}
How do I make it properly slide out and then back in when changing a profile?
I think I was incorrect with my comment. One way you can do this is to leverage :key and a v-if so that you can tell Vue to render a panel if you have selected one and then transition between panels that way. You won't need to have a transition-group then.
The thing is :key is what tells Vue that everything has changed. If you leave it off, Vue tries to recycle as much as it can. See the docs: Transitioning Between Elements
When toggling between elements that have the same tag name, you
must tell Vue that they are distinct elements by giving them unique
key attributes. Otherwise, Vue’s compiler will only replace the
content of the element for efficiency. Even when technically
unnecessary though, it’s considered good practice to always key
multiple items within a <transition> component.
Consider the minimal example below:
const panels = [{
title: "Hello"
},
{
title: "World"
},
{
title: "Foo"
},
{
title: "Bar"
}
];
const app = new Vue({
el: "#app",
data() {
return {
panels,
activePanel: null
};
},
computed: {
titles() {
return this.panels.map(panel => panel.title);
}
},
methods: {
handleTitleClick(idx) {
if (this.activePanel === idx) {
this.activePanel = null;
return;
}
this.activePanel = idx;
}
}
});
body {
margin: 0;
padding: 0;
}
#app {
display: flex;
align-items: stretch;
height: 100vh;
}
#panel-set {
flex: 1 0 70%;
display: flex;
}
#side-panel {
flex: 1 0 30%;
}
.panel {
padding: 1em;
flex: 1 0;
background-color: rgba(0, 0, 0, 0.2);
}
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: transform 500ms ease-in-out;
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateX(-100%);
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<div id="panel-set">
<transition name="slide-fade" mode="out-in">
<div class="panel" v-if="activePanel !== null" :key="activePanel">
<h2>{{panels[activePanel].title}}</h2>
<p>Lorem ipsum</p>
</div>
</transition>
</div>
<div id="side-panel">
<ul>
<li v-for="(title, idx) in titles" #click="handleTitleClick(idx)">{{title}}</li>
</ul>
</div>
</div>
The root cause is v-if="editingProfile" always true after showing one profile in your codes.
One solution is set it to false first, then in this.$nextTick to set it to true again. But you have to put this.editingProfile = true inside one setTimeout and delay time = transition time. Otherwise, slide out effect will be overwritten.
Like below demo:
new Vue({
el: '#emit-example-simple',
data() {
return {
editingProfile: false,
synced : {
profiles: [{'name':'A'}, {'name':'B'}, {'name':'C'}],
selectedProfile: 0
},
}
},
methods: {
editProfile: function (index){
this.editingProfile = !this.editingProfile
this.$nextTick(() => {
setTimeout(()=> {
this.synced.selectedProfile = index
this.editingProfile = true
}, 1200)
})
}
}
})
.fade-enter-active, .fade-leave-active {
transition: all 1.2s;
/* transform:translateX(0); */
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform:translateX(-100%);
border: 1px solid white;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="emit-example-simple">
<button #click="editProfile(0)">Profile 1</button>
<button #click="editProfile(1)">Profile 2</button>
<button #click="editProfile(2)">Profile 3</button>
<transition name="fade" mode="out-in">
<div v-if="editingProfile" id="edit-profile">
<input style="border: 5px solid red;" placeholder="Profile Name" v-model="synced.profiles[synced.selectedProfile].name">
</div>
</transition>
</div>
Or you can consider to use Group transition like below simple demo:
new Vue({
el: '#emit-example-simple',
data() {
return {
editingProfile: false,
profileContainers: [true, false],
synced : {
profiles: [{'name':'A'}, {'name':'B'}, {'name':'C'}],
selectedProfile: 0
},
}
},
methods: {
editProfile: function (index){
this.synced.selectedProfile = index
this.profileContainers = this.profileContainers.map((x)=>!x)
}
}
})
.list-items-enter-active {
transition: all 1.2s;
}
.list-items-leave-active {
transition: all 1.2s;
}
.list-items-enter, .list-items-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform:translateX(-100%);
border: 1px solid white;
}
.list-item {
display: inline-block;
border: 6px solid red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="emit-example-simple">
<button #click="editProfile(0)">Profile 1</button>
<button #click="editProfile(1)">Profile 2</button>
<button #click="editProfile(2)">Profile 3</button>
<transition-group name="list-items" tag="p">
<div v-for="(item, index) in profileContainers" :key="index" v-if="item">
<input style="border: 5px solid red;" placeholder="Profile Name" v-model="synced.profiles[synced.selectedProfile].name">
</div>
</transition-group>
</div>

How to animate list items in Vue when one is removed

i have a vertical list of items, each of which can be removed. I put my items inside a transition-group and created simple opacity and transform transitions for them. The transitions on the removed elements work as expected, however if I remove an element which is not placed at the bottom, the ones beneath just jump up and take its place without any transition. I Can't find a way to target this behaviour.
All I want is just that the elements below slide up smoothly.
Is there any way to achieve this effect by using css transitipms and Vue's animation hooks?
Here is a demo: https://jsfiddle.net/gcp18nq0/
Template:
<div id="app">
<div class="form">
<label for="name">Name</label>
<input type="text" id="name" v-model="name">
<button #click="addPlayer">Add player</button>
</div>
<div class="players">
<transition-group name="player">
<div class="panel" v-for="player in players" :key="player.id">
<h2>
{{ player.name}}
<span class="remove" #click="removePlayer(player.id)">Remove</span>
</h2>
</div>
</transition-group>
</div>
</div>
Script:
data() {
return {
name: "",
players: [
{id: 1, name: 'Player1'},
{id: 2, name: 'Player2'},
{id: 3, name: 'Player3'},
]
}
},
methods: {
addPlayer: function () {
//,,,,
},
removePlayer: function (playerId) {
//...
}
}
});
CSS
.form {
margin:0 auto;
width:400px;
}
.panel {
width: 400px;
margin: 10px auto;
overflow: hidden;
border: 1px solid;
text-align: center;
}
.remove {
float: right;
cursor: pointer;
text-decoration: underline;
font-size: 12px;
vertical-align: bottom
}
.player-enter,
.player-leave-to
/* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
.player-enter {
transform: translateY(30%);
}
.player-leave-to {
transform: translateX(30%);
}
.player-enter-active,
.player-leave-active {
transition: all 1.5s;
}
.player-move {
transition: all 1.5s;
}
The only working way I found was by adding position:absolute on "player-leave-active" state but since the element collapses it changes its vertical position, which is not the desired effect. I also tried changing the height but there the elements below still jump up a bit after the height is set to 0.
Im sure that this can be achieved easily with jQuery but i believe that there should be a way to do it without js.
Thank you in advance!
p.s. its my first post here, so i hope that it was explained clearly enough.
So I made some small tweaks to your fiddle: https://jsfiddle.net/gcp18nq0/1/ and hopefully that is what you looking for.
The most important change was setting display: inline-block on the .panel class, according to the Vue documentation:
One important note is that these FLIP transitions do not work with
elements set to display: inline. As an alternative, you can use
display: inline-block or place elements in a flex context.
new Vue({
el: "#app",
data() {
return {
name: "",
players: [{
id: 1,
name: 'Batman'
},
{
id: 2,
name: 'Robin'
},
{
id: 3,
name: 'Superman'
},
{
id: 4,
name: 'Spiderman'
},
]
}
},
methods: {
addPlayer: function() {
const newPlayer = {
id: this.players.length + 1,
name: this.name,
};
this.players.push(newPlayer);
},
deletePlayer: function(playerId) {
let playerToRemove = this.players.find((player) => {
return player.id === playerId;
});
let playerIndex = this.players.indexOf(playerToRemove);
this.players.splice(playerIndex, 1);
}
}
});
.form {
margin: 0 auto;
width: 400px;
}
.panel {
width: 400px;
margin: 6px auto;
overflow: hidden;
border: 1px solid;
text-align: center;
transition: all 1s;
display: inline-block;
}
.players {
position: relative;
text-align: center;
}
.remove {
float: right;
cursor: pointer;
text-decoration: underline;
font-size: 12px;
vertical-align: bottom
}
.player-enter,
.player-leave-to {
opacity: 0;
}
.player-enter {
transform: translateY(30%);
}
.player-leave-to {
transform: translateX(300%);
}
.player-leave-active {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div class="form">
<label for="name">Name</label>
<input type="text" id="name" v-model="name">
<button #click="addPlayer">Add player</button>
</div>
<div class="players">
<transition-group name="player" tag="div">
<div class="panel" v-for="player in players" :key="player.id">
<h2>
{{ player.name}}
<span class="remove" #click="deletePlayer(player.id)">Remove</span>
</h2>
</div>
</transition-group>
</div>
</div>

How to animate the sorting of a list with Vue.js

I’m trying to animate the sorting of a list with Vue.js, but not all items are animated. Do you know why? And how to make it work?
new Vue({
el: '#app',
data: {
reverse: 1,
items: [
{ name: 'Foo' },
{ name: 'Bar' },
{ name: 'Baz' },
{ name: 'Qux' }
]
}
})
.moving-item {
transition: all 1s ease;
-webkit-transition: all 1s ease;
}
ul {
list-style-type: none;
padding: 0;
position: relative;
}
li {
position: absolute;
border: 1px solid #42b983;
height: 20px;
width: 150px;
padding: 5px;
margin-bottom: 5px;
color: #42b983;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0-alpha.2/vue.min.js"></script>
<div id="app">
<button on-click="reverse = Math.abs(reverse-1)">
<span v-if="reverse == 0">△</span>
<span v-if="reverse == 1">▽</span> Order
</button>
<ul>
<li class="moving-item" v-for="item in items | orderBy 'name' reverse" bind-style="{ top: ($index * 35) + 'px'}">{{ item.name }}</li>
</ul>
</div>
I believe the problem is that only one of the elements is remaining in the DOM during the sort. The other three are being removed and reinserted to satisfy the new ordering – but as a result they are not triggering an animation.
Typically, animation is done using the Vue transition system (http://vuejs.org/guide/transitions.html). However, the same basic problem of deletion and reinsertion not tracking position state will occur using that technique. Usually, items are animated independent of their previous and new positions (like fade-out in their old position and fade-in in their new one).
If you really need to animate from the the old position to the new one, I think you would need to write your own Javascript transition that remembers the previous position of each item before it is removed and animates it to the new position when it is inserted.
There is an example here which should be a good starting point: http://vuejs.org/guide/transitions.html#JavaScript_Only_Transitions
Another option is to not sort by a filter and do it in javascript instead (so that the v-for only renders once). Then target your bind-style against a new index parameter on your items like this:
new Vue({
el: '#app',
data: {
reverse: 1,
items: [
{ name: 'Foo', position: 0 },
{ name: 'Bar', position: 1 },
{ name: 'Baz', position: 2 },
{ name: 'Qux', position: 3 }
]
},
methods: {
changeOrder: function (event) {
var self = this;
self.reverse = self.reverse * -1
var newItems = self.items.slice().sort(function (a, b) {
var result;
if (a.name < b.name) {
result = 1
}
else if (a.name > b.name) {
result = -1
}
else {
result = 0
}
return result * self.reverse
})
newItems.forEach(function (item, index) {
item.position = index;
});
}
}
})
.moving-item {
transition: all 1s ease;
-webkit-transition: all 1s ease;
}
ul {
list-style-type: none;
padding: 0;
position: relative;
}
li {
position: absolute;
border: 1px solid #42b983;
height: 20px;
width: 150px;
padding: 5px;
margin-bottom: 5px;
color: #42b983;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0-alpha.2/vue.min.js"></script>
<div id="app">
<button on-click="changeOrder">
<span v-if="reverse == -1">△</span>
<span v-if="reverse == 1">▽</span> Order
</button>
<ul>
<li class="moving-item" v-for="item in items" bind-style="{ top: (item.position * 35) + 'px'}">{{ item.name }}</li>
</ul>
</div>
V2 has this built in: https://v2.vuejs.org/v2/guide/transitions.html#Transition-Modes
Check out "List Move Transitions" and the example with the shuffle button.

js fails to incorporate into php page

I have the following js code:
<style>
<!--
body
{
font-family: "century schoolbook", serif;
font-size: 20px;
}
.hidden
{
display: none;
color: #000;
background: #FFFFFF;
}
.unhide
{
display: block;
color: #000;
}
a.unhide
{
text-decoration: none;
}
a.unhide:hover
{
text-decoration: underline;
}
.unhide:hover
{
background: #FFE5B4;
padding: 3px 8px;
display: table-row;
transition: background .25s ease-in-out;
-moz-transition: background .25s ease-in-out;
-webkit-transition: background .25s ease-in-out;
}
-->
</style>
<script type="text/javascript">
function unhide(divID) {
var item = document.getElementById(divID);
if (item) {
item.className = (item.className == 'hidden') ? 'unhide' : 'hidden';
}
}
</script>
...
<div class="conBoxcities">
<class id="info">
<a href="javascript:unhide('cityname');" class="unhide">
This is really a js link with a city name. Clicking brings down information about
that city.
</a>
</class>
<class id="info">
<div id="cityname" class="hidden">
This is where the content of the above link appears. It is just an info blurb,
basically.
This js script works here. On the other page it does not. I believe I messed up
somewhere in my classes...please help
</div>
</class>
</div>
The above code works perfectly well on a "bare" php page
When I incorporate it into my main page, the js links no longer function. I believe i may have a mistake in my arrangement (admittedly, classes and ids still confuse me).
This is the page where the link appears, but does not work.
Please help...
The problem is that in your "live" page you have redefined the function unhide with this code:
function unhide(divID) {
var item = document.getElementsByClassName(divID)[0];
console.log(item);
console.log(item.className == divID + ' hide');
if (item) {
item.className = (item.className == divID + ' hide') ? divID + ' unhide' : divID + ' hide';
}
}
If you remove, our comment out that code, everything works as expected.

Categories