How to define property with a component? - javascript

I currently have three steps in a form that I want to show sequentially, so I created three components - one for each step of the process.
My app.js file:
import LocationList from './components/LocationList.vue';
import ChooseTime from './components/ChooseTime.vue';
import ChooseMethod from './components/ChooseMethod.vue';
Vue.component('location-list', LocationList);
Vue.component('choose-time', ChooseTime);
Vue.component('choose-method', ChooseMethod);
let store = {
isVisible: {
steps: {
one: true,
two: false,
three: false,
}
}
};
new Vue({
el: '#app-order',
data: store,
router
});
Now, when my one and only route is called,
import VueRouter from 'vue-router';
let routes = [
{
path: '/order',
component: require('./views/Order.vue')
}
];
export default new VueRouter({
routes
});
all these components are being loaded properly. The issue is that when I try to v-show them one at a time:
Order.vue:
<template>
// ...
<location-list v-show="isVisible.steps.one"></location-list>
<choose-time v-show="isVisible.steps.two"></choose-time>
<choose-method v-show="isVisible.steps.three"></choose-method>
// ...
</template>
<script>
</script>
<style>
</style>
The error message I receive is:
[Vue warn]: Property or method "isVisible" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
But when I check within Vue's browser extension, isVisible is defined within the root element?
As you can see it is in the root-element, but not inside the Order view though.
Thanks for any help!

In Vue, child components do not have direct access to data defined in their parents. You have to pass the data down.
I think you would probably save yourself a little trouble if you just defined isVisible in Order.vue. However, if you want to leave it where it is, you need to pass it into the component.
One easy way to do that is to define isVisble as a property of Order.vue and then pass it through your router-view.
<router-view :is-visible="isVisible"></router-view>
There are other ways of passing props to routes that are defined in the router documentation.
The reason I say you would save your self some trouble defining isVisible in Order.vue is because whenever you want to change the values of your steps, you will need to do it at the root as you currently have it defined.

Related

Enums in Vue and javascript

I want to make enums work in in vue file.
I first define my enums in a js file
const Status= Object.freeze({
Normal: "normal",
Loading: "loading",
Error: "error",
Done: "done",
});
export default Status;
My main.vue file can't compile:
<template>
<div v-if="status == AudioCardStatus.Normal">
</template>
import Status from "./../enums/status"
Error is
Property or method "Status" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I have already looked at another similar SO question but the solution seems to be to use Typescript.
You should add that imported object to the data option in order to be available for the template :
import Status from "./../enums/status"
export default{
data(){
return{
status:Status
}
}
}
First, You import it as Status but use it as AudioCardStatus, Also you need to asign it to property in your data.
Second, you should import it inside a script tag.
Third, you don't need the extra ./ this enough ../enums/status

Vue Mixin properties are blank/empty/not reactive

I hope this question is not a duplicate. If it is so, please point me to the right directions.
I have a Vue application which is compiled with Webpack#NPM. I use mixin to propagate a property (roles) across all components. I update it with an ajax call from app instantiation. Problem is roles only updates for the <Root> component, not for all others.
////////////////////////
// app.js
////////////////////////
// import
window.axios = require('axios')
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes.js'
// mixin
Vue.mixin({
data: function () {
return {
// property in question
roles: [],
}
},
methods: {
getRoles: function() { //////////// this method updates property.
// get
axios.get('/api/vcr/admin/roles')
// process
.then(response=>{
this.roles = response.data.data;
})
// error?
.catch(error=>{
this.toast(error.response.data.message);
})
},
},
});
// router
const router = new VueRouter({
mode: 'history',
routes: routes,
});
// app
const app = new Vue({
el: '#app',
components: { App: require('./views/App').default },
router,
base: '/saas/vcr/admin',
created: function() { ////////////// I update it here
this.getRoles();
}
});
////////////////////////
// Foo.vue
////////////////////////
<script>
export default {
mounted: function() {
console.log(this.roles) ////// returns an empty array
}
}
</script>
Do you know how to make roles reactive?
The global mixin you have created does not call the function that populates the roles property, it relies on the inheriting instance to do so. In your app "root" instance, you're doing that in the created life-cycle hook which calls getRoles on the mixin, but in the component Foo you are not calling it, so it will have its default empty value. The roles property is not shared, each component will get its own copy of it and will need to be populated.
You could change the mixin to do this for you, by adding the life-cycle created hook as you have done in the root instance. Here's an example of that. Note implementing that in the mix-in does not prevent or override later life cycle hooks from being run on the instances it is merged into. But, it will in your case make an API call for every component instance that is created, which probably isn't desirable.
If you want to only populate it once then share it between all components, it might make more sense to use Vuex and have a global state where roles is populated centrally and shared between all components in a reactive way.

