Attempting to load a Vue component and populate it with Ajax json content in Laravel - ajax

I am new to Laravel and a complete noob to Vue. I searched many other Laravel/Vue posts but none seemed to be similar enough to get me to a solution. When attempting to load this component on my view I receive the following error
app.js:32654 [Vue warn]: Property or method "features" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
Please let me know what I am missing.
chrome.google.com/webstore/detail/vuejs-devtools/ shows that Vue is loaded
I would like to load data from an ajax call to my vue component. that can be updated on the fly by event handler
App.js
window.Vue = require('vue');
Vue.component('Example', require('./components/Example.vue'));
const app = new Vue({
el : '#app',
});
$(document).ready(function() {
$.ajaxSetup({
headers : {
'X-CSRF-TOKEN' : $('meta[name="csrf-token"]').attr('content')
}
});
})
Example.vue
<template>
<div class="container projects-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
<h1>I'm an example component!</h1>
<ul class="list-group">
<li class="list-group-item" v-for="feature in features">
{{ feature.properties.name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
}
</script>
bladefile
<head>
<meta name="csrf-token" content="INMA4kLlG32gfhf4Z3BBGIFxitrVCWzzqgqPfooEj">
// and yes Vue is loaded
</head>
<body>
<div id="app">
<example></example>
</div>
...
<script>
Vue.component('example',{
template: 'Example',
})
//returns the JSON listed below
someOtherObject.addListener('click', function(e) {
$.ajax({
url:json,
method:'GET',
success:function(msg){
app.data = JSON.parse(msg);
}
})
})
</script>
JSON
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[
-117.155083,
33.569672
]
},
"properties":{
"heading":null,
"face":"South",
"status":"1",
"name":"MEADOWLARK ",
"type":"Single Family Home",
"client_id":"26",
"client_name":"Pulte Homes",
"city_name":"French Valley"
}
},
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[
-117.151390,
33.543981
]
},
"properties":{
"heading":null,
"face":"South",
"status":"1",
"name":"Testing Project",
"type":"Single Family Home",
"client_id":"83",
"client_name":"Testing Account",
"city_name":"Temecula Valley"
}
},
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[
-117.223720,
33.571522
]
},
"properties":{
"heading":null,
"face":"South",
"status":"1",
"name":"Oak Ridge",
"type":"Single Family Home",
"client_id":"98",
"client_name":"Woodside 05S LP",
"city_name":"Beaumont"
}
}
]
}

