Is it possible to use vue-router without a component in vue 2? - vue.js

I have a very simple page, just a few lines of code and I would like to use vue-router without a vue-component.
I would like to get the string "my-search-query" after the root:
www.example.com/my-search-query
and use it in my main Vue() object. Is this possible?
Thank you!

It is possible to use vue-router without a vue-component and still be notified about path changes in the main Vue() app object:
You can get the "my-search-query" string from www.example.com/#/my-search-query like this:
const router = new VueRouter({
routes: [
{ path: '/:question' }
]
})
const app = new Vue({
el: '#app',
router,
watch: {
'$route.params.question': function(newVal, oldVal) {
alert(newVal === 'my-search-query');
}
}
});
Thanks to #samayo for pointing me in the right direction!

Since the router is part of your view model instance, you can create a computed function that returns the path value of the router. For example, you can do the following:
data: function() {
router: new VueRouter()
},
computed: function() {
routerPath: function() {
return this.router.currentRoute.path;
}
},
watch: function() {
routerPath: function(val) {
switch(val) {
case '/':
// #TODO: logic for home
break;
case '/path1':
var query = this.router.currentRoute.query;
// #TODO: logic form query params
break;
}
}
}
With this, the router will manage the history for you, but you don't have to divide your code up into components. For example, I have a pretty small page and just wanted to put everything into the same viewModel without breaking everything up into components.

As tom_h has said you can use window.location.pathname if you just want to get the path name. If you want to be notified when this pathname changes, then first initialize it to your data(){} object and watch it as:
data () {
return {
path: window.location.pathname
}
},
watch: {
path (newpath, oldpath) {
alert('location path just changed from ' + newpath + ' to ' + oldpath )
}
}

Related

Can not catch Vuex state change in my Vue component

I have a Vuex store where I have a getter which works correctly and I can see the changes on the state. But if I call this getter as computed property in component it does not work. The value is still the same.
The store code looks like:
mutations: {
UPDATE_SERVER_FILTERS(state, payload) {
this._vm.$set(state, 'serverFilters', payload);
//state.serverFilters = payload; // Both patterns work
},
getters: {
serverFilters(state) {
return state.serverFilters; // This works fine
}
}
}
The component code:
computed: {
serverFilters() {
return this.$store.getters[this.storeName + '/serverFilters'];
},
}
Here is JSFiddle example https://jsfiddle.net/camo/je0gw9t3/4/ which works fine. And it is a problem cause in my project it does not work. I am prepared to die...
How can I solve it?
In the most bottom part:
new Vue({
store,
el: '#example',
data() {
return {};
},
computed: {},
methods: {
changeFilters() {
this.$store.dispatch(this.storeName + '/updateFilters');
// ^^^^^^^^^^^^^^ there is no storeName
},
},
});
The changeFilters method. You are using this.storeName, but there is no this.storeName! Just like the Child component, add storeName: 'a' to the data() then it should work.
https://jsfiddle.net/4yfv3w87/
Here is the debug process for your reference:
First open the Vue Devtools and switch to the timeline tab. And just click the button, you will see that there is no action is being fired. So the problem must be the one who dispatches the action. And then you will notice that the root component doesn't have a storeName.
So don't panic, just try to trace the code. It will only take a few minutes to find out the issue!
Computed properties might have problem to make an observer reference from returned value out of function. Instead of chaining getters and computed properties, why you don't use just getters or computed properties ? In my opinion, it's a bad practice to use them both, and I can't imagine a situation you need it. So if you need filter operations in many components, just make a getter and use getter in components instead of computed properties.
If you really want to chain them, try this:
new Vue({
store,
el: '#example',
data() {
return {
storeName: 'a'
}
},
computed: {
filters() {
get() {
return this.$store.getters[`${this.storeName}/getFilters`];
}
set(newValue) {
this.$store.dispatch(this.storeName + '/updateFilters');
}
},
},
})
Comment please if someone check it. I don't know are it works.

vue.js two way data-binding between components

