I'm new to Vue and managed to make my first app with some glitches but I'm really enjoying it so far. I used a video tutorial which jump started with vue-cli project creation which as turns out is a litte different due to webpack.
I've created the project, the project does mostly what it should right now I'm trying to do some refactoring which includes DRYing out the code.
On each page I would like to access a variable stored in the cookie file I've done the saving and reading on the HomeComponent in the script section which works as promised.
<script>
import MenuComponent from '#/components/MenuComponent.vue'
import Typewriter from '#/components/vue-type-writer.vue'
export default {
name: 'HomeComponent',
components: {
MenuComponent,
Typewriter
},
prop:{
isPlaying: Boolean,
username: String,
currentSound: Object
},
data() {
return{
currentSound: null,
isPlaying: false,
username: ''
}
},
methods:{
clickButton() {
this.msg= 'test 2'
},
toggleSound(){
var a = this.currentSound;
if (a.paused) {
a.play();
this.isPlaying = true;
} else {
a.pause();
this.isPlaying = false;
}
},
getCookieInfo(){
var value = "; " + document.cookie;
var parts = value.split("; weegreename=");
if (parts.length == 2)
this.username = parts.pop().split(";").shift();
else this.username = '';
},
seveFormValues (submitEvent) {
this.username = submitEvent.target.elements.name.value;
this.$refs.audio1.pause();
this.$refs.audio2.play();
var expires = "";
var days = 31;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = "weegreename=" + (this.username || "") + expires + "; path=/";
}
},
mounted(){
this.isPlaying = true;
this.getCookieInfo();
if (this.username) this.currentSound = this.$refs.audio2;
else this.currentSound = this.$refs.audio1;
this.currentSound.play();
}
}
</script>
Now on every sub page I would like to access the getCookieInfo() method to check id the username is set.
I've tried to add this in the main App.vue script section, in the main.js
new Vue({
router,
render: h => h(App),
methods: {
//here the getCookieInfo code from above
}
}).$mount('#app')
created a new component whit the methods and then tried to access them in the main app via componentname.method as below.
import CookieComponent from '#/components/CookieComponent.vue'
export default {
// prop:{
// isToggled: Boolean
// },
components: {
MenuComponent,
CookieComponent
},
data() {
return{
isToggled: false
}
},
methods:{
clickToggle() {
this.isToggled = !this.isToggled;
},
},
mounted(){
CookieComponent.getCookieInfo();
}
}
I don't know right now the best approach and I will learn more in the future but this project is time sensitive - I decided to learn vue by making a simple site for my client :)
If you need it on every page it can be put into your App.vue. From there you have three options:
Pass the data as a prop to child components.
Create an event bus and emit the data to whichever component needs it.
Use Vuex to store the data and access it from your components.
If you really want to keep your cookie data inside the component you need to emit it up your component chain.
https://v2.vuejs.org/v2/guide/components.html#Emitting-a-Value-With-an-Event
Depending on how deep your chain goes and how many sibling components you have this can get really messy and in those cases Vuex or an event bus might be a better idea.
Do not try to do things like:
CookieComponent.getCookieInfo();
Please review the documentation to see good example on how to do component communication.
For that kind of stuff, the best practice is to use a state. It will save data of your application and will allow you to access them accross all components/pages.
You can see a simple state management in the Vue doc, or directly use VueX, the official state management library for Vue.
To sum up how it works (with VueX):
You create a cookieStore:
// Where data will be saved
const state = { cookie: {} }
// Getters allow you to access data
const getters = { cookie: state => state.cookie }
// Mutations allow you to modify the state
const mutations = {
// Set cookie data
saveCookie (state, cookieData) {
state.cookie = cookieData
}
}
In your HomeComponent, you will get the cookie info, and save it in
the store: this.$store.commit('saveCookie', cookieData)
In all other components, instead of getting the cookie info from the cookie, you can access the saved data from the store and do what you want with it: this.$store.getters.cookie
Related
This may be a really dumb question, but after reading the state management documentation of vue.js, i'd like to play around with the store pattern.
I noticed that the store.state is shared among the two apps in the example. But how would i now call the setMessageAction method of the store from within a component? Shouldn't the store be somehow injected into/registered with the vue instance in order to be accessible via this from within a component or something like that?
Yes, you are correct.
You should declare your store in your component declaration as described here
document.js
var store = {
debug: true,
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}
var vmA = new Vue({
data: {
privateState: {},
<!-- HERE YOU ARE PASSING THE STATE -->
sharedState: store.state
}
})
This is my Login.vue:
mounted() {
if (localStorage.login) this.$router.go(-1);
},
methods: {
axios.post(ApiUrl + "/login") {
...
}
then(response => {
...
localStorage.login = true;
this.$router.go(0); /* Reload local storage */
})
}
App.vue:
mounted() {
axios
.get("/user")
.then(response => {
localStorage.user_id = response.data.user.id;
localStorage.package_id = response.data.user.package_id;
})
},
Project.vue:
mounted() {
this.user_id = localStorage.user_id
this.package_id = localStorage.package_id
}
With that above code, I cannot get localStorage.user_id and localStorage.package_id as I expected. But if I change like the follow, it worked.
mounted() {
const self = this
setTimeout(function () {
self.user_id = localStorage.user_id
self.package_id = localStorage.package_id
self.getProject();
},1000)
}
But I think setTimeout not good in that case. Is there any way to refactor this code?
Thank you!
Try this: in your root component (it's usually const app = new Vue({ ... })) write the following:
import {localStorage} from 'localStorage'; // import your module if necessary
// this is relative to the way you manage your dependencies.
const app = new Vue({
//...
data: function() {
return {
localStorage: localStorage;
}
}
})
Now whenever you want to use localStorage, access it from root component like this:
this.$root.localStorage
Hope this solves your problem.
Don't know your project structure ,but I guess it's probably an async issue. You got the user information async, so when Project.vue mounted, the request is not complete yet. As a result, the localstorage is empty at the monent.
There are two solutions for this:
Make sure Project.vue is not rendered before userinfo is complete. For example, things like <project v-if="userinfo.user_id" /> should works.
Use some data binding libary like vuex to bind userinfo to Project.vue instead of assign it in lifecycle like mounted or created.
Hope it helps.
I'm facing a weird issue with my Electron + Vue setup.
CONDITIONS: Electron + Vue (I used a boilerplate) + vuex-persist (also tried vuex-persistedstate and vuex-persistfile).
PROBLEM: Vuex getters remain 0/null/'' when the store is being rehydrated. How do I know that? If the local storage is clean (I launch the app for the first time), first mutations update state (getters return correct values) and I can see an object being added to the browser's local storage. When the app is restarted, however, mutations trigger state and local storage update just like before, BUT getters remain empty/default. Below getter returns an empty array.
SETUP: I have an app that works with 3rd party API: gets data, calculates stuff and sends some data back. API also requires authorization. Below is my Vuex structure.
Part of my state object...
const state = {
token: '',
projects: [],
work_packages: [],
timeEntriesLocal: []
}
...and one of my getters:
const getters = {
todayLog () {
function sameDay (d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate()
}
var entries = state.timeEntriesLocal
var todayEntries = []
entries.forEach(element => {
var date = element.spentOn
var today = new Date()
if (sameDay(date, today)) {
todayEntries.push(element)
}
})
return todayEntries
}
}
It returns entries from an array that are due to "today".
timeEntriesLocal is filled with this method:
addTimeEntry () {
let entry = {
id: this.$store.state.UserData.timeEntriesLocal.length + 1,
project: this.getItemById(this.$store.state.UserData.current.project_id, this.$store.state.UserData.projects),
spentOn: new Date(),
comment: this.comment,
activityId: this.activityId,
workPackage: this.getItemById(this.$store.state.UserData.current.work_package_id, this.$store.state.UserData.work_packages),
hours: this.precisionRound(this.$store.state.UserData.duration / 60 / 60, 2),
published: false,
loading: false,
infoMessage: ''
}
this.$store.commit('ADD_LOCAL_ENTRY', entry)
}
And lastly here's the mutation that I have just used above:
ADD_LOCAL_ENTRY (state, entry) {
state.timeEntriesLocal.unshift(entry)
}
The changes in timeEntriesLocal are not being picked up, since you're changing the length of the Array. This is a limitation of JavaScript covered in Common Gotchas section of the docs. A way to go around it is also covered in Vuex`s docs:
Mutations Follow Vue's Reactivity Rules
Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components
observing the state will update automatically. This also means Vuex
mutations are subject to the same reactivity caveats when working with
plain Vue:
Prefer initializing your store's initial state with all desired fields
upfront.
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one. For example, using the object
spread syntax we can write it like this: state.obj = { ...state.obj, newProp: 123 }
So in your exmample, to make the Vue (Vuex) detect these changes, you can do something like:
ADD_LOCAL_ENTRY (state, entry) {
state.timeEntriesLocal.unshift(entry);
Vue.set(state, 'timeEntriesLocal', state.timeEntriesLocal);
}
I'm attempting to watch for localstorage:
Template:
<p>token - {{token}}</p>
Script:
computed: {
token() {
return localStorage.getItem('token');
}
}
But it doesn't change, when token changes. Only after refreshing the page.
Is there a way to solve this without using Vuex or state management?
localStorage is not reactive but I needed to "watch" it because my app uses localstorage and didn't want to re-write everything so here's what I did using CustomEvent.
I would dispatch a CustomEvent whenever you add something to storage
localStorage.setItem('foo-key', 'data to store')
window.dispatchEvent(new CustomEvent('foo-key-localstorage-changed', {
detail: {
storage: localStorage.getItem('foo-key')
}
}));
Then where ever you need to watch it do:
mounted() {
window.addEventListener('foo-key-localstorage-changed', (event) => {
this.data = event.detail.storage;
});
},
data() {
return {
data: null,
}
}
Sure thing! The best practice in my opinion is to use the getter / setter syntax to wrap the localstorage in.
Here is a working example:
HTML:
<div id="app">
{{token}}
<button #click="token++"> + </button>
</div>
JS:
new Vue({
el: '#app',
data: function() {
return {
get token() {
return localStorage.getItem('token') || 0;
},
set token(value) {
localStorage.setItem('token', value);
}
};
}
});
And a JSFiddle.
The VueJs site has a page about this.
https://v2.vuejs.org/v2/cookbook/client-side-storage.html
They provide an example.
Given this html template
<template>
<div id="app">
My name is <input v-model="name">
</div>
<template>
They provide this use of the lifecycle mounted method and a watcher.
const app = new Vue({
el: '#app',
data: {
name: ''
},
mounted() {
if (localStorage.name) {
this.name = localStorage.name;
}
},
watch: {
name(newName) {
localStorage.name = newName;
}
}
});
The mounted method assures you the name is set from local storage if it already exists, and the watcher allows your component to react whenever the name in local storage is modified. This works fine for when data in local storage is added or changed, but Vue will not react if someone wipes their local storage manually.
Update: vue-persistent-state is no longer maintained. Fork or look else where if it doesn't fit your bill as is.
If you want to avoid boilerplate (getter/setter-syntax), use vue-persistent-state to get reactive persistent state.
For example:
import persistentState from 'vue-persistent-state';
const initialState = {
token: '' // will get value from localStorage if found there
};
Vue.use(persistentState, initialState);
new Vue({
template: '<p>token - {{token}}</p>'
})
Now token is available as data in all components and Vue instances. Any changes to this.token will be stored in localStorage, and you can use this.token as you would in a vanilla Vue app.
The plugin is basically watcher and localStorage.set. You can read the code here. It
adds a mixin to make initialState available in all Vue instances, and
watches for changes and stores them.
Disclaimer: I'm the author of vue-persistent-state.
you can do it in two ways,
by using vue-ls and then adding the listener on storage keys, with
Vue.ls.on('token', callback)
or
this.$ls.on('token', callback)
by using storage event listener of DOM:
document.addEventListener('storage', storageListenerMethod);
LocalStorage or sessionStorage are not reactive. Thus you can't put a watcher on them. A solution would be to store value from a store state if you are using Vuex for example.
Ex:
SET_VALUE:(state,payload)=> {
state.value = payload
localStorage.setItem('name',state.value)
or
sessionStorage.setItem('name',state.value)
}
Ok I'll try to explain this the best I can. I have a ResourceInfo component that posts data to the /resources/ path and /users/ + uid + /created-resources path using newPostKeyand update.
I also have a QuizBuilder component. I want to post data from this component to a /resources/ + newPostKey + /quiz/ path. However, I don't know how to get the newPostKeyor key from that particular path I created in ResourceInfo from the QuizBuilder component.
Here are the two components. First the user adds info using the ResourceInfo component. Once they hit submit they go to the QuizBuilder component where they create the quiz.
ResourceInfo.vue
export default {
name: 'resource-info',
data () {
return {
header: 'Before you build your quiz we just need some quick info.',
sharedState: store.state,
resource: {
type: '',
title: '',
url: '',
desc: '',
timesPassed: 0,
authorId: store.state.userInfo.uid,
authorName: store.state.userInfo.displayName,
authorImage: store.state.userInfo.photoURL
},
}
},
methods: {
saveToFirebase () {
var newPostKey = firebase.database().ref().child('resources').push().key;
var updates = {};
updates['/resources/' + newPostKey] = this.resource;
updates['/users/' + store.state.userInfo.uid + '/created-resources/' + newPostKey] = this.resource;
// Clear inputs
this.resource.title = '',
this.resource.type = '',
this.resource.desc = '',
this.resource.url = ''
console.log("Saving resource data...")
return firebase.database().ref().update(updates);
}
}
}
QuizBuilder.vue
export default {
name: "quiz-builder",
data () {
return {
questions: [createNewQuestion()],
showQuestions: false
}
},
methods: {
addQuestion () {
this.questions.push(createNewQuestion())
},
addOption (question) {
question.options.push(createNewOption())
},
saveToFirebase (e) {
e.preventDefault();
var questions = this.questions;
this.firebaseRef = db.ref('a/path/here'); // /resources/ + that resources id + /quiz/
this.firebaseRef.push({ // Should I use set or push here?
questions
})
console.log('Saving quiz data...')
}
}
}
The answer depends on how the transition between the components/pages are made.
If you're building a single page app with vue-router or something, then the transition is replacing the former component with the latter, which all happens on the index.html, with no request sent(simplest situation). To still keep the generated key within our grasp after the first component is gone, you need to save it on a common parent of the two components. To be specific, add a key in the parent's data, and let the ResourceInfo emit a custom event with the generated key to notify the parent to set its key. See http://vuejs.org/guide/components.html#Using-v-on-with-Custom-Events .
If you refreshes the page when jumping from ResourceInfo to to Quiz, with server-side rendering (which should be really rare, since it requires more effort compared to the single-page way, and has an inferior performance), then it's irrelavent to vue and rather simple: redirect the user to Quiz after ResourceInfo is saved, with the key as a url param.
Edit upon OP's using store.js:
Just store the key in LocalStorage(store.js) and retrive it from another component should work since LocalStorage is available globally and even across pages/sessions.
Some thought: main.js just be the parent is in some sense right. There's no real parent vue component here, but our main.js is evaled by the browser in the global scope, so it's true that main.js is the root entry of our app, aka parent.