The features array must be declared either as a prop or part of the component data.
As a prop:
<template>
<div class="container projects-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
<h1>I'm an example component!</h1>
<ul class="list-group">
<li class="list-group-item" v-for="feature in features">
{{ feature.properties.name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
props: ['features']
}
</script>
As component data:
<template>
<div class="container projects-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
<h1>I'm an example component!</h1>
<ul class="list-group">
<li class="list-group-item" v-for="feature in features">
{{ feature.properties.name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
data: function() {
return {features: []}
}
}
</script>
If you use a prop, you'll need to bind the prop in the template. If you use component data, you'll need to update the success callback function in the AJAX request to correctly update the data of the component rather than the app as a whole.
Also, beware of the reactivity caveats for loading new data, as outlined in the docs.
EDIT: Full example solution using component data
Here's a working example using a simplified version of your example component. Note that to do this I used the ref attribute so that the component could be addressed directly. Using refs is described in the docs as an "escape hatch" that should only be used when strictly necessary. A better pattern might be an external store, such as Vuex.
Vue.component( "example", {
template: '<div><h1>Example Component!</h1><ul v-if="features.length"><li v-for="feature in features">{{ feature.name }}</li></ul></div>',
data: function() {
return { features : [] }
}
});
var app = new Vue( { el: '#app' });
//A function to represent the AJAX callback
var doMockAjax = function() {
var mockData = [{name: 'One'},{name: 'Two'},{name: 'Three'}];
app.$refs.example.features = mockData;
}
document.getElementById('load-data').addEventListener('click', function(e) {
//This simulates the ajax callback by populating the array asynchronously
setTimeout(doMockAjax,1000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<example ref="example"></example>
</div>
<button id="load-data">Load</button>

Related

Data not showing in my console.log in vue

In my vue app I have 2 methods, one method gets some data from my laravel backend and the second one needs to be able to grab it so that I can use it in that method.
What I'm struggling with is that the second method isn't grabbing the data.
Here is my code
<template>
<app-layout>
<div class="content-wrapper" style="margin-left: 0;">
<div class="content">
<div class="container">
<div class="row pt-5">
<div class="col-lg-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
Some data will show here
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '#/Layouts/AppLayout'
export default {
components: {
AppLayout,
},
data() {
return {
testData: ''
}
},
methods: {
firstMethod() {
axios.get('/api/get-data').then(response => {
this.testData = response.data;
});
},
secondMethod(){
console.log(this.testData);
}
},
mounted() {
this.firstMethod();
this.secondMethod();
}
}
</script>
your running both function in mount function so both run at same time and secondMethod() executed 1st at that time your this.testData is not set so you can use async and await to wait to finish firstMethod() then run secondMethod()
which will be like below code
<template>
<app-layout>
<div class="content-wrapper" style="margin-left: 0">
<div class="content">
<div class="container">
<div class="row pt-5">
<div class="col-lg-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
Some data will show here
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from "#/Layouts/AppLayout";
export default {
components: {
AppLayout,
},
data() {
return {
testData: "",
};
},
methods: {
async firstMethod() {
const { data } = await axios.get("/api/get-data");
this.testData = data;
},
secondMethod() {
console.log(this.testData);
},
},
async mounted() {
await this.firstMethod();
this.secondMethod();
},
};
</script>
You can try calling firstMethod in created() hook instead of mounted. In my opinion you do not need a method for modifying incoming data. Use watch instead:
watch: {
// whenever question changes, this function will run
testData: function (newValue) {
// do what transformation you need here
}
}
Watch hooks run when value of variable changes, so it should run when it is assigned.
The problem that you dont see the console log is because even if you execute first the first method, it's actually executed after the second method because takes more time to be resolved.
Try the below please, inside first method i added after then to execute the second method which means that that the first method would be resolved, thus we will have the api response.
In case you continue seeing nothing from console log then there is an issue with the api endpoint.
mounted() {
this.firstMethod();
},
methods: {
firstMethod() {
axios.get('/api/get-data').then(response => {
this.testData = response.data;
this.secondMethod();
});
},
secondMethod(){
console.log(this.testData);
}
},

infinite scroll using laravel and vue request not sent to display data

good day;
i new in vue.js
i have simple problem when i using infinite scroll i make configuration as below but when page reloaded it must send request to data base to show data when page =1 this request not sent
i want help to get it work
this is my
this is service component
<template>
<div>
<!-- start covr-page --------------------- -->
<div class="covr-page">
<h2>خدمات لوجستية متكاملة </h2>
<div class="overlay"></div>
</div>
<!-- end covr-page --------------------- -->
<!-- start servic --------------------- -->
<div class="servic sections">
<div class="container">
<h3 class="start-title">
<img src="/images/start-title.png" alt="">
خدمات لوجستية متكاملة
</h3>
<div class="row">
<div v-for="service,key in list" class="col-lg-offset-3 col-lg-6 col-md-12 col-md-8 col-sm-offset-1 col-sm-10">
<a href="order-service.html">
<div class="email-signature">
<div class="signature-details">
<div class="signature-img">
<img :src="`upload/${service.icon}`" alt="">
</div>
</div>
<div class="contant-serv">
<h4>{{service.ar_name}}</h4>
<p>
{{service.ar_description}}
</p>
</div>
</div>
</a>
</div>
<infinite-loading #distance="100" #infinite="infiniteHandler"></infinite-loading>
</div>
</div>
</div>
</div>
</template>
this is my route
Route::get('/Services', 'ServicesController#Services');
this is my js code
<script>
export default {
name: "Services",
data: function () {
return {
list: [],
page:1
}
},
mounted(){
// this.infiniteHandler()
// axios.get('/Services')
// .then((response) =>{
// this.list=response.data
// }).catch((error) =>this.errors=error.response.data.errors );
},
methods: {
infiniteHandler($state) {
this.$http.get('/Services?page='+this.page)
.then(response => {
return response.json();
}).then(data => {
$.each(data.data, (key, value)=> {
this.list.push(value);
});
$state.loaded();
});
this.page = this.page + 1;
},
},
}
</script>
you have not declared vue-infinite-loading
import InfiniteLoading from 'vue-infinite-loading';
export default {
components: {
InfiniteLoading,
},

displaying laravel show method on vue component

i have a list of movies am trying to display the details of one movie using the show method
here is my component for all movies
<template>
<div>
<el-row :gutter="10">
<el-col :span="6" v-for="movie in movies" v-bind:key="movie">
<el-card shadow="always" :body-style="{ padding: '0px'} ">
<img v-bind:src="movie.cover_photo" class="image" >
<div style="padding: 14px;">
<div class="bottom clearfix">
<router-link :to="{name:'details',params:{id:movie.id}}">{{movie.title}}</router-link>
<router-view></router-view>
<h4>{{ movie.year }}</h4>
<h4>{{ movie.type }}</h4>
</div>
<div class="block">
<el-rate :max="10" ></el-rate>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
movies: [],
movie:{
id:'',
}
};
},
created(){
this. fetchMovieList();
this.showMovie
},
methods: {
fetchMovieList() {
axios.get('/movies').then(response => {
this.movies = response.data;
})
.catch(error=>console.log(error))
},
showMovie(id){
axios.get('/movies/'+id).then((res)=>{
if(res.data.status==true){
this.movie= res.data.movie;
console.log(res.data.movie)
}else{
alert('No Movie founded with this id')
}
})
.catch((err)=>{alert('error')})
}
}
}
</script>
<style scoped>
.image {
width: 100%;
display: block;
}
</style>
my show method:
public function show($id)
{
$movie=Movie::find($id);
if($movie){
return response()->json(['status'=>true,'movie'=>$movie]);
}else{
return response()->json(['status'=>false]);
}
}
my router on app.js:
const movie=Vue.component('details', require('./components/DetailsComponent.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const router=new VueRouter({
mode:'history',
routes:[
{
path:'/movie/:id',
name:'movie',
component:movie
},
],
});
const app = new Vue({
el: '#app',
router,
});
when i click on the router link it just changes the url to the id of the movie but it doesnt show the component with details when i hit the show endpoint with a specific id it returns the movie in json format on the browser
I think your variable of v-for conflict with the same variable of data().
You should try another variable name of v-for.
Something like
<el-col :span="6" v-for="value in movies" v-bind:key="movie">
<el-card shadow="always" :body-style="{ padding: '0px'} ">
<img v-bind:src="value.cover_photo" class="image" >
<div style="padding: 14px;">
<div class="bottom clearfix">
<router-link :to="{name:'details',params:{id:value.id}}">{{value.title}}</router-link>
<router-view></router-view>
<h4>{{ value.year }}</h4>
<h4>{{ value.type }}</h4>
</div>
<div class="block">
<el-rate :max="10" ></el-rate>
</div>
</div>
</el-card>
</el-col>
Hope this helps you: Reacting to Params Changes
Regards,

How can I dynamically nest vue components?

I want to add one component inside other when user clicks a button. but how can we render the component in the virtual dom.
I tried using v-html but its not working.
Whats the best way to solve this issue?
export default {
data(){
return{
elements : {
hotel : '<hotel-form></hotel-form>'
},
}
},
methods:{
addHotel(){
console.log('add');
}
}
}
<template>
<div class="container" style="margin-top:300px;">
<div class="row" id="mainform">
<div v-for="item in elements">
<div v-html="item"></div>
</div>
</div>
<button #click="addHotel()">add hotel</button>
</div>
</template>
I would bind an array (hotels) to a <hotel-form> component tag via v-for. This way, no hotel-form components will be initially rendered, and then you can push an object (with any data to want bound to the hotel-form component) to the hotels array and the DOM will automatically render a new corresponding hotel-form component.
Here's a simple example:
Vue.component('hotel-form', {
template: '#hotel-form',
props: { id: Number, name: String },
});
new Vue({
el: '#app',
data() {
return { hotels: [], count: 0 }
},
methods: {
addHotel() {
this.hotels.push({ name: 'foo', id: this.count++ })
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<div id="mainform">
<hotel-form v-for="hotel in hotels" :key="hotel.id" v-bind="hotel">
</hotel-form>
</div>
<button #click="addHotel">add hotel</button>
</div>
<template id="hotel-form">
<div>
<h4>Hotel Form #{{ id }}</h4>
<div>{{ name }}</div>
</div>
</template>

Vuejs 2 display template twice IE and Safari laravel with blade

I made an autocomplete component and it works well in Chrome, but not in IE and Safari.
It displays the template twice in IE and Safari. It works but it shows the template in addition to the rendered HTML. See the image.
What did I do wrong?
<div id="main">
<autocomplete></autocomplete>
</div>
<template id="autocomplete">
<div>
<div class="col">
<section class="box clr1">
<div>
<div>
<input type="text" placeholder="Welk product zoekt U?" v-model="query" v-on:keyup="autoComplete" class="form-control">
<div class="panel-footer" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
<span style="text-decoration: underline;cursor:pointer" v-on:click="showDetail(result.id)">#{{ result.title }}</span>...
<div class="col">
<section class="box clr1">
<div>
<div v-for="detail in resultdetail">
<h1>#{{ detail.title }}</h1>
<h2>#{{ detail.page_title }}</h2>
<p v-html="detail.description"></p>...
Vue.component('autocomplete', {
template: '#autocomplete',
data: function () {
return {
show: false,
query: '',
results: [],
resultdetail: []
}
},
methods: {
autoComplete: function () {
this.results = [];
if (this.query.length > 1) {
axios.get('/getproductjson/' + this.query + '/0')
.then(function (response) {
this.results = response.data
}.bind(this))...
showDetail: function (productId) {
if (productId > 0) {
this.show = true;
this.resultdetail = [];
axios.get('/getproductjson/loremipsumdipsum/'+productId)
.then(function (response) {
this.resultdetail = response.data
}.bind(this))...
}
});
new Vue({
el: '#main'
});
Result:
Internet explorer does not support the template tag.
What you're seeing in Internet Explorer is your instantiated Vue, and, since IE doesn't implement template it just shows your template on screen.
In IE if you want to store your template in the DOM you typically have to use the a script template.
<script type="text/x-template" id="autocomplete">
...
</script>

Resources