Please take a look at this not-working pseudo code:
Vue.component('child', {
props: [],
template: '<div><input v-model="text"></div>',
data: function() {
return {child-text: ""}
}
})
Vue.component('parent', {
template: '<h1> {{text}} </h1>'
data: function() {
return {parent-text: ""}
}
})
What is the most elegant way to fix this code that whenever the user changes the content of input box in child component, then the variable child-text in child component and the variable parent-text in parent component will change automatically? I also want that if the variable child-text and/or parent-text change then the content of input box will change respectively?
I solved this with my own little data store, its a very simple approach but works good enough for me without the necessity to dive into Vuex.
First, I create my data store somewhere before initializing anything else.
window.globalData = new Vue({
data: {
$store: {}
},
});
After that, I add a global Mixin that allows to get and set data to the global storage.
Vue.mixin({
computed: {
$store: {
get: function () { return window.globalData.$data.$store },
set: function (newData) { window.globalData.$data.$store = newData; }
}
}
});
Then, every component can access the data storage by this.$store. You can check a working example here:
https://codesandbox.io/s/62wvro7083

what is vuex-router-sync for?

As far as I know vuex-router-sync is just for synchronizing the route with the vuex store and the developer can access the route as follows:
store.state.route.path
store.state.route.params
However, I can also handle route by this.$route which is more concise.
When do I need to use the route in the store, and what is the scenario in which I need vuex-router-sync?
Here's my two cents. You don't need to import vuex-router-sync if you cannot figure out its use case in your project, but you may want it when you are trying to use route object in your vuex's method (this.$route won't work well in vuex's realm).
I'd like to give an example here.
Suppose you want to show a message in one component. You want to display a message like Have a nice day, Jack in almost every page, except for the case that Welcome back, Jack should be displayed when the user's browsing top page.
You can easily achieve it with the help of vuex-router-sync.
const Top = {
template: '<div>{{message}}</div>',
computed: {
message() {
return this.$store.getters.getMessage;
}
},
};
const Bar = {
template: '<div>{{message}}</div>',
computed: {
message() {
return this.$store.getters.getMessage;
}
}
};
const routes = [{
path: '/top',
component: Top,
name: 'top'
},
{
path: '/bar',
component: Bar,
name: 'bar'
},
];
const router = new VueRouter({
routes
});
const store = new Vuex.Store({
state: {
username: 'Jack',
phrases: ['Welcome back', 'Have a nice day'],
},
getters: {
getMessage(state) {
return state.route.name === 'top' ?
`${state.phrases[0]}, ${state.username}` :
`${state.phrases[1]}, ${state.username}`;
},
},
});
// sync store and router by using `vuex-router-sync`
sync(store, router);
const app = new Vue({
router,
store,
}).$mount('#app');
// vuex-router-sync source code pasted here because no proper cdn service found
function sync(store, router, options) {
var moduleName = (options || {}).moduleName || 'route'
store.registerModule(moduleName, {
namespaced: true,
state: cloneRoute(router.currentRoute),
mutations: {
'ROUTE_CHANGED': function(state, transition) {
store.state[moduleName] = cloneRoute(transition.to, transition.from)
}
}
})
var isTimeTraveling = false
var currentPath
// sync router on store change
store.watch(
function(state) {
return state[moduleName]
},
function(route) {
if (route.fullPath === currentPath) {
return
}
isTimeTraveling = true
var methodToUse = currentPath == null ?
'replace' :
'push'
currentPath = route.fullPath
router[methodToUse](route)
}, {
sync: true
}
)
// sync store on router navigation
router.afterEach(function(to, from) {
if (isTimeTraveling) {
isTimeTraveling = false
return
}
currentPath = to.fullPath
store.commit(moduleName + '/ROUTE_CHANGED', {
to: to,
from: from
})
})
}
function cloneRoute(to, from) {
var clone = {
name: to.name,
path: to.path,
hash: to.hash,
query: to.query,
params: to.params,
fullPath: to.fullPath,
meta: to.meta
}
if (from) {
clone.from = cloneRoute(from)
}
return Object.freeze(clone)
}
.router-link-active {
color: red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.js"></script>
<div id="app">
<p>
<router-link to="/top">Go to Top</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
fiddle here
As you can see, the components are well decoupled from vuex and vue-router's logic.
This pattern sometimes works really effectively for the case that you're not concerned about the relationship between current route and the value returned from vuex's getter.
I saw this thread when I was learning Vue. Added some of my understanding on the question.
Vuex defines a state management pattern for Vue applications. Instead of defining component props and passing the shared state through props in all the places, we use a centralized store to organize the state shared by multiple components. The restriction on state mutation makes the state transition clearer and easier to reason about.
Ideally, we should get / build consistent (or identical) views if the provided store states are the same. However, the router, shared by multiple components, breaks this. If we need to reason about why the page is rendered like it is, we need to check the store state as well as the router state if we derive the view from the this.$router properties.
vuex-router-sync is a helper to sync the router state to the centralized state store. Now all the views can be built from the state store and we don't need to check this.$router.
Note that the route state is immutable, and we should "change" its state via the $router.push or $router.go call. It may be helpful to define some actions on store as:
// import your router definition
import router from './router'
export default new Vuex.Store({
//...
actions: {
//...
// actions to update route asynchronously
routerPush (_, arg) {
router.push(arg)
},
routerGo (_, arg) {
router.go(arg)
}
}
})
This wraps the route updates in the store actions and we can completely get rid of the this.$router dependencies in the components.

Cant mount children component to ref

I have a problem with VuePaginator , that I can mount it to my Vue app $refs properties. I am doing everyting according to docs, here is my component in the html:
<v-paginator :resource.sync="comments" ref="vpaginator" resource_url="{{route('api.item.comments', $item->pk_i_id)}}"></v-paginator>
The pagination works correctly, but I can't trigger fetchData() from the vuejs code, because paginator is not getting mounted to vm.$refs.vpaginator.
Here is the code that I use:
var app = new Vue({
el: '#comments',
data : {
comments: [],
newComment: {
text: ""
}
},
components: {
VPaginator: VuePaginator
},
methods: {
addComment: function(comment){
var vm = this;
this.$http.post($('meta[name="item-url"]').attr('content'), comment)
.then(function(response){
toastr.success(response.data.result);
comment.text = "";
vm.$.vpaginator.fetchData();
}).catch(function (error) {
if(error.data){
toastr.error(error.data.text[0]);
}
})
},
logRefs: function(){
console.log(this.$refs.vpaginator);
}
}
});
I have created logRefs() function to check the $ref property and it is always undefined.
Since you are using the Version 1 of VueJS, usage is a bit different - check this demo http://jsbin.com/rupogesumo/edit?html,js,output
<v-paginator :resource.sync="comments" v-ref:vpaginator resource_url="{{route('api.item.comments', $item->pk_i_id)}}"></v-paginator>
Docs Reference: https://v1.vuejs.org/api/#v-ref

Handle methods differently in Vue depending on mobile or not

I'm having trouble setting up my Vue components to handle their methods differently if the user is on mobile. For instance a navigation drop down, if a user clicks on a link, I want to prevent them from going to that location, but instead drop down the drop down. Whereas on desktop, I want them to go to it if they click on it and only drop down on hover. I'll need this for so many other aspects of my project.
I have a main Vue instance:
var Main = new Vue({
el: 'body',
data: {
mobile: true
},
ready: function() {
if( document.clientWidth >= 992 )
{
this.mobile = false;
}
}
});
export default Main;
Then for my components, I'm doing something like this:
import Main from './../Main';
var NavLink = Vue.component('navlink', {
template: '#nav-link-template',
replace: true,
data: function() {
return {
}
},
props: ['text', 'url'],
ready: function() {
},
methods: {
handleClick: function(e) {
e.preventDefault();
console.log(Main.mobile);
if( Main.mobile )
{
if( this.$children.length )
{
// Has dropdown
this.$children[0].dropDown();
}
else
{
// No dropdown so redirect user
window.location = this.url;
}
}
else
{
// Not mobile so let user go
window.location = this.url;
}
}
}
});
Not only does Main.mobile return the default value no matter what resolution because their ready methods seem to run BEFORE the Main ready method.. but this also feels like the wrong setup.
Thanks for any insight.
First, according to you code, you dont need Main commonjs module to be a vuejs instance. Make it as a simple js object
Main = {
mobule: document.clientWidth >= 992
}
export default Main;
Or you may want to handle client window size dynamically
var Main = new Vue({
created: function() {
// dunno why v-on="resize: func" not working
global.window.addEventListener('resize', function () {
//calc width and heigh somehow
self.$broadcast('resize', width, heigh);
});
}
});
export default Main;