Throwing together Skeleton App and Vue-Routes -- help needed

I am quite content with my accomplishments writing a Vue app, starting from Skeleton. Now I need a navigation bar for the content that I can generate. This seems to be simple enough by using vue-routes.

I have a hard time getting the two things together, maybe you can help by pointing me in the right direction.

I changed main.js to render my own Vue component:

// ...
export default new Vue({
    el: '#content',
	render: h => h(Abakus),
})

main.php remained unchanged so all that should happen is that Abakus.vue gets rendered.

So I pasted demo code from the fiddle for Nested Vue Routes into Abakus.vue. JS went into the <script> section, HTML in <template> and CSS into <style>.

But this does not work at all: nothing gets displayed, I cannot find my code in the Firefox debugger and the browser keeps redirecting to non-existing URLs. What am I missing?

  • Do I need to change the demo vue-routes to the NC URL scheme?
    /settings/emails/apps/abakus/settings/email?

  • Do I need to register the vue-routes in NC (appinfo/routes.php)? How would this be done?

Any help greatly appreciated!

A quick glance at your code lets me think you are trying to get Vue3 running, am I right? Are you using the @nextcloud/vue module as a dependency? This is Vue2-only, thus it might cause trouble. I am unsure, if Vue2+Vue3 can be mixed on one page, though.

Is there no error message in the browser logs?

The Vue routes must stick with the app’s namespace (/apps/abacus). Otherwise, you will try to get/post/put/… to resources in the core that are unknown at best. There are redirection in place to try to fetch such routes to the server with 404 and similar.

We are using Vue + router in the cookbook app, so I know it is working. The router can be found here.
The routes are in general independent of the real routes. These routes in Vue are just named anchors (from a technical perspective, after the # in the URL). We are in fact in a SPA, mostly.

The actual routes (that represent API endpoints) we call from this file and need to be registered in the routes.php files.

There are a few packages under @nextcloud/ in NPM available that might help you to resolve some of your problems. These are integrating with the current NC installation.

@christianplus: Thanks a ton for your answer and your insights. I now understand the bit about vue routes and NC routes. I checked my dependencies and they look a lot like the ones of cookbook:

	"dependencies": {
		"@nextcloud/axios": "^1.10.0",
		"@nextcloud/dialogs": "^3.1.4",
		"@nextcloud/router": "^2.0.0",
		"@nextcloud/vue": "^5.4.0",
		"vue": "^2.7.0",
		"vue-router": "^3.5.4"
	},

The JS console remains silent and the NC log looks unsuspicious to me. That is, there is tons of messages App class OCA\\ReadmeMD\\Appinfo\\Application is not setup via query() but directly, but they seem unrelated.

My code contains the statement router.push('/apps/abakus/settings/profile') and after loading the app the URL shows this address so the routes object gets instantiated. Trouble is, the display area remains empty, nothing gets displayed. If I load the given URL in the browser my app seems to crash as the default app gets loaded.

I may still be missing something obvious am unable to find it. Here is my complete Abakus.vue file:


<template>
  <div id="content">
    <h1>Nested Named Views</h1>
    <router-view></router-view>
  </div>
</template>


<script>

  import Vue from 'vue'
  import VueRouter from 'vue-router'

  Vue.use(VueRouter)

  export default ({
    name: 'Abakus',
    components: {
      UserSettingsNav,
      UserSettings,
      UserEmailsSubscriptions,
      UserProfile,
      UserProfilePreview,
    },
  })

  const UserSettingsNav = {
    template: `
    <div class="us__nav">
      <router-link to="settings/emails">Emails</router-link>
      <br>
      <router-link to="settings/profile">Profile</router-link>
    </div>  `
  }
  const UserSettings = {
    template: `
  <div class="us">
    <h2>User Settings</h2>
    <UserSettingsNav/>
    <router-view class ="us__content"/>
    <router-view name="helper" class="us__content us__content--helper"/>
  </div>
    `,
    components: { UserSettingsNav }
  }

  const UserEmailsSubscriptions = {
    template: `
  <div>
    <h3>Email Subscriptions</h3>
  </div>
    `
  }

  const UserProfile = {
    template: `
  <div>
    <h3>Edit your profile</h3>
  </div>
    `
  }

  const UserProfilePreview = {
    template: `
  <div>
    <h3>Preview of your profile</h3>
  </div>
    `
  }

  const router = new VueRouter({
    mode: 'history',
    routes: [
      { path: 'settings',
        // You could also have named views at the top
        component: UserSettings,
        children: [{
          path: 'emails',
          component: UserEmailsSubscriptions
        }, {
          path: 'profile',
          components: {
            default: UserProfile,
            helper: UserProfilePreview
          }
        }]
      }
    ]
  })

  router.push('settings/emails')


  new Vue({
    router,
    name: 'Abakus',
    el: '#content',
  })


</script>

<style lang="css" scoped>
  .us {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto;
    grid-template-areas: 
      "header header"
      "nav content"
      "nav helper"
      ;
  }

  h2 {
    grid-area: header;
  }

  .us__nav {
    grid-area: nav;
    border: 1px dotted;
    margin-right: .75rem;
    padding: .3rem;
  }
  .us__content {
    grid-area: content;
  }
  .us__content--helper {
    grid-area: helper;
  }
</style>

Safari’s console complains that I am using a version of vue without template compiler. This sounds interesting as my *.vue file contains templates in the script section that will need parsing.

Not being sure how to address this issue I did an npm install vue-template-compiler, which added a dependency in package.json. Unfortunately webpack does not seem to use the template compiler automagically. The message remains and I still see no output.

I am sorry, I did not answer earlier. I missed to watch the topic :roll_eyes:.

Could you please share the routes.php file as well? This one will be especially relevant when 404 happen. If you had published your app to GitHub or similar, that would be very useful to allow for manual browsing.

I assume that you have an index file that just displays the Vue app (ideally). The route (as configured in routes.php) to show the Vue app would be /. Then, your main route would be /apps/abacus/#/settings/profile most probably.

Note the #. The path /apps/abakus/ describes the main page as requested from the NC server. So, this page is requested and contains/loads all JS code. Within the Vue app, the anchor (after the #) is used to locate the state within the Vue app. In your case that is the relevant path in the Vue router.

Vue (router) will handle the glory details but technically the change of the Vue route will not result in a different base URL and thus no server interaction is needed. This makes the speed improvements of Vue over classical pages.

As far as I know, you should precompile the Vue apps. Then, the building of the app can be offloaded on the dev machine and done only once. I am not even sure, if the live-building of Vue components is possible. It is for sure not recommended.

Further, I am a bit confused. You are creating a Vue app inside the script section of your vue file. This seems for me that you are throwing things together that do not belong together. Sorry, no offense intended, just my impression from your writing.

You should have a basic html file like this:

<div class="content"></div>

This will just contain the starting point for the Vue app and will be dynamically replaced by the real app.

Second, you have a main JS file as an entry point. You can call it as you like but let’s call it main.js. This file just contains the needed code to init the vue app.

import Vue from 'vue'
import VueRouter from 'vue-router'

// Further imports, for example
import Abakus from './Abakus.vue'

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: 'settings',
      // You could also have named views at the top
      component: UserSettings,
      children: [{
        path: 'emails',
        component: UserEmailsSubscriptions
      }, {
        path: 'profile',
        components: {
          default: UserProfile,
          helper: UserProfilePreview
        }
      }]
    }
  ]
})

router.push('settings/emails')

new Vue({
  router,
  render: h => h(Abakus),
  el: '#content',
})

Finally, you organize the (sub-) components of your app into individual *.vue files.