Vue not rendering my data - laravel

I have a component that I get data on create with ajax and I show this in a Template. Basically I have this:
my Vue code:
Vue.component('feedback-table', {
template: '#grid-template',
data: function () {
return {
chancesOfApproval:[],
}
},
created:function(){
$.getJSON('/getFeedbackQuestionsAndChances',function(data){
this.chancesOfApproval = data.chancesOfApproval;
}.bind(this));
},
});
new Vue({
el: '#feedback-wrapper',
});
And here is my template:
<template id="grid-template">
<table class="table table-bordered table-responsive table-striped">
<tr v-for="entry in chancesOfApproval">
<td>#{{ entry.position }}</td>
</tr>
</table>
</template>
<div id="feedback-wrapper">
<feedback-table></feedback-table>
</div>
The data is being getted from the jquery because if I do console.log(this.chanceOfApproval) for example, it appears fine in the console. And I do not get any error in my console, so I have no idea why it does not work.

It behaves as expected here, where I've switched out the getJSON for a setTimeout doing the same assignment.
Vue.component('feedback-table', {
template: '#grid-template',
data: function() {
return {
chancesOfApproval: [],
}
},
created: function() {
/*
$.getJSON('/getFeedbackQuestionsAndChances', function(data) {
this.chancesOfApproval = data.chancesOfApproval;
}.bind(this));
*/
setTimeout(() => {
this.chancesOfApproval = [{
position: 1
}, {
position: 2
}];
}, 500);
},
});
new Vue({
el: '#feedback-wrapper',
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<template id="grid-template">
<table class="table table-bordered table-responsive table-striped">
<tr v-for="entry in chancesOfApproval">
<td>#{{ entry.position }}</td>
</tr>
</table>
</template>
<div id="feedback-wrapper">
<feedback-table></feedback-table>
</div>

You are using function.bind(this) to bring this from outer scope to the function scope, in your $http success handler.
Have you checked if it is not creating any issues? I am not aware of how bind is implemented. Maybe it is not allowing Vue to trigger its Reactivity System.
If you see values in console as a plain JSON object, then it is probably wrong. Vue.js modifies object params to getters / setters / observers so that it can bind to the DOM.
Alternatively, why dont you try the arrow function or using a self variable, just to see if it solves the issue?
Arrow function (ES6 and above):
created:function(){
$.getJSON('/getFeedbackQuestionsAndChances',data => {
this.chancesOfApproval = data.chancesOfApproval;
});
}
Or use a local variable:
created:function(){
var self = this; // self points to this of Vue component
$.getJSON('/getFeedbackQuestionsAndChances',function(data){
self.chancesOfApproval = data.chancesOfApproval;
});
}
Another note: instead of setTimeout(), you also have the option of Vue.nextTick() as follows:
Vue.nextTick(function () {
// DOM updated
})
or in your case:
this.$nextTick(function () {
// DOM updated
})

Related

How to pass a vue data value or id from a href link to load new component to show this value or id?

I have one vue componet i wanna click to a href link bellow code to load another vue componet to pass id or any value of this form.
<template>
<div class="container">
<table class="table table-hover">
<tbody>
<tr>
<th>No</th>
<th>another vue component</th>
</tr>
<tr>
<td>1 </td>
<td>
<a href="'/NewVueComponent.vue/ + this.form.id'" > show </a>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data (){
return {
form: new Form({
id: '',
})
}
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
In your view component, have a method that will push router to another view. The id can be passed as an arg via params.
<script>
export default: {
data() {
return {
id: "someId"
}
},
methods: {
goTo() {
this.$router.push({name: OtherComponent, params: {
id: this.id
}})
}
}
}
</script>
and on your route declaration, make sure props is set to true
{
path: "/path-to-component",
component: OtherComponent,
name: "OtherComponent",
props: true,
}
and of course on the OtherComponent you will have to define id as a prop.
You can read more about it here https://router.vuejs.org/guide/essentials/passing-props.html

Cannot read property replace of null method parameter

I started messing around with Vue.js earlier this week.
So far I created a list of MTG(a TCG) cards. The data comes from the database through an Ajax request. This all works like a charm.
What i want to do next is replace the string that contains the costs of a card e.g. something like '{1}{U}{G}' with images for the corresponding tag.
HTML:
<div v-for="(cards, key) in mainBoard" class="">
<table class="table">
<tr>
<th colspan="5">#{{ key }}</th>
</tr>
<tr>
<th>#</th>
<th>Name</th>
<th>ManaCost</th>
#if($deck->enableCommander())
<th>Commander</th>
#else
<th></th>
#endif
<th>Actions</th>
</tr>
<tr v-for="card in cards">
<td>#{{card.pivot.quantity}}</td>
<td>#{{card.name}}</td>s
<td v-html="replaceManaSymbols(card)"></td>
#if($deck->enableCommander())
<td>
<span v-if="card.pivot.commander" #click="preformMethod(card, 'removeCommander', $event)"><i class="fa fa-flag"></i></span>
<span v-else #click="preformMethod(card,'assignCommander', $event)"><i class="far fa-flag"></i></span>
</td>
#else
<td> </td>
#endif
<td>
<button #click="preformMethod(card,'removeCardFromDeck', $event)"><i class="fa fa-times-circle"></i></button>
<button #click="preformMethod(card,'plusCardInDeck', $event)"><i class="fa fa-plus-circle"></i></button>
<button #click="preformMethod(card,'minusCardInDeck', $event)"><i class="fa fa-minus-circle"></i></button>
</td>
</tr>
</table>
</div>
Vue.js
new Vue({
el: '#Itemlist',
data: {
mainBoard: [],
sideBoard: [],
},
methods:{
preformMethod(card, url){
var self = this;
var varData = {
slug: '{{ $deck->slug }}',
card: card.id,
board: card.pivot.mainboard
};
$.ajax({
url: '/vue/'+url,
data: varData,
method: 'GET',
success: function (data) {
self.mainBoard = data.mainBoard;
self.sideBoard = data.sideBoard;
},
error: function (error) {
console.log(error);
}
});
},
replaceManaSymbols(card){
var mc = card.manaCost;
var dump = mc.replace(/([}])/g, '},').split(',');
var html = '';
/**
* replace each tag with an image
*/
return html;
}
},
mounted(){
var self = this;
var varData = {
slug: '{{ $deck->slug }}'
};
$.ajax({
url: '/vue/getDeckList',
data: varData,
method: 'GET',
success: function (data) {
self.mainBoard = data.mainBoard;
self.sideBoard = data.sideBoard;
},
error: function (error) {
console.log(error);
}
});
}
})
I pass the card as a parameter to the replaceManaSymbols method. I can console.log the contents of mana without any issue. But as soon as a want to modify the string Vue throws the error TypeError: Cannot read property 'toLowerCase/split/replace' of null. I'm not really sure what's going wrong. Any idea's?
As a rule of thumb, you shouldn't use methods on the display side. Keep them for the update side - processing changes back into a store and such. Methods aren't reactive - they need to be called. You want your display to automatically reflect some underlying data, so you should use computed.
You should also avoid using v-html, because you end up with markup outside your templates, and that markup is static. It's not totally clear to me what will be in v-html, but you should try and keep markup in your template, and inject values using data, props or computed. Hope this helps!
If you want a div with some text that magically turns into an image tag, this could be a good use for <component :is>.

How i can send ajax request after render vue component?

I have a component
html:
table
tr(is="trcom" v-for="xml in xmls" :xml="xml")
js:
components: {
trcom: {
props: ['xml'],
template: "<tr><td> {{ xml.query }} </td><td> downloading </td><td> downloading </td></tr>",
data: function(){
return {
position: "",
}
}
}
}
can i send ajax request and replace template if ajax is done?
final template:
<tr><td> {{ xml.query }} </td> <td> {{ position }} </td> ...etc... </tr>
Given our discussion in the comments below your question, I have a recommendation:
1) The elements that you're adding and want replaced after individual ajax calls should each be their own component.
2) Because you will be using individual child components, you should use the mounted lifecycle hook to perform your ajax calls.
3) Instead of "replacing" the components' templates, which I'm not sure is even possible, you can instead use conditional rendering to show an initial state vs. a post-ajax state.
Below is a toy example which also uses jQuery (the jQuery itself isn't necessary but is being used for simplicity):
Child Component
Vue.component('my-child-component', {
template: `
<div>
<div v-if="initialized">
I'm initialized! My prop value is: {{my_prop}}
</div>
<div v-else>
I'm not initialized yet!
</div>
</div>
`,
props: ['my_prop'],
data: function() {
return {
initialized: false
};
},
mounted: function() {
//needed because we can't use "this" to reference the vue instance in our ajax call!
var this_vue_instance = this;
//this jQuery ajax call will execute as soon as this component has finished rendering
$.post('/your/target/url', {any_data: 'you might need'}, function(data) {
this_vue.initialized = true;
});
}
});
Root Vue Instance
<script>
$(document).ready(function() {
var root_vue_instance = new Vue({
el: '#app',
data: {
items: [0, 1, 2, 3]
}
});
});
</script>
<div id="app">
<div v-for="item in items">
<my-child-component :my_prop="item"></my-child-component>
</div>
</div>
The above is really bare-bones, but should server as a helpful example for implementing the solution to your current problem.

why items are not loading in blog.vue?

I am loading template from blog.vue in app.js, but with this it displays only header row of table not table rows as ajax call is not made.
If i comment require('./bootstrap');, it does not display table header, ajax is called but no data is displayed in table rows.
1. blog.vue file
<template>
<div class="table-responsive">
<table class="table table-borderless">
<tr>
<th>Title</th>
<th>Description</th>
</tr>
<tr v-for="item in items">
<td>#{{ item.title }}</td>
<td>#{{ item.description }}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component ready.')
}
}
</script>
2. app.js file
require('./bootstrap');
Vue.component('list-blog', require('./components/blog.vue'));
new Vue({
el :'#manage-vue',
data :{
items: [],
newItem : {'title':'','description':''},
fillItem : {'title':'','description':'','id':''}
},
ready: function() {
this.getVueItems();
},
methods: {
getVueItems: function() {
this.$http.get('/vueitems?page='+page).then((response) => {
this.$set('items', response.data.data.data);
this.$set('pagination', response.data.pagination);
});
},
}
});
3. bootstrap.js
window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
window.Vue = require('vue');
require('vue-resource');
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);
next();
});
Can you please tell me where am i wrong in this ? i am using laravel-elixir here.
I think this should be only response.data.data
// this.$set('items', response.data.data.data);
// to
this.$set('items', response.data.data);
You could even simplify this
this.$http.get('/vueitems?page='+page).then((response) => {
this.items = response.data.data;
this.pagination = response.data.pagination;
}.bind(this)); // important bind(this)
It seems like you are returning a paginated object. Therefore the third data is false - isn't it?
Also you would usually pass the items to the component as property. Simply define the prop at your blog.vue
<script>
export default {
props: ['items'],
mounted() {
console.log('Component ready.')
}
}
</script>
And then pass it whereever you use it
<list-blog :items="items"></list-blog>
I highly recommend to use Chrome and the Vue devtools https://github.com/vuejs/vue-devtools - these will help you to follow and undestand the data bindings
Another smaller thing is that you shouldn't bind the pagination object AND the items seperatly. The pagination object contains the items. If you don't want o use "pagination.data" in your templates simply use a computed property instead of creating two sources of truth.

