0

I am working a study project on vue-cli and even though it is a simple project, I am trying to apply some stuff I've been learning.

I have a header with two routes:

  • Portfolio
  • Stocks

I have set a simple fade animation once entering into any of these two routes.

There are two issues here, and I think both are related. I will get into it after posting the code blocks as per listed below.

I will post my code below:

Header.vue (this is my header)

<template>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <router-link to="/" class="navbar-brand"><a>Stock Trade</a></router-link>
        <div class="d-inline" @click="toPortfolio">
            <ul class="navbar-nav mr-auto">
                <router-link :to="{ name: 'portfolio'}"
                             tag="li" class="nav-item nav-link"><a
                             class="text-decoration-none">Portfolio</a></router-link>
            </ul>
        </div>
        <div class="collapse navbar-collapse" @click="toStocks">
            <ul class="navbar-nav mr-auto">
                <router-link :to="{name: 'stocks'}"
                             tag="li" class="nav-item nav-link">
                    <a class="text-decoration-none">Stocks</a>
                </router-link>
            </ul>
        </div>
        <div class="collapse navbar-collapse end-day">
            <div class="navbar-nav ml-auto">
                <li class="nav-item nav-link">End Day</li>
            </div>
        </div>
        <div class="d-inline" @click="isDropping = !isDropping">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle"
                       href="#"
                       role="button"
                       data-toggle="dropdown"
                       aria-haspopup="true"
                       aria-expanded="false">
                        Save & Load <span class="dropdown-toggle-no-caret"></span>
                    </a>
                    <!-- I must understand why it does not work below-->
                    <div class="dropdown-menu"
                         :class="{show: isDropping}"
                         aria-labelledby="navbarDropdown">
                        <a class="dropdown-item " href="#">Save Data</a>
                        <a class="dropdown-item" href="#">Load Data</a>
                    </div>
                </li>
            </ul>
        </div>
        <div>
            <!-- Got to add a Getter here instead so "DRY" does not happen with the "EUR" assignment-->
            <strong class="navbar-text" style="color: dimgray">Funds: {{$store.state.funds}}</strong>
        </div>
    </nav>
</template>
<script>
    import {mapActions} from 'vuex'
    import * as types from '/Users/Dev/WebstormProjects/the-trader-app/src/store/types.js'
    export default {
        data() {
            return {
                isDropping: false
            }
        },
        methods: {
            ...mapActions({
                toPortfolio: types.COMMIT_TO_PORTFOLIO,
                toStocks: types.COMMIT_TO_STOCKS
            })
        }
    }

</script>
<style scoped>
    a {
        color: dimgray;
    }

    a:hover{
        color: black !important;
    }

    .end-day {
        cursor: pointer;
    }
</style>

Stocks.vue (this is where the stocks would be displayed)

<template>
    <div class="d-flex flex-wrap justify-content-sm-around">
        <transition name="fade">
            <div class="wrapper" v-if="showStocks">
                <div class="stock-title">
                    <h4 class="box-headers">BMW</h4>
                    <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
                </div>
                <div class="stock-content">
                    <input type="text" class="mr-auto" placeholder="How many shares?">
                    <button>Buy</button>
                </div>
            </div>
        </transition>
        <transition name="fade">
            <div class="wrapper" v-if="showStocks">
                <div class="stock-title">
                    <h4 class="box-headers">Apple</h4>
                    <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
                </div>
                <div class="stock-content">
                    <input type="text" class="mr-auto" placeholder="How many shares?">
                    <button>Buy</button>
                </div>
            </div>
        </transition>
        <transition name="fade">
            <div class="wrapper" v-if="showStocks">
                <div class="stock-title">
                    <h4 class="box-headers">Facebook</h4>
                    <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
                </div>
                <div class="stock-content">
                    <input type="text" class="mr-auto" placeholder="How many shares?">
                    <button>Buy</button>
                </div>
            </div>
        </transition>
        <transition name="fade">
            <div class="wrapper" v-if="showStocks">
                <div class="stock-title">
                    <h4 class="box-headers">Google</h4>
                    <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
                </div>
                <div class="stock-content">
                    <input type="text" class="mr-auto" placeholder="How many shares?">
                    <button>Buy</button>
                </div>
            </div>
        </transition>
    </div>