In Vue, what parameters does createElement() actually get?

(This is a further question after this one: In Vue, what is the relationship of template, render, VNode?)
Found createElement()'s source code (from here):
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) { ... }
Code
main.js: (partly)
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App),
router
})
App.vue:
<template>
<div id="content">
<!-- <img src="./assets/logo.png" alt="">-->
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
Questions
A. In App.vue, export default {} will return an empty object, is that correct?
B. In main.js:
B-1. import App from './App.vue', will bind App to the empty object, is that correct?
B-2. render: h => h(App), if the answer to previous two questions were yes, then here h (which is alias of vue's createElement() function) will get a single parameter, for context with value as empty object, is that correct?
B-3. But, that doesn't make much sense, because the template are actually rendered in the browser in my test, it's not empty.
C. So, in the code above, how many parameters are passed to createElement(), and what is the value of each of them?
D. Which part did I misunderstand about Vue or ES6 in above description?
Single-file components, i.e. .vue files, go through special processing by Vue Loader. See:
https://vue-loader.vuejs.org/#what-is-vue-loader
This will interfere in the export/import process so that everything works as expected.
So:
In App.vue, export default {} will return an empty object, is that correct?
It would for a .js file. But for a .vue file it won't, not once it's been through Vue Loader.
Try console logging the value of App in main.js to see what actual gets imported. You should see a render function (compiled from your template) as well a few other little supporting pieces.
Update
To answer your follow-up question from the comments.
createElement just creates a VDOM node. It does not create the component instance for that node. Vue further processes the tree of VDOM nodes after they are returned from render and creates any child component instances it needs. It'll then call render on those components to generate the VDOM nodes that they need and so on until everything is rendered.
I've created a small example to try to illustrate this. It consists of a parent with two component children. Notice that the logging for creating and rendering the child components does not happen straight away when calling createElement. Instead it happens after the render function has returned.
const child = {
render (createElement) {
console.log('rendering the child')
return createElement('div', null, ['hello'])
},
created () {
console.log('child is created')
}
}
new Vue({
el: '#app',
components: {
child
},
created () {
console.log('parent is created')
},
render (createElement) {
console.log('rendering the parent')
const children = [
createElement('child'),
createElement('child')
]
const root = createElement('div', null, children)
console.log('finished calling createElement')
return root
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app"></div>
In App.vue, export default {} will return an empty object, is that correct?
No, .vue files are Single File Components that are compiled by vue-loader.
B. In main.js:
B-1. import App from './App.vue', will bind App to the empty object, is that correct?
No, see above.
B-2. render: h => h(App), if the answer to previous two questions were yes, then here h (which is alias of vue's createElement() function) will get a single parameter, for context with value as empty object, is that correct?
No. App is a component ready to be attached to a Vue Instance. import App is simply importing the compiled file as an ES6 module.
C. So, in the code above, how many parameters are passed to createElement(), and what is the value of each of them?
If you console.log(App) you will see the compiled module (at minimum):
{
render: function render() {}
staticRenderFns: Array[0]
_compiled: true
beforeCreate: Array[2]
__file: "/src/App.vue"
beforeDestroy: Array[1]
_Ctor: Object
}
D. Which part did I misunderstand about Vue or ES6 in above description?
Both. Vue files are compiled to become Vue Instances. ES6 modules encapsulate all sorts of functionality, and expose this functionality to other JavaScript files, as libraries.

vue.js / vue-router - create instance of single file component with props to route to

My goal is to further reduce code and the most obvious one in my project is to abstract lists. Currently I have a vue single file component called List which includes an async data loader. I've also created a bunch of 'derived' single file components using this List component as root tag element, passing props as needed to load the correct data.
Now, since I've split up my components into separate files using this plugin it is common to have a folder structure which looks like this:
\components\
\components\List\
\components\List\List.vue
\components\List\List.vue.js
\components\List\List.scoped.vue.css
\components\List\List.vue.html
As you can see, 4 files per component. Imagine having 10 different list components all using List as their base. That is 10 folders with a total of 40 files. And for what? Pretty much the same code, 2-3 values that change (the properties), the rest stays the same.
I've been trying to adjust List so that I can create an instance of it and pass the properties as constructor values. Why? Instead of having 1 folder with 4 files per list, I could just have the base List and the create the components like so:
let FooList = new List('foo', true, {}, (x) => {});
let BarList = new List('bar', false, {}, (y) => {});
I want to use these objects in the vue-router like so:
const router = new Router({
...
routes: [
{
path: "some/foo,
component: FooList,
},{
path: "any/bar,
component: BarList,
},
]
});
Anything I tried failed.
What have I tried so far?
export default { ... } exports a default single file component. I figured if this is a component, I might as well just override some values in it.
How did I try to do this?
I tried using Object.assign({ ... }, List) in the hope of creating a List object which has the properties defined like I want them to be.
I also tried using the vue built in "extends" option of single files components to extend List, but this doesn't save code at all since I still need to define a template/render method for the component .. which results in those 4 files again. I tried to use Vue.component(..) and Vue.extend(..), alone and in combination but couldn't succeed.
Anything I tried resulted either in a max stack exceeded exception (recursion gone wrong), vue errors were thrown stating that something doesn't fit or just not displaying anything at all.
Is there a way of doing this?
You could define a prop on the List component to specify the type of list and modify the behavior.
let routes = [
{
path: '/home',
name: 'home',
component: List,
props: { config: { type: 'Listing', display: 'Tile' } }
},
]

Vuejs and vue-socketio: how to pass socket instance to components

I am trying to use Vuejs together with the Vue-Socket.io plugin and have a question regarding the correct way of passing the socket between components.
Ideally I want to use only one socket in my whole app, so I want to instantiate the socket in the root instance and then pass it down to the components that need it as a 'prop'. Is that the correct way of doing it?
If so, what am I doing wrong? The error I receive is TypeError: this.socket.emit is not a function so I am probably not passing the socket object correctly.
The component using the socket has the following script
<script>
export default {
name: 'top',
props: ['socket'],
data () {
return {
Title: 'My Title'
}
},
methods: {
button_click: function (val) {
// This should emit something to the socketio server
console.log('clicking a button')
this.socket.emit('vuejs_inc', val)
}
}
}
</script>
My initialization in the root component looks like this:
import Vue from 'vue'
import VueSocketio from 'vue-socket.io'
import App from './App'
import router from './router'
Vue.use(VueSocketio, 'http://127.0.0.1:5000')
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App socket="this.$socket"/>',
components: {App},
sockets: {
connect: function () {
console.log('Vuejs socket connected.')
},
from_server: function (val) {
console.log('Data from server received: ' + val)
}
}
})
And App then passes the socket via
<top socket="this.socket"></top>
NB: I know that I could also either put the socket instantiation into the component that needs it (in this case: top), or I could access the socket object from the root component via this.$root.$socket, but I don't want to do either, because
As stated above, I might want to use the socket in other components
I don't just want to assume a socket object is there in the root instance
In essence, I want to do it right from an architectural standpoint.
There is no need to pass anything. The Vue-Socket.io plugin makes the socket available on every component (and Vue) via this.$socket.
I think your code would work except for the fact that you do not appear to be binding the socket.
<top socket="this.socket"></top>
Should be
<top :socket="socket"></top>
The first line above will just set socket to the string "this.socket". The second will set the property to the result of the expression, this.socket.
You would also need to change the template for the Vue:
<App :socket="$socket"/>

Categories