I started playing with Vue.js and VueMaterial this week. It's really fun and totally new for me as I didn't do Javascript for years (Native Android being my cup of tea).
I am currently stuck with a very strange issue that I would say related to my lack of knowledge with the framework lifecycle.
Issue
The first time I am loading/refreshing manually the project in Chrome, it fails loading the VueMaterial components for some reason, and I get the message below:
Unknown custom element: md-toolbar - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I get this message with every single VueMaterial components I use in the project (md-whiteframe, md-input-container, and so on).
And the result appears as below:
Now, if I specifically change the URL in chrome by giving the path of another component (e.g. Home), then some components are correctly displayed (except the toolbar for some reason).
I go back (with Chrome), and land on the first component, except that now, everything is displayed correctly, still except the toolbar (see below).
On certain occasion (really rare and completely randomly, the Toolbar is displayed correctly.
Context
I installed vue-cli via npm and built the scaffold with the webpack template, then installed VueMaterial with npm as well.
Code
Below, some bits of the code.
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.css'
Vue.config.productionTip = false
let colorPrimary = {
color: 'red',
hue: 700,
hexa: '#D32F2F'
}
let colorAccent = {
color: 'yellow',
hue: 600,
hexa: '#FDD835'
}
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
Vue.use(VueMaterial)
Vue.material.registerTheme('default', {
primary: colorPrimary,
accent: colorAccent,
warn: colorPrimary,
background: 'white'
})
Vue.material.setCurrentTheme('default')
App.vue
<template>
<div id="app">
<toolbar></toolbar>
<router-view></router-view>
</div>
</template>
<script>
import Toolbar from './components/Toolbar';
export default {
components: {
Toolbar
},
name: 'app'
}
</script>
<style src="./styles/general.css"></style>
Toolbar.vue (custom component)
<template>
<div id="toolbar">
<md-toolbar class="md-primary">
<h2 class="md-title" style="flex: 1">Firebucket</h2>
</md-toolbar>
</div>
</template>
<script>
export default {
name: 'toolbar'
}
</script>
<style scoped src="../styles/toolbar.css"></style>
Login.vue
<template>
<div id="login" class="bg-primary">
<md-whiteframe md-elevation="2" id="login-form">
<span class="md-title text-primary">{{wording.login}}</span>
<md-input-container>
<label>{{wording.username}}</label>
<md-input></md-input>
</md-input-container>
<md-input-container>
<label>{{wording.password}}</label>
<md-input></md-input>
</md-input-container>
<router-link id="login-button" tag="md-button" to="XX" class="md-raised md-primary">{{wording.login}}</router-link>
<br />
<md-button class="md-primary">{{wording.createAccount}}</md-button>
</md-whiteframe>
</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
wording: {
login: 'Login',
createAccount: 'Create an account',
username: 'Username',
password: 'Password'
}
}
}
}
</script>
<style scoped src="../styles/login.css"></style>
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from './pages/Login'
import Home from './pages/Home'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/home',
name: 'home',
component: Home
},
{
alias: '/',
path: '/login',
name: 'login',
component: Login
}
]
})
--
Again, web development is not my thing, I've heard a lot of good thing about Vue.js so I wanted to give it a try. But sorry if I am missing something really obvious.
I really can't wrap my head around the fact that it's not working the first time but working fine when I go back...
PS: the router-link (in Login.vue) doesn't work neither, nothing happens when I tap the button, but I guess that's a different issue.
Any idea anyone? General feedback on the code posted above is more than welcome so I can improve it.
The code is available on GitHub, if that can help.
Thanks a lot!
You are adding the VueMaterial plugin after you create your Vue. Instead, add it before.
Vue.use(VueMaterial)
Vue.material.registerTheme('default', {
primary: colorPrimary,
accent: colorAccent,
warn: colorPrimary,
background: 'white'
})
Vue.material.setCurrentTheme('default')
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
Related
I am very new to the Vue framework, as well as Javascript, but am currently building a site using Vue and I want to have some links at the top of my site that the user can navigate to. I have tried using the Vue Router (https://router.vuejs.org/guide/#javascript) in order to make these links. At this point, I just want to make a little 'About Us' section that the user can navigate to. But, despite the URL changing accordingly to 'localhost:8080/#/about_us', the Vue component that I have associated with the link will not show up.
I have structured my code in the main.js as such:
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
Vue.config.productionTip = false
export const eventBus = new Vue();
Vue.use(VueRouter);
const AboutUs = {template: '<div>about_us</div>'};
const route = [{path:'/about_us', component: AboutUs}];
const router= new VueRouter({route});
new Vue({
render: h => h(App),
router
}).$mount('#app')
And then I have my app.vue designed as (note: I reduced much of the code to its essentials for brevity):
import AboutUs from './components/AboutUs.vue'
import { eventBus } from './main.js'
export default {
data(){
return {
films: []
}
},
components: {
"about-us": AboutUs
},
mounted(){
fetch('https://ghibliapi.herokuapp.com/films')
.then(res => res.json())
.then(films => this.films = films)
.catch(error=> console.log(error))
}
}
</script>
body {
background-color: deepskyblue;
}
<h1>Ghibli Fandom Extravaganza</h1>
<nav>
<li><router-link to="/about_us">About us </router-link></li>
<router-view></router-view>
</nav>
<p>List of Ghibli Movies: <films-list :films="films"/></p>
<film-detail />
At this point, my AboutUs component is only a very basic Vue that shows some information about the site in some simple HTML tags. But although the link is active and does work, the information from the Vue is not displayed, while the other Vue components continue to show, which indicates that maybe they are not connected? I have tried to follow the tutorial in the Vue Router site, but I don't think that I understand the mechanics of how the code actually works. Can anybody recommend me any corrections?
UPDATE:
Here is the code to my AboutUs.vue
<template>
<div>
<h1>This site is for examining the movies of Studio Ghibli</h1>
</div>
</template>
<script>
export default {
name: 'about-us'
}
</script>
<style scoped>
</style>
I think there is no need to import 'aboutus' component. You can just write like this <router-link to="about_us">About us </router-link>
and in the main.js declare the route like this
const route = [{path:'/about_us',name:'about_us', component: () => import("path to about us file")}];
The code samples you provided are a bit confusing, you should simply pass an imported view straight in to the component property of a router entry.
Where you have done:
const AboutUs = {template: '<div>about_us</div>'};
Replace that line with:
import AboutUs from './components/AboutUs.vue'
I can't figure out from your sample, when and what the relevance of components: {"about-us": AboutUs }, it is not needed.
Here is a sample of my setup:
router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'index',
component: () => import('../components/views/welcome')
},
{
path: '/about-us',
name: 'about-us',
component: () => import('../components/views/about-us')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
App.vue
<template>
<v-app v-cloak>
<router-link :to="{ name: 'index' }">Welcome</router-link>
<router-link :to="{ name: 'about-us' }">About Us</router-link>
<router-view></router-view>
</v-app>
</template>
<script>
export default {
name: 'App'
}
</script>
components/views/about-us.vue
<template>
<div>This is the About Us page!</div>
</template>
<script>
export default {
name: 'about-us'
}
</script>
This sample uses History Mode
Other things to note
When routing, mounted is unreliable, instead you should place any fetch logic into it's own method when calling any :
methods: {
fetch () {
// https://github.com/axios/axios
axios.get('https://ghibliapi.herokuapp.com/films').then( ... )
}
}
Call this.fetch method in both beforeRouteUpdate and beforeRouteEnter instead of mounted, you can't even rely on created when it comes to views handled by vue-router.
Axios is suggested instead of native fetch because axios provides more functionality, features and browser compatibility.
In about-us.vue you add these Navigation Guards like so:
<template>
<div>This is the About Us page!</div>
</template>
<script>
export default {
name: 'about-us'
methods: {
fetch () {
axios.get('https://ghibliapi.herokuapp.com/films').then( ... )
}
}
// Will fire if you are already on the view but a parameter changes (dynamic routing)
beforeRouteUpdate(to, from, next) {
this.fetch()
next()
},
// Will fire when you enter the view
beforeRouteEnter(to, from, next) {
this.fetch()
next()
},
}
</script>
Both should be added, understand that they won't fire at the same time, only one of them will execute fetch once when relevant.
This will resolve any issues you would otherwise encounter with Dynamic Routing should you ever use them.
Folder Structure
src/
+ App.vue
+ main.js
+ router.js
+ vue.config.js
+ assets/
+ logo.png
+ components/
+ views/
+ welcome.vue
+ about-us.vue
Hope this clears up the setup requirement for you.
I'm new to Vue-router and can't figure out what's gone awry. Clicking on a navigation link causes the desired component to be displayed for an instant, but then the browser tries to open the component as if it were a file.
For example, clicking on the "Badger!" link results in the browser attempting to open a local file named file:///home/travis/.../prototype/dist/badger which of course results in a file not found error.
Any insights on this? I've tried to follow existing examples carefully.
main.js:
import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
import Badger from './component/Badger.vue';
import Grid from './component/Grid.vue';
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/badger', component: Badger },
{ path: '/grid', component: Grid },
]
});
new Vue({
el: '#app',
router,
render: h => h(App),
});
App.vue
<template>
<div id="app">
<ul>
<li>
<router-link to="/badger">Badger!</router-link>
</li>
<li>
<router-link to="/grid">Data!</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default {
mounted() {
console.log('mounted App component')
},
data() {
// ...
}
},
}
</script>
<style>
// ...
</style>
When the router is in history mode, the build artifacts must be delivered to the browser from an http server, as #bigless mentioned.
If your workflow requires opening the build artifact from the local filesystem, that can be done if you remove these lines from the router declaration:
mode: 'history',
base: __dirname,
This will put you in "hash mode" (the default state). Your artifact can then be loaded either from an http server or from the local filesystem.
Relevant docs: https://router.vuejs.org/guide/essentials/history-mode.html
I setup my Vue project to use dynamic layouts - that is, layouts that persist from page to page, assuming the layout for the new page is the same as the last page. My problem is that when I go to a route with a different layout, the router-link component gets created and destroyed, then created again, which is causing me some issues. My setup is as follows:
App.vue
<template>
<component :is="layout">
<router-view :layout.sync="layout" />
</component>
</template>
<script>
import LayoutPortal from '#/layouts/LayoutPortal';
import LayoutOffline from '#/layouts/LayoutOffline';
import LayoutDefault from '#/layouts/LayoutDefault';
export default {
name: 'App',
components: {
LayoutPortal,
LayoutOffline,
LayoutDefault,
},
...
Some router-view Component
<template>
...
</template>
<script>
import LayoutDefault from '#/layouts/LayoutDefault';
export default {
...
created() {
this.$emit('update:layout', LayoutDefault);
},
}
</script>
Layout Default
<template>
<div class="wrapper">
<slot />
</div>
</template>
<script>
export default {
name: 'layout-default',
};
</script>
tldr;
If you setup your project using dynamic layouts, following any of a number of tutorials out there online, when you navigate to a route with a different layout than the last page, the new router-view component gets created, destroyed, then created again. This causes issues like doubling up on mounted() calls and more.
I ultimately went with nested (child) routes (https://router.vuejs.org/guide/essentials/nested-routes.html):
{
path: '/portal',
component: LayoutPortal,
beforeEnter(to, from, next) {
if (!store.getters.auth) {
next('/login');
return;
}
next();
},
children: [
{
path: 'home',
name: 'portal-home',
component: PortalHome,
},
{
path: 'about',
name: 'portal-about',
component: PortalAbout,
},
...
In this way I can load up the layout as the parent route, separate beforeEnter logic into separate route groups, and avoid the problem where going to a page with a new layout loads a component twice.
I am kinda new in vue.js
I have a laravel app with vue.js. When hp is loading script also loading all elements are initialised (owl carousel, rev slider etc), but when i click other route contact or about and come back to hp the sliders or other related to js doesnt load .
routes.js
import VueRouter from 'vue-router';
import Home from './components/views/Home.vue';
import About from './components/views/About.vue';
import Contact from './components/views/Contact.vue';
let routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact },
{ path: '/notes', component: Notes }
];
export default new VueRouter({
routes,
linkActiveClass: 'active'
});
and app.js
import router from './routes';
import './components';
const app = new Vue({
el: '#app',
router
});
Is there a way to run the functions to load carousels etc each time i change view ?
On mounted trigger you can add your custom js for each component
<script>
export default {
mounted () {
}
}
</script>
if anyone stumbles upon this and still looking for a way to do it, this is how I managed to do this. wrap the <route-view/> in a <transition> which you can control with css and call a method on enter which calls the function you want.
this will call the function as soon as the component is loaded in the DOM on every route change
<transition name="slide" v-on:enter="reInitJS">
<router-view></router-view>
</transition>
<script>
//import the wanted function
import {init} from './main';
export default {
name: 'App',
methods: {
reInitJS(){
//call the function
init();
}
}
}
</script>
Very simple but seems to not be working. Must be because i'm new to VueJS. I downloaded this repo https://github.com/creotip/vue-particles. As I want to create a under construction page using this style.
Problem: I need to create a hamburger menu icon in the top right hand corner which on click calls a method to hide and show a div ( really basic stuff ). I've followed the vue JS tutorials and what people have said on stack overflow but I just cant get my template to speak to the method.
When I click on the hamburger button i keep getting "_vm.hello is not a function". What am i doing wrong? Please see screenshot. There must be something simple to solve this.
Heres what my code looks like:-
app.vue
<template>
<div id="app">
<div class="wrap-banner">
<div class="button_container" #click="hello()">
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
</div>
</template>
main.js
import Vue from 'vue'
import App from './App'
import Home from './components/home'
Vue.use(Home)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App },
methods: {
hello: function() {
alert('hello')
}
}
})
Screenshot
You need to move hello method to App.vue
App.vue
<template>
<div id="app">
<div class="wrap-banner">
<div class="button_container" #click="hello">
Demo Panel
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {};
},
methods: {
hello: function() {
alert("hello");
}
}
};
</script>
main.js
import Vue from "vue";
import App from "./App";
Vue.config.productionTip = false;
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
Checkout demo at https://codesandbox.io/s/8y5yzv00nj
Vue File Architecture
You need to be aware that a Vue file normally has 3 components (HTML, Js and CSS). This file then needs to be processed with a compiler (babel for example) in order to make it readable for the browser. See Vue Single File Components for more information.
Clean Solution
The vue-cli gives you a working starter template with babel and webpack all preconfigured. Just create a vue project and change the template as needed.
Install Vue-CLI
vue create my-project
npm run dev
Quick Solution
If you do not want to use a compiler, just implement it like following:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods: {
hello: function() {
alert('hello')
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<button #click="hello()">Click me</button>
</div>