Vue: props keep coming back as undefined - laravel

I'm checking my Root and child (Topbar) component and each time, the foo prop is undefined in each one. I'm not sure what I am doing wrong since I am defining it.
app.js
window.Vue = require('vue');
Vue.component('Topbar',
require('./components/Topbar.vue').default);
new Vue({
el: "#app",
data: {
activeTab: "cart"
},
props: {
foo: "testing props"
}
})
Topbar.vue
<template>
<div class="cont">
<div class="tabs">
<a v-on:click="handleClick('account')" v-bind:class="[ activeTab === 'account' ? 'active' : '' ]">Account</a>
<a v-on:click="handleClick('cart')" v-bind:class="[ activeTab === 'cart' ? 'active' : '' ]">{{foo}}</a>
</div>
</div>
</template>
<script>
export default {
props: ['activeTab'],
data() {
return {
activeTab: "CCC"
}
},
props: ["foo"],
}
</script>
laravel-blade-template file
<div id = "app">
<Topbar :foo="testing props"></Topbar>
</div>

Try removing the colon in front of foo.
If you add the colon, then it's executed as JavaScript, and the variable testing doesn't exist. If you remove the colon, then you should receive "testing props" as a string.
<div id = "app">
<Topbar foo="testing props"></Topbar>
</div>

There are 3 problems with your code.
You are defining a prop in app.js. You cannot assign a value to it. The syntax you are using cannot be used to pass a variable. More about the syntax about declaring a prop here.
In Topbar you are assigning a value to a prop, but are using the binding syntax. Vue will execute the following js code testing props which is not a valid code. You should use this :foo="'testing props'". The reason for the apostrophe inside the quotes is the fact that 'testing props' is a valid string in JS, and it will get assigned to your prop.
You are also defining props 2 times in Topbar.vue. You'll end up with your last definition and that's probably not what you want.

