Search code examples
vue.jsbootstrap-4nuxt.jsbootstrap-vue

Why is nuxt-link refreshing the page when used with Bootstrap-vue?


I am using nuxt and bootstrap to build a custom hover dropdown menu for navigation. The issue I have is that my navigation submenu NuxtLinks are refreshing the entire page instead of smoothly changing the app content in my Nuxt block. The nav bar is dynamically generated in the default.vue layout and uses a b-dropdown-hover component where the NuxtLink is wrapped around that content. Why does the page do a full refresh for those links/anchors but my b-navbar-brand image does a smooth transition? I apologize, I am very new to Nuxt. This video @ ~1:35:00 demonstrates what I'm trying to do.

components/BDropdownHoverRight.vue

<template>
  <nuxt-link :to="aTo">
    <div class="ddr-top" @mouseover="onOver1($event.target)" @mouseleave="onLeave1($event.target)">
      <b-dropdown ref="dropdown_ddr" :text="cText" class="m-md-2 ddr">
        <slot></slot>
      </b-dropdown>
    </div>
  </nuxt-link>
</template>

<script>
export default {
  name: 'BDropdownHoverRight',
  props: {
    cText: {
      type: String,
    },
    aTo: {
      type: String,
    },
  },
  methods: {
    onOver1(t) {
      if (t.nodeName === 'DIV') {
        console.log(t)
        console.log(t.nodeName)
        let num_child_nodes = 0
        try {
          if (t.querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length >= 0) {
            num_child_nodes = t.querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length
          }
        } catch (e) {
          if (t.querySelectorAll(':scope > div > ul')[0].getElementsByTagName('div').length >= 0) {
            num_child_nodes = t.querySelectorAll(':scope > div > ul')[0].getElementsByTagName('div').length
          }
        }

        if (num_child_nodes > 0) {
          try {
            t.querySelectorAll(':scope > div > ul')[0].style.display = 'block'
          } catch (e) {
            try {
              t.querySelectorAll(':scope > ul')[0].style.display = 'block'
            } catch (e) {}
          }
        }
      }
    },
    onLeave1(t) {
      try {
        t.querySelectorAll(':scope > div > ul')[0].style.display = 'none'
      } catch (e) {
        try {
          t.querySelectorAll(':scope > ul')[0].style.display = 'none'
        } catch (e) {}
      }
    },
  },
}
</script>

layouts/default.vue

<template>
  <div>
    <b-navbar id="top-nav-bar" toggleable="lg" type="light" sticky>
      <b-navbar-brand to="/">
        <Rabbit id="tl-logo" />
      </b-navbar-brand>

      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <template v-for="dir in navtop_dd">
            <b-dropdown-hover
              :key="dir.id"
              :c-text="dir.name"
              :a-to="dir.hasOwnProperty('ato') ? dir.ato : '/nolink'"
            >
              <template v-if="'submenus' in dir && dir.submenus.length > 0">
                <template v-for="dir1 in dir.submenus">
                  <b-dropdown-hover-right
                    :key="dir1.id"
                    :c-text="dir1.name"
                    :a-to="dir1.hasOwnProperty('ato') ? dir1.ato : '/nolink'"
                  >
                    <template v-if="'submenus' in dir1 && dir1.submenus.length > 0">
                      <template v-for="dir2 in dir1.submenus">
                        <b-dropdown-hover-right
                          :key="dir2.id"
                          :c-text="dir2.name"
                          :a-to="dir2.hasOwnProperty('ato') ? dir2.ato : '/nolink'"
                        >
                        </b-dropdown-hover-right>
                      </template>
                    </template>
                  </b-dropdown-hover-right>
                </template>
              </template>
            </b-dropdown-hover>
          </template>
        </b-navbar-nav>

        <!-- Right aligned nav items -->
        <b-navbar-nav class="ml-auto">
          <b-nav-form>
            <b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
            <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
          </b-nav-form>

          <b-nav-item-dropdown right>
            <!-- Using 'button-content' slot -->
            <template #button-content>
              <b-img src="../assets/imgs/account-circle.svg" style="height: 35px"> </b-img>
              <!-- <em>User</em> -->
            </template>
            <b-dropdown-item href="#">Profile</b-dropdown-item>
            <b-dropdown-item href="#">Sign Out</b-dropdown-item>
          </b-nav-item-dropdown>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <b-container id="app-content">
      <Nuxt />
    </b-container>
    <div id="footer">
      <div style="height: 100%; padding: 5px">© 2021</div>
    </div>
  </div>