</template>
<script>
    import {mapGetters} from 'vuex';
    import * as types from '/Users/Dev/WebstormProjects/the-trader-app/src/store/types.js';

    export default {
        computed: {
            ...mapGetters({
                showStocks: types.GET_STOCKS
            })
        }
    }
</script>
<style scoped>

    .wrapper {
        border: solid lightgray 1px;
        width: 500px;
        margin-top: 20px;
        border-radius: 5px;
        box-shadow: 3px 3px 5px 6px #ccc;
    }

    .stock-title {
        background-color: lightskyblue;
        color: royalblue;
        display: flex;
        flex-wrap: wrap;
        padding: 7px 0 0 20px;
    }

    .box-headers {
        display: inline-block;
        margin: 0;
        padding: 10px -1px 10px 10px;
    }

    p {
        align-self: flex-end;
    }

    .stock-content {
        display: flex;
    }

    input {
        margin: 10px;
        padding: 10px;
    }

    button {
        background-color: royalblue;
        color: white;
        padding: 0 20px;
        border-radius: 5px;
        justify-self: center;
        margin: 10px;
    }

    ::placeholder {
        font-size: 15px;
    }

    .fade-enter {
        opacity: 0;
    }

    .fade-enter-active {
        transition: opacity 2s;
    }

    .fade-leave-active {
        transition: opacity 2s;
        opacity: 0;
    }


</style>

Portfolio.vue

<template>
    <div class="d-flex flex-wrap justify-content-sm-around">
        <transition name="fade">
        <div class="wrapper" v-if="showPortfolio">
            <div class="stock-title">
                <h4 class="box-headers">BMW</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
        <transition name="fade">
        <div class="wrapper" v-if="showPortfolio">
            <div class="stock-title">
                <h4 class="box-headers">Apple</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
        <transition name="fade">
        <div class="wrapper" v-if="showPortfolio">
            <div class="stock-title">
                <h4 class="box-headers">Facebook</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
        <transition name="fade">
        <div class="wrapper" v-if="showPortfolio">
            <div class="stock-title">
                <h4 class="box-headers">Google</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
    </div>
</template>
<script>
    import {mapGetters} from 'vuex';
    import * as types from '/Users/Dev/WebstormProjects/the-trader-app/src/store/types.js';

    export default {
        computed: {
            ...mapGetters({
                showPortfolio: types.GET_PORTFOLIO
            })
        }
    }
</script>

<style scoped>

    .wrapper {
        border: solid lightgray 1px;
        width: 500px;
        margin-top: 20px;
        border-radius: 5px;
        box-shadow: 3px 3px 5px 6px #ccc
    }

    .stock-title {
        background-color: #ffbf00;
        color: indianred;
        display: flex;
        flex-wrap: wrap;
        padding: 7px 0 0 20px;
    }

    .box-headers {
        display: inline-block;
        vertical-align: baseline;
        margin: 0;
        padding: 10px -1px 10px 10px;
    }

    .stock-content {
        display: flex;
    }

    p {
        align-self: flex-end;
    }

    input {
        margin: 10px;
        padding: 10px;
    }

    button {
        background-color: indianred;
        color: white;
        padding: 0 20px;
        border-radius: 5px;
        justify-self: center;
        margin: 10px;
    }

    ::placeholder {
        font-size: 15px;
    }

    .fade-enter {
        opacity: 0;
    }

    .fade-enter-active {
        transition: opacity 2s;
    }

    .fade-leave-active {
        transition: opacity 2s;
        opacity: 0;
    }

main.js

import Vue from 'vue'
import App from './App.vue'
import VueRouter from "vue-router";
import 'bootstrap/dist/css/bootstrap.css'
import BootstrapVue from 'bootstrap-vue'
import {store} from "./store/store";
import {routes} from "./routes";

Vue.use(BootstrapVue);
Vue.use(VueRouter);

