How to bind an event on a vue-router template - javascript

I've been following a guide to create a vue-router object, but the browser displays the following warning:
[Vue warn]: Property or method "auth_login" is not defined on the
instance but referenced during render. Make sure to declare reactive
data properties in the data option. (found in anonymous component -
use the "name" option for better debugging messages.)
I just added an event binding on a html tag, like the following:
<div id="app">
<router-view>
</router-view>
<script type="text/temptlate" id="t_auth">
<div class="auth">
<router-view></router-view>
</div>
</script>
<script type="text/temptlate" id="t_auth_login">
<div class="auth_login">
<div>
<button class="btn-primary full" id="btn_login" #click="auth_login" #keyup.enter="auth_login">登录</button>
</div>
</div>
</script>
</div>
The JS code is:
(function() {
let getView = (id) => {
tmp = document.getElementById(id)
if (tmp == null) {
return null;
}
return tmp.innerHTML
};
const routes = [{
path: '/auth',
component: { template: getView('t_auth') },
children: [
{ path: 'register', component: { template: getView('t_auth_register') } },
]
}];
const router = new VueRouter({
routes: routes
});
const app = new Vue({
router: router,
el: "#app",
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
auth_login: function(event) {
// 方法内 `this` 指向 vm
alert('Hello ' + this.name + '!')
}
}
}).$mount('#app')
})();
Why can't it find the auth_login method? What about the lifecycle?
How can I bind the event inside the template ?
The full source code is located there: https://github.com/295421489/reminder-ximu/tree/dev/public

I don't have a direct answer for your question, but this is how you can debug your Vue apps:
Install https://github.com/vuejs/vue-devtools in your Google Chrome browser. You may need to restart the browser for the extension to start working. (I don't remember how I got it the first time)
Once you have Vue dev tools, you will start seeing this in your developer console, whenever you load a Vue app (development build of Vue.js):
Your routes will also show up very well. As you can see, my app above is currently in the route /chapter/1 (that orange box on the left side)
Click on "Send to Console" and the $vm instance will become available in your developer console.
Here, you can find if your auth_login method is available or not, for your route. And you can also do a lot more debugging for your app.
If you want a working Vue app (with routes) to test, you will find a jsFiddle in this answer: https://stackoverflow.com/a/40215123/654825
Hope it helps!