Sorting and Filtering ajax data using Laravel and VueJs

Current code is sorting and filtering data using vue.js. It is working fine but data is dummy, it is hardcoded. I need to get data dynamically from table using vue js and laravel. How can I get dynamic data in gridData?
JS
Vue.component('demo-grid', {
template: '#grid-template',
props: {
data: Array,
columns: Array,
filterKey: String
},
data: function () {
var sortOrders = {}
this.columns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#app',
data: {
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
}
})
laravel.blade.php
#extends('layouts.app')
#section('title', 'Customers List')
#section('styles')
#endsection
#section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">Customers List</div>
<div class="panel-body">
<script type="text/x-template" id="grid-template">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th v-for="key in columns" #click="sortBy(key)" :class="{active: sortKey == key}">#{{key | capitalize}}<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"></span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in data | filterBy filterKey | orderBy sortKey sortOrders[sortKey]">
<td v-for="key in columns">
#{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<div id="app">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid :data="gridData" :columns="gridColumns" :filter-key="searchQuery"></demo-grid>
</div>
</div>
</div>
</div>
</div>
</div>
#endsection
#section('scripts')
<script src="/js/vue.js"></script>
<script src="/js/vue-resource.min.js"></script>
<script src="/js/customers.js"></script>
#endsection
You will need to do a few things.
First, in Laravel, create a new route in your routes.php file, for ex.:
Route::get('/api/fighters', 'SomeController#index');
Then in your controller (somecontroller.php), you will have a method index which will query your database table and return it as JSON data.
public function index() {
//query your database any way you like. ex:
$fighters = Fighter::all();
//assuming here that $fighters will be a collection or an array of fighters with their names and power
//when you just return this, Laravel will automatically send it out as JSON.
return $fighters;
}
Now, in Vue, your can call this route and grab the data. Using AJAX. You can use any AJAX library that you like, even jQuery. I currently use Superagent.js. Vue will work just fine with any.
So, in your Vue code, create a new method to get your data.:
methods: {
getDataFromLaravel: function() {
//assign `this` to a new variable. we will use it to access vue's data properties and methods inside our ajax callback
var self = this;
//build your ajax call here. for example with superagent.js
request.get('/api/fighters')
.end(function(err,response) {
if (response.ok) {
self.gridData = response.body;
}
else {
alert('Oh no! We have a problem!');
}
}
}
}
Then you can just call this new method using a button or anyway you like. For example, using a button click event:
<button type="button" #click="getDataFromLaravel">Get Data</button>
Or you can even just have the data loaded automatically using Vue's ready function:
// bootstrap the demo
var demo = new Vue({
el: '#app',
data: {
.... }
ready: function () {
this.getDataFromLaravel();
},
methods: {
.... }
});
Done! You now have your database data assigned to Vue's data property gridData.

Resources