I could identify two errors in your codes:
You should not have props definition in your export
The value testing props that you have assigned to foo is a string and should wrapped with single quotes (').
The following codes should work:
export default {
props: ['activeTab', 'foo'],
data() {
return {
activeTab: "CCC"
}
}
}
And this
<Topbar :foo="'testing props'"></Topbar>

Related

Render content with Vue syntax / component string through AJAX call?

I have this HTML pattern:
<div id="New"> == ajax loaded content == </div>
It was easy to render HTML at server side and use innerHTML to inject the content into the right place.
Now I am trying to use Vue.js to do the same thing but render HTML at the client side. I can make this pattern into a component, let's say componentA, with template:
componentA
template:
`<div><slot></slot></div>`
It works if the HTML page content is something like:
<componentA>
<componentB></componentB> and some other none component content
</componentA>
The componentB is rendered and replaced the slot in componentA.
The problem is how do I use AJAX call (the call is made outside of componentA) to load
<componentB></componentB> and some other none component content
into the slot of componentA, and still make componentB to render correctly?
In real situation, the content from AJAX call can be
<componentB>, <componentC>, <componentD> ...
The following will treat componentB as regular string
in HTML:
<componentA>
<div id="New"></div>
</componentA>
in JS:
document.getElementById('New').innerHTML =
'<componentB></componentB> And some other none component content';
Is there a proper way to render string from AJAX return with Vue syntax as Vue?
One solution is put the ajax response like <component></component> to Component.template inside render function (Vue Guide: Render Function).
Like below demo:
const Foo = Vue.component('foo', {template: '<p>Foo - {{flag}}</p>', props: ['flag']})
const Bar = Vue.component('bar', {template: '<p>Bar - {{flag}}</p>', props: ['flag']})
const Generic = Vue.component('generic', {
render: function (createElement) {
return createElement('div', [
createElement('h3', 'Title'),
createElement('button', {on: {click: this.loadComponent}}, 'Load Component'),
this.dynamicComponent
&& createElement(Vue.component('v-fake-slot', {template:this.dynamicComponent, props: ['flag']}), {
props: {
flag: this.parent
}
})
])
},
props: ['parent'],
data () {
return {
components: ['<foo :flag="flag"></foo>', '<bar :flag="flag"></bar>'],
index: 0,
dynamicComponent: ''
}
},
methods: {
loadComponent: function () {
setTimeout(() => {
this.index += 1
this.dynamicComponent = this.components[this.index % 2]
}, 1000)
}
}
})
new Vue({
el: '#app',
data () {
return {
test: 'root'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<generic :parent="test"></generic>
</div>

Create global method vue on app.js laravel

I want create global method to translate message using Laravel-JS-Localization
But when i call the method using vue mustache got an error like this:
Property or method "trans" is not defined on the instance but referenced during render.
Make sure that this property is reactive.
Here my laravel app.js code:
require('./bootstrap');
window.Vue = require('vue');
Vue.component('dashboard', require('./components/Dashboard').default);
const app = new Vue({
el: '#vue',
methods: {
trans: function (key) {
return Lang.get(key);
},
},
});
Dashboard.vue code :
<template>
<p>{{ trans('common.welcome') }}</p>
</template>
<script>
data () {
return {
name: '',
}
},
</script>
dashboard.blade.php code :
..........
<div class="col-9" id="vue">
<dashboard></dashboard>
</div> <!--c end col-8 -->
..........
I would probably go with creating a Plugin. For example
Vue.use({
install (Vue) {
Vue.prototype.$trans = Lang.get
}
})
Adding this to your app.js code before creating any components or new Vue({...}) will mean all your components will have access to the $trans method.
Alternatively, you can create a Global Mixin but these aren't strongly recommended.
Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components
Vue.mixin({
methods: {
trans (key) {
return Lang.get(key)
}
}
})

Maintaining button text state after page refresh using Vue.js and local storage

I am working on a task list using Vue.js component and Laravel, with a button to mark each individual task as "complete" or "incomplete". At the moment I can't even get it to change state, let alone maintain it after the page refresh. The console log says [Vue warn]: Error in mounted hook: "TypeError: Assignment to read-only properties is not allowed in strict mode".
CompleteButton.vue
<template>
<button type="button" #click="on_order_button_click()">
{{ buttonText }}
</button>
</div>
</template>
<script>
export default {
props: ['userId', 'item'], required: true,
data() {
return {
item2: this.item
}
},
methods: {
on_order_button_click() {
this.item2.is_complete = !this.item2.is_complete;
localStorage.setItem(this.item2.id, this.item2.is_complete);
}
},
mounted() {
var storedState = localStorage.getItem(this.item2.id);
if (storedState) {
this.item2.is_complete = storedState;
}
},
computed: {
buttonText() {
return this.item2.is_complete === true ? "Completed" : "Incomplete";
}
}
};
</script>
index.blade.php
<complete-button user-id="{{ $user->id }}" item="{{ $item}}"></complete-button>
You are assigning item2 as item prop and which is readonly since it's passed as a property so item2 keeping reference to the same readonly object.
You can simply use spread syntax or Object.assign method to create a new object.
item2: {...this.item}
UPDATE : As you commented, If it's a JSON string then simply parse it ans keep it as item2.
item2: JSON.stringify(this.item)

VueJS: How do I initialise data so it shows in my template on component load

This is driving me nuts!
//ProfilePage.vue
<template>
<div>
<p>{{ this.$data.profile.desc }}</p>
<profileImage v-bind:profile="profile"></profileImage>
<profileText v-bind:profile="profile" v-on:updateData="updateDesc"></profileText>
</div>
</template>
<script>
import profileText from './ProfileText.vue';
import profileImage from './ProfileImage.vue';
export default {
name: 'profilePage',
component: {
profileText,
profileImage
},
data() {
return {
profile: {
image: '',
desc: ''
}
}
},
created() {
this.fetchProfile();
},
methods: {
async fetchProfile() {
const uri = 'http://localhost:8000/api/......get';
const response = await axios.get(uri);
.then(response => this.updateProfileData(response.data))
},
updateProfileData(data) {
this.$data.profile.image = data['image'];
this.$data.profile.desc = data['description'];
},
updateDesc(data) {
this.$data.profile.desc = data.desc;
},
}
}
</script>
<style scoped>
</style>
In the above .vue file. I execute a fetch to the back end which successfully returns the correct data from the DB. I successfully save the data returned to the data() part of the file. Next I import a component (the code for which is below) from the correct page, add it as a component and add it to the template and use v-bind to pass in profile from the data() part of this page. Now the imported/child component looks like this:
//ProfileText.vue
<template>
<div>
<form #submit="update">
<textarea v-model="description"></textarea>
<button type="submit">Submit</button>
</form>
<div>
<template>
<script>
export default{
name: "profileText",
props: ["profile"],
data() {
return {
description: this.$props.profile.desc
}
},
methods: {
update(e) {
e.preventDefault();
const newData = {
desc: this.$data.description
}
this.$emit('updateData', newData);
}
}
}
</script>
<style scoped>
</style>
I use v-model to bind the contents of "description" in data() to the contents of the textarea. I have it so when i edit the text area and click submit the function emits the data to the parent component which triggers a function that updates the parent data() with the new data from the text area of this component. This parts works perfectly.
However, the part I can't figure out is when the parent component executes the fetch and binds the response with the child component, why isn't the response showing up in the textarea when it loads.
I have done the exact same thing with another lot of components and it works fine on that lot. The only difference there is that with that lot the execute function brings back a response with an array of data and I use v-for(x in xs) and then bind the attributes of data() with the component x. That's the only difference. What am I missing in the code above to load the data sent in "profile" from the parent component with v-bind to the textarea in the child component with v-model. In data() i have it to return description: this.$props.profile.desc, but it is not initialising description with profile.desc - Going nuts here $#! I've been staring at the code for two days straight trying different things.
mounted Function
Called after the instance has been mounted, where el is replaced by
the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
Note that mounted does not guarantee that all child components have
also been mounted. If you want to wait until the entire view has been
rendered, you can use vm.$nextTick inside of mounted:
mounted: function () { console.log('component mounted'); }
This hook is not called during server-side rendering.
Source
Component Lifecycle
Few things:
Your syntax has errors in the ProfileText.vue file. Missing closing template and div tags
<template>
<div>
<form #submit="update">
<textarea v-model="description"></textarea>
<button type="submit">Submit</button>
</form>
</div>
</template>
You are mixing async/await and .then(). It should be:
async fetchProfile() {
const uri = 'http://localhost:8000/api/......get';
const response = await axios.get(uri);
this.updateProfileData(response.data)
},

How to render data with AJAX in Vuejs 2?

I have problems while trying to set data in a Vue instance
(example in: https://jsfiddle.net/a5naw6f8/4/ )
I assign the values to data property by AJAX. I can render exam object but when I want to access to an inner array exam.questions.length I get an error:
"TypeError: exam.questions is undefined"
This is my code :
// .js
new Vue({
el: '#app',
data:{
id: "1",
exam: {}
},
created: function () {
this.fetchData();
},
methods: {
fetchData: function () {
$.get("https://dl.dropboxusercontent.com/s/15lkz66wy3fut03/data.json",
(data) => {
this.exam = data;
}
);
}
}
});
<!-- .html -->
<div id="app">
<h1>ID: {{ id }}</h1>
<h1>Exam: {{ exam }}</h1>
<!-- exam.questions array is undefined -->
<!-- <h1>Questions length: {{ exam.questions.length }}</h1> -->
</div>
I found a similar problem but it was relative to this reserved word scope:
Vuejs does not render data from ajax
Does someone know how to fix this problem?
You have a couple problems:
Your JSON file isn't valid. The trailing commas after the the false cause the parser to choke.
You aren't parsing your JSON (which is why you probably didn't see the JSON error). Once you fix the JSON file, you need to parse it with something like this.exam = JSON.parse(data) rather than assigning the string to this.exam
Once you do that, you can test for the object in the template with v-if before trying to access its properties:
<h1 v-if="exam.questions">Questions length: {{ exam.questions.length }}</h1>
v-if will prevent the attempt to access exam.questions.length until the ajax request has returned data

Resources