I solved this question.
The error is can't find the method,I think it is because of scope. So, I created a component firstly:
var t_auth_login = Vue.extend({
template: getView('t_auth_login'),
// 在 `methods` 对象中定义方法
methods: {
auth_login: function(event) {
}
});
and the routes values as :
const routes = [{
path: '/auth',
component: t_auth}]
everything is ok.

Related

Force to use window.location.href in vue-router hash mode

I'm using vue-router 3.0.1, and the mode is hash.
The current url is:
/#/?type=1
I tried to use window.location.href for the same path, but different query parameter like this.
window.location.href = '/#/?type=2';
But the url of the browser changes, but nothing else happens.
At the first place, I am trying this, because router.push didn't re-render the component.
The original window.location.href should give the different result, but vue-router looks like to override window.location.href.
How can I force to move to /#/?type=2, in this case?
You don't need to use window.location.href to make it work. The problem here is that the component is reused when you only update the query parameter and the component will not automatically re-render. One way to solve this issue is to watch the $route in your component. Here's an code example. You also can find the jsFiddle here https://jsfiddle.net/Fourzero/cbnom5sL/22/.
Html
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<!-- You can use router-link to trigger the url change or router.push in your code -- it doesn't matter. -->
<router-link to="/?type=2">type 2</router-link>
<router-link to="/?type=1">type 1</router-link>
<router-view></router-view>
</div>
JavaScript
const Bar = {
template: '<div>Type {{type}}</div>',
data () {
return {
type: ''
}
},
mounted () {
this.type = this.$route.query.type;
},
watch: {
$route(to, from) {
// Update the data type when the route changes.
this.type = to.query.type;
}
}
}
const router = new VueRouter({
mode: 'hash',
routes: [
{ path: '', component: Bar },
]
})
new Vue({
router,
el: '#app',
data: {
msg: 'Hello World'
}
})
For detailed explanation, you can refer the official doc https://router.vuejs.org/guide/essentials/dynamic-matching.html.

How to get the latest data from parent to child components after page refresh

I am working on a project and using Vue.js for the frontend. I have following code in the main.js file.
new Vue({ // eslint-disable-line no-new
//el: '#app',
router,
data () {
return {
friends: []
}
},
methods: {
getFriends: function () {
return this.friends;
}
},
created: function () {
this.$http.get('/user/' + this.getUserIDCookie('userID') +
'/friends').then(function (response) {
this.friends = response.data;
});
},
components: {
'nav-bar': require('./components/Navigation.vue')
},
template: `
<div id="app">
<nav-bar></nav-bar>
<router-view class="router-view"></router-view>
</div>`
}).$mount('#app');
In one of the pages(for ex. when the page is redirected to localhost/#/user/1/details, I am retrieving the friends' list from main.js like below:
<script type="text/babel">
export default {
name: 'profile',
data: function () {
return {
user: {},
friends: []
}
},
methods: {
// Some methods
},
created: function () {
this.friends = this.$root.getFriends();
}
}
</script>
The problem arises when I refresh the current page. After page refresh, this.friends is null/undefined because this.$root.getFriends() is returning null/undefined. I can move it to user component, but I want to keep it in main.js so that GET call is used once and data will be available to the whole application.
Any input regarding how to solve this issue would be great. I am using Vue 2.0.1
Really, what you want to do, is pass the data the component needs as props.
The dirt simple easiest way to do it is this.
<router-view class="router-view" :friends="friends"></router-view>
And in your profile component,
export default {
props:["friends"],
name: 'profile',
data: function () {
return {
user: {},
friends: []
}
},
methods: {
// Some methods
}
}
If you want to get more sophisticated, the later versions of VueRouter allow you to pass properties to routes in several ways.
Finally, there's always Vuex or some other state management tool if your application gets complex enough.
The problem is that when you refresh the page, the whole app reloads, which includes the get, which is asynchronous. The router figures out that it needs to render details, so that component loads, and calls getFriends, but the asynchronous get hasn't finished.
You could work around this by saving and pulling the Promise from the get, but Bert's answer is correct: the Vue Way is to send data as props, not to have children pull it from parents.

Vue 2 - Communication between components (sending and receiving data)

So I working on app in Vue. I have problem with sending and receiving data between components. Already tried with $dispatch/$broadcast, $emit/$on but still now working. I want to send selected active_club from ClubSelection.vue to vue_main.js.
Vue version: 2.0.3
Structure of my app:
vue_main - main Vue file
HeaderElement.vue (child of vue_main)
ClubSelection.vue (child of HeaderElement)
Need to send active_club from ClubSelection to vue_main.
ClubSelection.vue
<script>
export default{
props: [
'club', 'title'
],
created(){
//Get club list
this.$http.get('/api/clubs', function(data) {
this.clubs = data;
console.log(data);
//read active club from parent
this.selected = this.$parent.$parent.active_club;
});
},
data(){
return{
clubs: [],
selected: null,
}
},
watch: {
selected: function(v) {
this.club = v;
//Post to database selected club
this.$http.post('/api/clubs/' + v + '/active')
},
club: function(v) {
this.selected = v;
//Change active_club at parent (THIS NOT WORKING)
// this.$emit('active_club', v);
// this.$parent.active_club = v;
club.$emit('active_club', v);
},
}
}
</script>
vue_main.js
const app = new Vue({
router,
data() {
return {
user: [],
active_club: null,
ranking: null
}
},
created: function() {
var self = this;
this.$http.get('/api/users/me', function(data) {
this.user = data;
self.active_club = data.active_club;
})
}
}).$mount('#app');
const club = new Vue();
//THIS NOT WORKING
club.$on('active_club', function (id) {
alert(id)
this.active_club = id;
});
Errors:
Vue warn]: Error in watcher "club" (found in component
)
vue_main.js:16924 Uncaught (in promise) ReferenceError: club is not
defined
I have tried many set ups, this is one of them. How to make this working?
$dispatch and $broadcast are deprecated in Vue 2.0.
In your case, what you need is communication between a parent component and child component. When a child $emits an event, parent can listen to it by providing a method in template markup itself, using v-on:parentMethod() as follows:
<child-component v-on:child-event="handlerMethod"></child-component>
The above markup is done inside parent component's template. And the parent component needs to have that handlerMethod in its methods.
Here is a sample "parent-child communication" question on Stackoverflow, which has a jsFiddle example also: Delete a Vue child component
You may use the above answer as reference to implement $emit in your app.
Edit: Additional Notes
I forgot to mention the note about three level hierarchy you have. In your app, you have the following hierarchy:
parent: vue_main
child 1: HeaderElement
child 1.1: ClubSelection
For sending events from ClubSelection to vue_main, you may either use non parent-child communication method or you can relay the event using the intermediate HeaderElement.
Here is how the event relay can work:
Step 1: ClubSelection sends a $emit, which is received by HeaderElement using v-on.
Step 2: The handlerMethod in HeaderElement does a this.$emit, which can be received by your main template using another v-on.
While the above may look a bit convoluted, it is much more efficient than broadcasting to every single component in the app, as it is generally done in Angualr 1.x or other frameworks.

how communicate two separate .vue files?

In a project with vue.js 2:
I've a component living in a .vue file that represents a list of elements. Also, I've a sidebar that is the summary of this list. This sidebar is another component in a .vue file.
So, how I can keep communication between each them, for example, if I removed a element from a list, reflect the change in a var declared in sidebar that is the total number of elements?To ilustrate:
SideBar.vue
<template>
...
<span></span> ===> here I need total of elements listed in ListElements.vue
...
<template>
ListElements.vue
<template>
...
#click="deleteEntry"
...
<template>
<script>
methods: {
deleteEntry(entry) {
//here I need to notify to SideBar.vue in order to update the total of elements in the this.entries list.
let index = this.entries.indexOf(entry);
if (window.confirm('Are you sure you want to delete this time entry?')) {
this.entries.splice(index, 1);
}
}
</script>
OK, I've created a simplified example of how this works. Your bus needs to be global so it is accessible by all Vue components, this simply means placing it outside of all other components and view models:
var bus = new Vue({});
var vm = new Vue({
// Main view model has access to bus
el: '#app'
});
Then you just need to emit the event on the bus on some event and catch that in the other component:
Component one emits a message to the bus on keyup:
Vue.component('component-one', {
template: '<div>Enter a message: <input v-model="msg" v-on:keyup="updateMessage"> </div>',
methods: {
updateMessage() {
bus.$emit('msg', this.msg);
}
},
data() {
return {
msg: ""
}
}
});
Component-two listens for the message:
Vue.component('component-two', {
template: "<div><b>Component one says: {{ msg }}</b></div>",
created() {
bus.$on('msg', (msg) => {
this.msg = msg;
});
},
data() {
return {
msg: ""
}
}
});
Here's the fiddle: https://jsfiddle.net/v7o6d2vL/
For your single page components to get access the the bus you just need to make sure your bus is in the global scope, which you can do by using window:
window.bus = new Vue({});
you can then use bus.$emit() and bus.$on() inside your components as normal

Ractive creating duplicate "ghost" components, but not displaying or calling lifecycle events

I'm at my wit's end with this one.
Essentially Ractive is creating two instances of a component, but only calling oninit of one of them — the one that isn't actually in the DOM. This happens after navigating to a different "page" (main.set('page', 'somepage')) and then navigating back to the previous page.
The main ractive instance is pretty simple:
var main = new Ractive({
el: 'main',
template: require('templates/main'),
partials: partials,
components: { ... },
data: {
loading: true,
page: 'loading',
component: function(name){
if (!!this.partials[name]) return name;
this.partials[name] = '<' + name + '/>';
return name;
},
},
});
and itss template:
{{> navigation}}
<div id='page'>
{{> component(page) }}
</div>
{{> footer}}
I tried reproducing this in a Plunkr, but was unsuccessful. Instead, I added debugger statements to a live, unminfied version.
edit: Live version removed
It happens after navigating back to the index page. Open the console. Click on the 'login' button in the top right, then click on the logo to go back to the index. There are source maps, files of interest are homepage.js (the component that is being 'ghosted') and router.js (where the main ractive instance is).
It looks like the router is calling the registered function from the first component that registered the route:
oninit: function() {
// self out here has new guid
var self = this;
route('/', getFeatured, getTrending);
function getFeatured(context, next) {
// self in here has "r-1"
next();
...
It doesn't seem page.js has the concept of unregistering. One option is to move the route registration to the root component and route to the child component:
// in root:
route('/', function(){
this.findComponent('homepage').getFeatured()
}, function(){
this.findComponent('homepage').getTrending()
}
// in homepage component, add as methods:
getFeatured: function(context, next){
...
},
getTrending: function(context, next){
...
},
Marty's insight enabled me to see what was going wrong, so he gets the accepted answer, but here is my solution to the problem.
I made a subclass that provides a route function which wraps any route's functions in a callback that rebinds them to the current component. It does assume some global scope which I try to avoid, but is a necessary evil in this case.
//Controller.js
module.exports = Ractive.extend({
route: function(path, fn) {
var self = this;
var fns = [].slice.call(arguments, 1).map(function(fn) {
return function() {
// assumes `main` is global — the root Ractive instance of your app
self = main.findComponent(self.component.name)
fn.apply(self, arguments);
}
});
// also assumes `page` is a global
page.apply(this, [path].concat(fns));
}
});
And for the component:
return Controller.extend({
...
oninit: function() {
this.route('/', getFeatured, getTrending);
//the `this` in `getFeatured()` will now be the correct instance
},
...
});
}

Categories