export const router = new VueRouter({
  routes,
  mode: 'history',
  scrollBehavior(to, from, savedPosition) {
    if(savedPosition) {
      return savedPosition
    }
    if (to.hash) {
      return {selector: to.hash}
    }
    return {x: 0, y: 0};
  }
});

new Vue({
  el: '#app',
  store,
router,
  render: h => h(App)
});

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import altAnims from "@/store/modules/altAnims";

Vue.use(Vuex);

export const store = new Vuex.Store({
   state: {
      sharesValue: {
         BMW: 1,
         Apple: 0,
         FaceBook: 0,
         Google: 0
      },
      user:{
         funds: 1000,
         shares: {
            BMW: 0,
            Apple: 0,
            FaceBook: 0,
            Google: 0
         }
      }
   },
   modules: {
      altAnims
   }
});

types.js

// Getters
export const GET_PORTFOLIO = 'portfolio/shared';
export const GET_STOCKS = 'stocks/shared';
//Mutations
export const MUTATE_PORTFOLIO = 'shared/SET_PORTFOLIO';
export const MUTATE_STOCKS = 'shared/SET_STOCKS';
//Actions
export const COMMIT_TO_PORTFOLIO = 'shared/ALTERNATE_PORTFOLIO';
export const COMMIT_TO_STOCKS = 'shared/ALTERNATE_STOCKS';

altAnims.js (this is where I fool around with this animation)

import * as types from '../types';

const state = {
    showStock: false,
    showPortfolio: false
};

const getters = {
    [types.GET_PORTFOLIO]: state => {
        return state.showPortfolio
    },
    [types.GET_STOCKS]: state => {
        return state.showStock
    }
};

const mutations = {
    [types.MUTATE_PORTFOLIO]: state => {
        state.showPortfolio = true;
        state.showStock = false
    },
    [types.MUTATE_STOCKS]: state => {
        state.showStock = true;
        state.showPortfolio = false
    }
};

const actions = {
[types.COMMIT_TO_PORTFOLIO]: ({commit}) => {
    commit(types.MUTATE_PORTFOLIO);
},
    [types.COMMIT_TO_STOCKS]: ({commit}) => {
    commit(types.MUTATE_STOCKS);
    }
};

export default {
    state,
    getters,
    mutations,
    actions
}

First Issue

The first thing I tried to do was to create the simple fade animation that you guys can see on both Stocks.vue & Portfolio.vue files, that should get triggered by a @click event on Headers.vue.

Well, the first issue is that for some reason, when I reload the page and trigger the @click event for the first time into any of both Profile.vue or Stocks.vue the animation does not occur. But after switching in between each other it starts to work.

I do not understand why it does not work at the first click, as you can se on altAnims.js the state of both items is set originally to false.

const state = {
    showStock: false,
    showPortfolio: false
};

Then once committing the action of mutating the state, as you can see on the progression at altAnims.js inverting their Boolean statements, on the first click v-if should be reading a getter with the state of false to true, as you can see what the @click event does at Header.vue

Second Issue

Even though I did not resolve this issue at the first place, I tried to move ahead.

This is what I have tried to do @ Portfolio.vue

<template>
    <div class="d-flex flex-wrap justify-content-sm-around">
        <transition name="fade">
        <div class="wrapper" v-if="revealBMW">
            <div class="stock-title">
                <h4 class="box-headers">BMW</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
        <transition name="fade">
        <div class="wrapper" v-if="revealApple">
            <div class="stock-title">
                <h4 class="box-headers">Apple</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
        <transition name="fade">
        <div class="wrapper" v-if="revealFaceBook">
            <div class="stock-title">
                <h4 class="box-headers">Facebook</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
        <transition name="fade">
        <div class="wrapper" v-if="revealGoogle">
            <div class="stock-title">
                <h4 class="box-headers">Google</h4>
                <p class="box-headers" style="font-size: 13px">(Price: 20 EUR)</p>
            </div>
            <div class="stock-content">
                <input type="text" class="mr-auto" placeholder="How many shares?">
                <button>Sell</button>
            </div>
        </div>
        </transition>
    </div>