</template>

<script>
import BDropdownHover from '@/components/BDropdownHover'
import BDropdownHoverRight from '@/components/BDropdownHoverRight'

export default {
  components: {
    BDropdownHover,
    BDropdownHoverRight,
  },
  data() {
    return {
      navtop_dd: [
        {
          id: 1,
          name: 'Transactions',
          ato: '/transactions',
          submenus: [
            {
              id: '1a',
              name: 'Sales Orders',
              ato: '/transactions/salesorders',
              submenus: [
                {
                  id: '1b',
                  name: 'New',
                },
                {
                  id: '2b',
                  name: 'List',
                },
              ],
            },
            {
              id: '2a',
              name: 'Item Fulfillments',
              ato: '/transactions/itemfulfillments',
              submenus: [
                {
                  id: '1b',
                  name: 'New',
                },
                {
                  id: '2b',
                  name: 'List',
                },
              ],
            },
          ],
        },
        {
          id: 2,
          name: 'Inventory',
        },
        {
          id: 3,
          name: 'Reports',
        },
        {
          id: 4,
          name: 'Setup',
        },
        {
          id: 5,
          name: 'Support',
        },
      ],
    }
  },
  mounted() {
    var x = document.querySelectorAll('.b-dropdown.navtop-dd')
    for (var i = 0; i < x.length; i++) {
      if (x[i].querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length == 0) {
        var btn = x[i].querySelectorAll(':scope > .btn')[0]
        btn.classList += ' no-content-after'
      }
    }
    var x = document.querySelectorAll('.b-dropdown.ddr')
    for (var i = 0; i < x.length; i++) {
      if (x[i].querySelectorAll(':scope > ul')[0].getElementsByTagName('div').length == 0) {
        var btn = x[i].querySelectorAll(':scope > .btn')[0]
        btn.classList += ' no-content-after'
      }
    }
  },
}
</script>

<style>
#top-nav-bar {
  border-bottom: 1px solid green;
}

#tl-logo {
  height: 40px;
  margin: 5px;
}

#footer {
  height: 40px;
  color: black;
  border-top: 1px solid green;
  margin: auto;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: space-around;
}

.navtop-dd button {
  background: none !important;
  color: #6c757d !important;
  border: none !important;
}

#app-content {
  margin: 20px auto;
}

.ddr > button::after {
  display: inline-block;
  margin-left: 0.555em;
  right: 0px;
  content: "";
  border-top: 0.25em solid transparent;
  border-right: 0.3em solid transparent;
  border-bottom: 0.25em solid transparent;
  border-left: 0.35em solid;
  vertical-align: 0.075em;
}

.b-dropdown {
  width: 100%;
}

.ddr > button {
  text-align: left;
}

.no-content-after::after {
  content: none !important;
}

.ddr > ul {
  top: -1.2rem;
  left: calc(100% - 0.5rem);
}

.dropdown-menu {
  min-width: 0 !important;
}

.dropdown-item {
  color: #6C757D;
}

.ddr-top:hover {
  background-color: #e4ffda;
}

a:hover {
  text-decoration: none !important;
}
</style>

Solution

  • There is a LOT of irrelevant code here. I took the time to format it properly. Please make the effort yourself next time (to format and input interesting bits only).

    Also, the answer on how to fix the issue was actually given in the video itself. The video is talking about the differences between a and nuxt-link tags.

    Which relates to this part of Bootstrap's Vue documentation where you can see that

    [to] prop: Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object

    So, you should use something like this

    <template>
      <b-dropdown>
        <template #button-content>
          Custom <strong>Content</strong> with <em>HTML</em> via Slot
        </template>
        <b-dropdown-item to="/test">Go to test page via Vue-router</b-dropdown-item>
      </b-dropdown>
    </template>
    

    I also saw that your code is rather different from the video. You should not use querySelector, you don't have to import Nuxt components neither and you have several ESlint warning/errors.

    I do recommend trying to focus on a single part of learning and not mixing all of them. It's fine to want to go a bit further, but be careful of not being lost with too much abstraction while you do learn a lot of new concepts (Vue/Nuxt).


    On a side note, if you want to continue learning Nuxt, you can check this: https://masteringnuxt.com/ (created by a Nuxt ambassador and other well known people in the Vue ecosystem)

    Have fun creating projects with Nuxt!