I am trying to understand Vue 3 data management for Shopify Theme. After going through my old code which is based on Vue 2, I cannot update the data object by changing value in methods function.
Below is the snippet with issue.
Vue.createApp({
delimiters: ['${', '}'],//for NO CONFLICT with liquid theme
data: () => {
return {
message: 'Hello Vue'
}
}, //data ends
methods: {
setMessage: (params) => {
//setting new message
this.message = params;
}
}, //methods ends
}).mount('#app1')
<script src="https://unpkg.com/vue#3.2.31/dist/vue.global.js"></script>
<div id="app1">
<h5>${ message }</h5>
<button v-on:click="setMessage('new message')">Submit</button>
</div>
Define method like this setMessage(params) { not like this setMessage: (params) => {
Related
I want to ask how do I rewrite vue js variable data when I use pusher on vue js.
In this case the pusher I have will change the data every 5 minutes but here I don't rewrite the previous variable.
Usually I only use:
<template>
<div class="animated fadeIn">
<b-card>
<b-card-header>
Record User
</b-card-header>
<b-card-body>
<div>
<h3>Name : {{ name }}</h3>
<h4>Email : {{ email }}</h4>
</div>
</b-card-body>
</b-card>
</div>
</template>
<script>
import Pusher from 'pusher-js'
export default {
name: 'Index',
data() {
return {
name: 'John Doe',
email: 'jdoe#gmail.com'
}
},
created () {
this.subscribe()
},
methods: {
subscribe () {
let pusher = new Pusher(process.env.VUE_APP_PUSHER_KEY, { cluster: 'ap1', forceTLS: true })
pusher.subscribe('users')
pusher.bind('list', data => {
console.log(data);
this.name = data.name
this.email = data.email
})
},
},
}
</script>
But it hasn't changed, please help.
Thank you
The problem is that pusher will append it's own context during bind. There is a way to get around it though
bind function allows you to pass the context as the 3rd parameter. You can pass this after the handler like this:
subscribe () {
let pusher = new Pusher(process.env.VUE_APP_PUSHER_KEY, { cluster: 'ap1', forceTLS: true })
pusher.subscribe('users')
pusher.bind('list', data => {
console.log(data);
this.name = data.name
this.email = data.email
}, this) // <=== pass this as context
},
ref: https://pusher.com/docs/channels/using_channels/events#binding-with-optional-this-context
if that doesn't work, you can also use the that var, which should escape the context issue.
subscribe () {
let that = this;
let pusher = new Pusher(process.env.VUE_APP_PUSHER_KEY, { cluster: 'ap1', forceTLS: true })
pusher.subscribe('users')
pusher.bind('list', data => {
console.log(data);
that.name = data.name
that.email = data.email
})
},
You might want to try the vue-pusher library which might handle the context to be more vue-friendly.
https://www.npmjs.com/package/vue-pusher
Why does that work?
there's nothing special about that, but in javascript this is a special variable that references the context. In some cases, when dealing with callback functions, the context changes. assigning this to a new variable that, stores the context of the vue method in a variable that you can then reference it even if, in this case, Pusher bind function binds a different context.
I need global variables for errors. But I don't want set input variable for every component.
How I can watch $errors in component ABC without input variable?
(without <abc :errors="$errors"></abc>)
index.js:
Vue.prototype.$errors = {};
new Vue({
el: '#app',
render: h => h(App),
}
App.vue:
...
name: 'App',
components: {
ABC
}
...
methods:{
getContent() {
this.$errors = ...from axis...
}
Component ABC:
<template>
<div>{{ error }}</div>
</template>
...
watch: {
???
}
Here's an example of how it could be done:
const errors = Vue.observable({ errors: {} })
Object.defineProperty(Vue.prototype, '$errors', {
get () {
return errors.errors
},
set (value) {
errors.errors = value
}
})
new Vue({
el: '#app',
methods: {
newErrors () {
// Generate some random errors
const errors = {}
for (const property of ['name', 'type', 'id']) {
if (Math.random() < 0.5) {
errors[property] = 'Invalid value'
}
}
this.$errors = errors
}
}
})
new Vue({
el: '#app2',
watch: {
$errors () {
console.log('$errors has changed')
}
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<pre>{{ $errors }}</pre>
<button #click="newErrors">New errors</button>
</div>
<div id="app2">
<pre>{{ $errors }}</pre>
</div>
I've created two Vue instances to illustrate that the value really is shared. Clicking the button in the first instance will update the value of $errors and the watch is triggered in the second instance.
There are a few tricks in play here.
Firstly, reactivity can only track the reading and writing of properties of an observable object. So the first thing we do is create a suitable object:
const errors = Vue.observable({ errors: {} })
We then need to wire this up to Vue.prototype.$errors. By defining a get and set for that property we can proxy through to the underlying property within our observable object.
All of this is pretty close to how data properties work behind the scenes. For the data properties the observable object is called $data. Vue then uses defineProperty with get and set to proxy though from the Vue instance to the $data object, just like in my example.
as Estradiaz said:
You can use Vuex and access the value outside of Vue like in this answer: https://stackoverflow.com/a/47575742/10219239
This is an addition to Skirtles answer:
You can access such variables via Vue.prototype.variable.
You can set them directly, or use Vue.set, it works either way.
My code (basically the same as Skirtless):
main.js
const mobile = Vue.observable({ mobile: {} });
Object.defineProperty(Vue.prototype, '$mobile', {
get() { return mobile.mobile; },
set(value) { mobile.mobile = value; }
});
function widthChanged() {
if (window.innerWidth <= 768) {
if (!Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', true);
} else if (Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', false);
}
window.addEventListener("resize", widthChanged);
widthChanged();
Home.vue:
watch: {
'$mobile'(newValue) {
// react to Change in width
}
}
I have 2 components OperatorsList and OperatorButton.
The OperatorsList contains of course my buttons and I simply want, when I click one button, to update some data :
I emit select with the operator.id
This event is captured by OperatorList component, who calls setSelectedOperator in the store
First problem here, in Vue tools, I can see the store updated in real time on Vuex tab, but on the Components tab, the operator computed object is not updated until I click antoher node in the tree : I don't know if it's a display issue in Vue tools or a real data update issue.
However, when it's done, I have another computed property on Vue root element called selectedOperator that should return... the selected operator : its value stays always null, I can't figure out why.
Finally, on the button, I have a v-bind:class that should update when the operator.selected property is true : it never does, even though I can see the property set to true.
I just start using Vue, I'm pretty sure I do something wrong, but what ?
I got the same problems before I used Vuex, using props.
Here is my OperatorList code :
<template>
<div>
<div class="conthdr">Operator</div>
<div>
<operator-button v-for="operator in operators" :op="operator.id"
:key="operator.id" #select="selectOp"></operator-button>
</div>
</div>
</template>
<script>
import OperatorButton from './OperatorButton';
export default {
name: 'operators-list',
components : {
'operator-button': OperatorButton
},
computed : {
operators() { return this.$store.getters.operators },
selected() {
this.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
},
},
methods : {
selectOp(arg) {
this.$store.commit('setSelectedOperator', arg);
}
},
}
</script>
OperatorButton code is
<template>
<span>
<button type="button" v-bind:class="{ sel: operator.selected }"
#click="$emit('select', {'id':operator.id})">
{{ operateur.name }}
</button>
</span>
</template>
<script>
export default {
name: 'operator-button',
props : ['op'],
computed : {
operator() {
return this.$store.getters.operateurById(this.op);
}
},
}
</script>
<style scoped>
.sel{
background-color : yellow;
}
</style>
and finally my app.js look like that :
window.Vue = require('vue');
import Vuex from 'vuex';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
const store = new Vuex.Store({
state: {
periods : [],
},
mutations: {
setInitialData (state, payload) {
state.periods = payload;
},
setSelectedOperator(state, payload) {
this.getters.operateurs.forEach( op => {
op.selected = (op.id==payload.id)
})
},
},
getters : {
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
// op.selected=false; //replaced after Radu Diță answer by next line :
if (ops.selected === undefined) op.selected=false;
})
return ops;
}
},
operatorById : (state, getters) => (id) => {
return getters.operators.find(operator => operator.id==id);
},
}
});
import Chrono from './components/Chrono.vue';
var app = new Vue({
el: '#app',
store,
components : { Chrono },
mounted () {
this.$store.commit('setInitialData',
JSON.parse(this.$el.attributes.initialdata.value));
},
computed: {
...mapState(['periods']),
...mapGetters(['operators', 'operatorById']),
selectedOperator(){
this.$store.getters.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
}
},
});
Your getter in vuex for operators is always setting selected to false.
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
op.selected=false;
})
return ops;
}
}
I'm guessing you do this for initialisation, but that's a bad place to put it, as you'll never get a selected operator from that getter. Just move it to the proper mutations. setInitialData seems like the right place.
Finally I found where my problems came from :
The $el.attributes.initialdata.value came from an API and the operator objects it contained didn't have a selected property, so I added it after data was set and it was not reactive.
I just added this property on server side before converting to JSON and sending to Vue, removed the code pointed by Radu Diță since it was now useless, and it works.
I'm new with Vue.js, and I notice some content re-render after changing any data that is not part of that content, here is an example:
https://jsfiddle.net/gustavompons/rtxqhyv2/1/
HTML
<div id="app">
<input v-model="foo1">
<div v-html="showFoo1()"></div>
<div v-html="showFoo2()"></div>
</div>
JS
new Vue({
el: '#app',
data: {
foo1: 'foo1',
foo2: 'foo2'
},
methods: {
showFoo1 () {
console.log('this is ok to execute on input')
return this.foo1
},
showFoo2 () {
console.log('this should NOT execute on input')
return this.foo2
}
}
})
So every time I type on the input, I get "this should NOT re-render on input" in the console, which I think it's not ok because there is no reason to execute that piece of code every time.
Is this the way Vue work or am I doing something wrong?
I'm using vue.js v2
The results of methods are not cached and will be executed every time the component is re-rendered. If you want caching and dependency tracking, use computed properties instead:
computed: {
showFoo1 () {
console.log('this is ok to execute on input')
return this.foo1
},
showFoo2 () {
console.log('this should NOT execute on input')
return this.foo2
}
}
And get rid of the () when accessing them.
I've asked in IRC and slack but I can't get anyone to figure out why this isn't working.
//test-vue.blade.php
#extends('spark::layouts.app')
#section('content')
<div id="testing">
<input type="text" v-model="vin">
<button type="button" v-on:click="test">Click me</button>
</div>
#endsection
/* resources/assets/js/boostrap.js
|--------------------------------------------------------------------------
| Laravel Spark Components
|--------------------------------------------------------------------------
|
| Here we will load the Spark components which makes up the core client
| application. This is also a convenient spot for you to load all of
| your components that you write while building your applications.
*/
require('./../spark-components/bootstrap');
require('./home');
var f = new Vue({
el:'#testing',
data: {
items: '',
vin: 'vdfdfa'
},
created: function () {
console.log(this.vin);
this.test();
},
mounted: function (){
},
methods: {
test : function(){
alert('test function called');
},
getDetailByVin: function(){
console.log('testing');
var urlData = 'http://asisauto.app:8000/test_data.json';
// GET /someUrl
var self = this;
this.$http.get(urlData).then((response) => {
// success callback
console.log(response.body);
}, (response) => {
// error callback
console.log('error');
});
}
}
});
I've tried lots of things and can't figure out what happens.
Mounted gets called
so does
f.text(); if I place it after the declaration.
But the button press never works. there aren't any errors in the console either.