</template>
<script>
    import {mapGetters} from 'vuex';
    import * as types from '/Users/Dev/WebstormProjects/the-trader-app/src/store/types.js';

    export default {
        computed: {
            ...mapGetters({
                showPortfolio: types.GET_PORTFOLIO
            })
        },
        methods: {
            revealBMW(){
                if(this.$store.state.sharesValue.BMW > 0) {
                    return this.showPortfolio
                }
            },
            revealApple(){
                if(this.$store.state.sharesValue.Apple > 0) {
                    return this.showPortfolio                }
            },
            revealFaceBook(){
                if(this.$store.state.sharesValue.FaceBook > 0) {
                    return this.showPortfolio                }
            },
            revealGoogle(){
                if(this.$store.state.sharesValue.Google > 0) {
                    return this.showPortfolio                }
            }
        }
    }
</script>

<style scoped>

    .wrapper {
        border: solid lightgray 1px;
        width: 500px;
        margin-top: 20px;
        border-radius: 5px;
        box-shadow: 3px 3px 5px 6px #ccc
    }

    .stock-title {
        background-color: #ffbf00;
        color: indianred;
        display: flex;
        flex-wrap: wrap;
        padding: 7px 0 0 20px;
    }

    .box-headers {
        display: inline-block;
        vertical-align: baseline;
        margin: 0;
        padding: 10px -1px 10px 10px;
    }

    .stock-content {
        display: flex;
    }

    p {
        align-self: flex-end;
    }

    input {
        margin: 10px;
        padding: 10px;
    }

    button {
        background-color: indianred;
        color: white;
        padding: 0 20px;
        border-radius: 5px;
        justify-self: center;
        margin: 10px;
    }

    ::placeholder {
        font-size: 15px;
    }

    .fade-enter {
        opacity: 0;
    }

    .fade-enter-active {
        transition: opacity 2s;
    }

    .fade-leave-active {
        transition: opacity 2s;
        opacity: 0;
    }
</style>

I have added some methods so I would only display the containers which had a number of shares more then 0 at altAnims.js's state every time I would enter this route. In order to test this I have hardcoded BMW: 1 and the final result is that the animation stopped working and the BMW shares container is also not responding to my methods.

I am not sure if this would be a good practice, maybe placing these methods in the computed instead, or something totally different that I am missing.

Sorry for such a long question, full of blocks of code. I was trying to be as detailed as possible.

I would like to get help on both issues

P.S - One thought I have just had is if this could work using templates for each container that would like to load in this case. I did not try it, but would it be possible?

Cheers and thanks in advance!

Arp
  • 979
  • 2
  • 13
  • 30
  • Mate, this is just too much code. You should [create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) of your issue...ideally on site like Codesandbox so peoplel can run it and experiment. Some notes...why you doing transitions in so complicated way? You are using Vue Router and still I don't see `` anywhere. Look [here](https://router.vuejs.org/guide/advanced/transitions.html) how to do transitions between routes without `v-if`'s, Vuex and all that stuff. – Michal Levý Dec 24 '19 at 08:33

1 Answers1

1

There is just too many things to write about and Codereview would be better place to do that but....

Here you can find my take on the problem. Things to look for:

  1. Transitions using <route-view> without vuex and other complicated stuff - tuned using appear so it transitions on initial render and mode="out-in" (just try to remove it to see the effect)
  2. Better store state - don't use company names as object keys, not every company name is valid JS identifier
  3. Rewrote Portfolio and Stocks component to use v-for for rendering array data from store

Transitions with Vue Router

<transition name="fade" mode="out-in" appear>
  <router-view :key="$route.fullPath"/>
</transition>
Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • Thanks very much for all of this work in helping me out! It looks much neater. Now I am trying to figure out how to limit the content being passed in the ```v-for``` trying to pass a ```method``` in ```v-for``` https://stackoverflow.com/questions/59472978/can-i-use-the-index-argument-in-v-for-nested-in-a-method . I did not know about Codereview! I will make sure to never repeat this mess I have done here, sir! THanks very very much! – Arp Dec 24 '19 at 19:57