I'm newbie to Vue.js. I've been stock in making a dynamic dropdown with Ajax in Vue.js. I have no idea how to populate the select tag with data from the API that is hosted in Google App Engine (GAE) and made in Flask.
File index.html
<div id="app2">
<select v-model="table_list">
<option v-for="item in table_list" :value="item">{{ item }}</option>
</select>
</div>
new Vue({
el: '#app2',
data: {
table_list: []
},
created: function () {
var tableList = this.tableList
axios.get('API_URL').then(function (response) {
for (var i = 0; i < response.data.length; i++) {
tableList.push(response.data[i])
}
}).catch(function (error) {
console.log('failure')
})
}
The data of the API:
["test","test1","test2","test3"]
File flask.py
from flask import Flask, request
from google.cloud import bigquery
from flask import jsonify
app = Flask(__name__)
#app.route('/')
def tables_list():
client = bigquery.Client()
dataset_id = 'test_table'
tables = client.list_tables(dataset_id) # Make an API request.
tables_list = [];
for table in tables:
tables_list.append(table.table_id)
return jsonify(tables_list)
if __name__ == "__main__":
app.run()
v-model="table_list" should be a selected model, not the list itself.
<select v-model="table_list_selected">
<option v-for="item in table_list">{{ item }}</option>
</select>
And data: should return a function, plus the model name is table_list, not tableList
new Vue({
el: '#app2',
data: () => ({
table_list: [],
table_list_selected: ''
}),
Also, when you want to keep the scope of this when calling a function, either use arrow functions, or bind this to the function or do away with the callback and use async/await.
created: function () {
axios.get('API_URL').then(response => {
this.table_list = response.data
}).catch(function (error) {
console.log('failure')
})
}
Or with bind()
created: function () {
axios.get('API_URL').then(function(response) {
this.table_list = response.data
}.bind(this)).catch(function (error) {
console.log('failure')
})
}
Or with async/await
created: async function () {
try {
this.table_list = await axios.get('API_URL').data
} catch (e) {
console.log('failure')
}
}
You can consider using an existing flexible solution for selects/dropdowns with built-in support of dynamic contents.
E.g. Vue-Multiselect.
Related
I have a main page containing a component called single-contact as below:
<el-row id="rowContact">
<!-- Contacts -->
<el-scrollbar wrap-class="list" :native="false">
<single-contact ref="singleContact"></single-contact>
</el-scrollbar>
</el-row>
And I want to dynamically render this component after AJAX polling, so in SingleContact.vue I use $axios and mounted() to request the data from the backend. And I want to render the component using v-for. I have my code as:
<template>
<div :key="componentKey">
<el-row id="card" v-for="contact in contacts" :key="contact.convUsername">
<div id="avatar" ><el-avatar icon="el-icon-user-solid"></el-avatar></div>
<h5 id='name' v-if="contact">{{contact.convUsername}}</h5>
<div id='btnDel'><el-button size="medium" type="danger" icon="el-icon-delete" v-on:click="delContact(contact.convUsername)"></el-button></div>
</el-row>
</div>
</template>
And the data structure is:
data() {
return {
timer: null,
contacts: []
}
And the method of Ajax polling is:
loadContacts () {
var _this = this
console.log('loading contacts')
console.log(localStorage.getItem('username'))
this.$axios.post('/msglist',{
ownerUsername: localStorage.getItem('username')
}).then(resp => {
console.log('success')
var json = JSON.stringify(resp.data);
_this.contacts = JSON.parse(json);
console.log(json);
console.log(_this.contacts[0].convUserName);
// }
}).catch(failResponse => {
console.log(failResponse)
})
}
This is what I get in the console:
Console Result
And the mounted method I compute is as:
beforeMount() {
var self = this
this.$axios.post('/msglist',{
ownerUsername: localStorage.getItem('username')
}).then(resp => {
this.$nextTick(() => {
self.contacts = resp.data
})
}).catch(failResponse => {
console.log(failResponse)
})
},
mounted() {
this.timer = setInterval(this.loadContacts(), 1000)
this.$nextTick(function () {
this.loadContacts()
})
},
beforeDestroy() {
clearInterval(this.timer)
this.timer = null
}
I can get the correct data in the console. It seems that the backend can correctly send json to the front, and the front can also receive the right result. But the page just doesn't render as expected.
Any advice would be great! Thank you in advance!
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>
i have 2 data from API
1. Category Food
2. Finish Good
how can i show 2 data from API in 1 page vue,
I only can show 1 data from API
this is what i tried
export default {
data(){
items:[],
finish_goods:[],
created() {
let uri = 'http://192.168.10.75:8000/api/finish_goods'; // Data 1
this.axios.get(uri).then(response => {
this.items = response.data.data;
});
},
created() {
let uri = 'http://192.168.10.75:8000/api/cat_foods'; // Data 2
this.axios.get(uri).then(response => {
this.finish_goods = response.data.data;
});
}
},
methods: {}
}
You're along the right lines, but it looks like your template syntax is a bit messed up...
// Make sure axios is installed via npm, you can skip this step
// if you've declared window.axios = axios somewhere in your app...
import axios from 'axios';
export default {
// Data must be a function that returns the initial state object...
data() {
return {
finishGoods: [],
catFoods: []
};
},
// Created is a hook and can only be defined once, think of it like an event listener...
created() {
let finishGoodsUri = 'http://192.168.10.75:8000/api/finish_goods';
// Fetch finish goods, note that I'm not calling this.axios...
axios.get(finishGoodsUri).then(response => {
this.finishGoods = response.data.data;
});
let catFoodsUri = 'http://192.168.10.75:8000/api/cat_foods';
// Fetch cat foods...
axios.get(catFoodsUri).then(response => {
this.catFoods = response.data.data;
});
}
}
Now in your template you can do the following:
<template>
<div>
<div v-for="finishGood in finishGoods">
{{ finishGood.attribute }}
</div>
<div v-for="catFood in catFoods">
{{ catFood.attribute }}
</div>
</div>
</template>
my advice, combine the API as 1
created() {
let uri = 'http://192.168.10.75:8000/api/combine_data'; // sample
this.axios.get(uri).then(response => {
this.finish_goods = response.data.data.goods;
this.items = response.data.data.foods;
});
}
js:
$('#sortable').sortable({
stop: function(event, ui) {
saveContentOrder();
}
});
function saveContentOrder()
{
const idsInOrder = $("#sortable").sortable("toArray");
var data = {
idsInOrder:idsInOrder
};
$.ajax({
type: "POST",
url: "/xxx/yyy/zzz",
data: data
}).done( function() {
}).fail(function() {
});
}
index.blade.php:
<tbody id="sortable" >
#foreach ($references as $index => $reference)
<tr id="reference_id_{{$reference->id}}">
<td width="65%">
<a href="{{ route('admin.reference.edit', $reference->id ) }}"><b>{{ $reference->title }}</b>
</a><br>
</td>
<td>
#if(!count($reference->images))<span style="color:#ff0000;font-weight:700;">0</span>#else{{ count($reference->images) }}#endif
</td>
<td>
{{ $reference->priority }}
my webroute:
Route::post('/xxx/yyy/zzz', 'AdminReferenceController#reorder');
my Controller:
public function reorder(Request $request)
{
$order = $request->get('idsInOrder',[]);
if (is_array($order))
{
foreach($order as $position => $idName)
{
$id = str_replace("reference_id_","",$idName);
$gesamt = Reference::all()->count();
$c = \App\Reference::find($id);
if($c)
{
$c->priority = $gesamt-$position;
$c->save();
}
}
}
when i am on my first page it saves the position and priority change that i drag and drop.but when i go to the second page for example and drag and drop the order it gives the same priority as in page 1. which means it displays thinks first that should be 20th or 30th. i basically want it to be on the right order all the time. i have a show 10, show 30, and show 100. when i for example get to the show 30 and i have no pages since i dont have so many entries right now it works without issues. but as soon as i go to show 10 and got 3 pages the priority gets mixed up. how can i fix this
this is js File:
$('#sortable').sortable({
update: function(event, ui) {
saveContentOrder();
}
});
function saveContentOrder()
{
const idsInOrder = $(this).sortable("serialize");
var data = {
idsInOrder:idsInOrder
};
$.ajax({
type: "POST",
url: "/xxx/yyy/zzz",
data: data
}).done( function() {
}).fail(function() {
});
}
this is controller file
public function reorder(Request $oRequest)
{
//$oRequest->idsInOrder get all orders positiion ids.affter drag
data like,
//orders[]=6&orders[]=5&orders[]=3&orders[]=4&orders[]=2&orders[]=1
parse_str($oRequest->idsInOrder);
$nCount = 1;
foreach($orders as $order)
{
// update database order
$nCount++;
}
return Response::json(['success' => true]);
}
Im trying to get my json result into my react code
The code looks like the following
_getComments() {
const commentList = "AJAX JSON GOES HERE"
return commentList.map((comment) => {
return (
<Comment
author={comment.author}
body={comment.body}
avatarUrl={comment.avatarUrl}
key={comment.id} />);
});
}
How do i fetch AJAX into this?
First, to fetch the data using AJAX, you have a few options:
The Fetch API, which will work out of the box in some browsers (you can use a polyfill to get it working in other browsers as well). See this answer for an example implementation.
A library for data fetching (which generally work in all modern browsers). Facebook recommends the following:
superagent
reqwest
react-ajax
axios
request
Next, you need to use it somewhere in your React component. Where and how you do this will depend on your specific application and component, but generally I think there's two scenarios to consider:
Fetching initial data (e.g. a list of users).
Fetching data in response to some user interaction (e.g. clicking a
button to add more users).
Fetching initial data should be done in the life-cycle method componentDidMount(). From the React Docs:
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
Here they use jQuery to fetch the data. While that works just fine, it's probably not a good idea to use such a big library (in terms of size) to perform such a small task.
Fetching data in response to e.g. an action can be done like this:
var UserGist = React.createClass({
getInitialState: function() {
return {
users: []
};
},
componentWillUnmount: function() {
this.serverRequest && this.serverRequest.abort();
},
fetchNewUser: function () {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
var users = this.state.users
users.push(lastGist.owner.login)
this.setState({ users });
}.bind(this));
},
render: function() {
return (
<div>
{this.state.users.map(user => <div>{user}</div>)}
<button onClick={this.fetchNewUser}>Get new user</button>
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
Lets take a look on the fetch API : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Lets say we want to fetch a simple list into our component.
export default MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
lst: []
};
this.fetchData = this.fetchData.bind(this);
}
fetchData() {
fetch('url')
.then((res) => {
return res.json();
})
.then((res) => {
this.setState({ lst: res });
});
}
}
We are fetching the data from the server, and we get the result from the service, we convert is to json, and then we set the result which will be the array in the state.
You can use jQuery.get or jQuery.ajax in componentDidMount:
import React from 'react';
export default React.createClass({
...
componentDidMount() {
$.get('your/url/here').done((loadedData) => {
this.setState({data: loadedData});
});
...
}
First I'd like to use fetchAPI now install of ajax like zepto's ajax,the render of reactjs is asyn,you can init a state in the constructor,then change the state by the data from the result of fetch.