I'm working on a project that is using Vue.js and Vue Router as the frontend javascript framework that will need to use a select box of users many places throughout the app. I would like to use select2 for the select box. To try to make my code the cleanest I can, I've implemented a custom filter to format the data the way select2 will accept it, and then I've implemented a custom directive similar to the one found on the Vue.js website.
When the app starts up, it queries the api for the list of users and then stores the list for later use. I can then reference the users list throughout the rest of the application and from any route without querying the backend again. I can successfully retrieve the list of users, pass it through the user list filter to format it the way that select2 wants, and then create a select2 with the list of users set as the options.
But this works only if the route that has the select2 is not the first page to load with the app. For example, if I got to the Home page (without any select2 list of users) and then go to the Users page (with a select2), it works great. But if I go directly to the Users page, the select2 will not have any options. I imagine this is because as Vue is loading up, it sends a GET request back to the server for the list of users and before it gets a response back, it will continues with its async execution and creates the select2 without any options, but then once the list of users comes back from the server, Vue doesn't know how to update the select2 with the list of options.
Here is my question: How can I retrieve the options from an AJAX call (which should be made only once for the entire app, no matter how many times a user select box is shown) and then load them into the select2 even if the one goes directly to the page with the select2 on it?
Thank you in advance! If you notice anything else I should be doing, please tell me as I would like this code to use best practices.
Here is what I have so far:
Simplified app.js
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
globals: {
users: {
data: []
},
}
};
},
methods: {
fetchUsers: function() {
this.$http.get('./api/v1/users/list', function(data, status, response) {
this.globals.users = data;
});
},
}
});
Sample response from API
{
"data": [
{
"id": 1,
"first_name": "John",
"last_name": "Smith",
"active": 1
},
{
"id": 2,
"first_name": "Emily",
"last_name": "Johnson",
"active": 1
}
]
}
User List Filter
Vue.filter('userList', function (users) {
if (users.length == 0) {
return [];
}
var userList = [
{
text : "Active Users",
children : [
// { id : 0, text : "Item One" }, // example
]
},
{
text : "Inactive Users",
children : []
}
];
$.each( users, function( key, user ) {
var option = { id : user.id, text : user.first_name + ' ' + user.last_name };
if (user.active == 1) {
userList[0].children.push(option);
}
else {
userList[1].children.push(option);
}
});
return userList;
});
Custom Select2 Directive (Similar to this)
Vue.directive('select', {
twoWay: true,
bind: function () {
},
update: function (value) {
var optionsData
// retrive the value of the options attribute
var optionsExpression = this.el.getAttribute('options')
if (optionsExpression) {
// if the value is present, evaluate the dynamic data
// using vm.$eval here so that it supports filters too
optionsData = this.vm.$eval(optionsExpression)
}
var self = this
var select2 = $(this.el)
.select2({
data: optionsData
})
.on('change', function () {
// sync the data to the vm on change.
// `self` is the directive instance
// `this` points to the <select> element
self.set(select2.val());
console.log('emitting "select2-change"');
self.vm.$emit('select2-change');
})
// sync vm data change to select2
$(this.el).val(value).trigger('change')
},
unbind: function () {
// don't forget to teardown listeners and stuff.
$(this.el).off().select2('destroy')
}
})
Sample Implementation of Select2 From Template
<select
multiple="multiple"
style="width: 100%"
v-select="criteria.user_ids"
options="globals.users.data | userList"
>
</select>
I may have found something that works alright, although I'm not sure it's the best way to go about it. Here is my updated code:
Implementation of Select2 From Template
<select
multiple="multiple"
style="width: 100%"
v-select="criteria.reporting_type_ids"
options="globals.types.data | typeList 'reporttoauthorities'"
class="select2-users"
>
</select>
Excerpt from app.js
fetchUsers: function() {
this.$http.get('./api/v1/users/list', function(data, status, response) {
this.globals.users = data;
this.$nextTick(function () {
var optionsData = this.$eval('globals.users.data | userList');
console.log('optionsData', optionsData);
$('.select2-users').select2({
data: optionsData
});
});
});
},
This way works for me, but it still kinda feels hackish. If anybody has any other advice on how to do this, I would greatly appreciate it!
Thanks but I'm working on company legacy project, due to low version of select2, I encountered this issue. And I am not sure about the v-select syntax is from vue standard or not(maybe from the vue-select libaray?). So here's my implementation based on yours. Using input tag instead of select tag, and v-model for v-select. It works like a charm, thanks again #bakerstreetsystems
<input type="text"
multiple="multiple"
style="width: 300px"
v-model="supplier_id"
options="suppliers"
id="select2-suppliers"
>
</input>
<script>
$('#app').ready(function() {
var app = new Vue({
el: "#app",
data: {
supplier_id: '<%= #supplier_id %>', // We are using server rendering(ruby on rails)
suppliers: [],
},
ready: function() {
this.fetchSuppliers();
},
methods: {
fetchSuppliers: function() {
var self = this;
$.ajax({
url: '/admin_sales/suppliers',
method: 'GET',
success: function(res) {
self.suppliers = res.data;
self.$nextTick(function () {
var optionsData = self.suppliers;
$('#select2-suppliers').select2({
placeholder: "Select a supplier",
allowClear: true,
data: optionsData,
});
});
}
});
},
},
});
})
</script>
Related
I'm trying to make a simple "Load More" function for posts using Vue JS but when I try to append new posts, the previous ones are removed.
This is my PostWallComponent, which is supposed to hold all posts (<post-item-component>).
I fetch first 4 posts from the DB, store them in this.posts and then I send them using the v-for loop to <post-item-component>.
Then when someone clicks on the "More" button I call getPosts() function where I fetch another 4 posts from the DB. Here comes my problem - I store these new posts inside this.posts and I try to append them to the post container. They do append but the previous 4 get deleted from the container.
I think I know what is wrong - at line this.posts = response.data I replace old posts with new ones but I don't know how to append new ones without removing old ones. I tried to push() new posts to the array but that turned into a big mess (repetitive posts in the container).
<template>
<div class="container">
<div class="post_container">
<post-item-component v-for="post in this.posts"
v-bind:cuid="cuid"
v-bind:auid="auid"
v-bind:post="post"
v-bind:key="post.id">
</post-item-component>
<button type="button" #click="getPosts">More</button>
</div>
</div>
</template>
<script>
import PostItemComponent from "./PostItemComponent";
export default {
props: ['init_place', 'init_type', 'current_user_id', 'active_user'],
components: {
PostItemComponent
},
data() {
return {
place: this.init_place,
type: this.init_type,
cuid: this.current_user_id,
auid: this.active_user,
limit: 4,
offset: 0,
posts: [],
};
},
mounted() {
console.log('Component mounted.');
this.getPosts();
},
methods: {
getPosts() {
console.log('post');
axios.get('/p/fetch', {
params: {
place: this.place,
type: this.type,
cuid: this.cuid,
auid: this.auid,
offset: this.offset,
limit: this.limit,
}
})
.then((response) => {
console.log(response);
this.posts = response.data;
this.offset = this.limit;
this.limit += 4;
})
.catch(function (error) {
//currentObj.output = error;
});
}
}
}
</script>
In case someone wonders:
cuid is current user id = ID of user whose profile I opened
auid is active user ID = logged in user ID
<post-item-component> is just couple of divs displaying post header, body etc.
you could also use this.posts = this.posts.concat(response.data)
the problem is that the Array.push() method does not work with vue reactivity. For that you need to replace the whole array. As one proposed solution, you could use the spread operator to achieve this as so:
this.posts = [...this.posts, ...response.data];
This is replacing the whole array with a new array that is combining the old items with the fetched ones by spreading each of the array elements into the new array.
You can see an example here:
codesandbox example
I am trying to build a carousel using Laravel 5.6 and Vue.js. I can call the data from db, and console.log response.data. It works like expect it to. Script below.
<script>
$(document).ready(function() {
$("#availability").owlCarousel();
});
export default {
props: {
areaId: null,
tutorId: null,
},
data () {
return {
availability: []
}
},
methods: {
getAvailability () {
var that = this;
axios.get( '/' + this.areaId + '/' + this.tutorId + '/availability').then((response) => {
console.log(response.data)
that.availability = response.data;
});
}
},
mounted () {
this.getAvailability();
}
}
</script>
Now I would expect to be able to display the data like
{{availability.monday_begin}}
but it displays nothing, empty tags.
When I add a second .data to response, so change
that.availability = response.data;
to
that.availability = response.data.data;
The data object shows up as undefined in the vue-dev tools, and {{ availability.monday_begin }} throws an error cannot read property of undefined.
I also tried adding a v-if to the template, based on a couple different articles I read, but nothing I tried worked.
Thanks for your help.
I am playing with laravel and datatables.
Here is the table with filtering option in the form I want to understand.
Basically configured routes and controllers as in the example but cannot dynamically get values from a drop down list below via ajax.
<select class="form-control" id="asortment" name="asortment">
<option value="68">A</option>
<option value="5">B</option>
...
Javascript responsible for ajax communication:
<script type="text/javascript" charset="utf8" src="//cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script>
<script>
$(document).ready( function () {
$('#datatable').DataTable({
"processing": true,
"serverSide": true,
"ajax": {
url: "{{ route('api.products.index') }}",
data: function (d) {
d.product = $('input[name=product]').val();
d.fromDate = $('input[name=fromDate]').val();
d.toDate = $('input[name=toDate]').val();
d.asortment = $('input[name=asortment]').val();
},
},
"columns": [
{ "data": "Name", },
{ "data": "Type" },
{ "data": "Asortment" },
{ "data": "Margin" }
]
});
});
$('#search-form').on('submit', function(e) {
oTable.draw();
e.preventDefault();
});
</script>
My API controller looks like this:
class APIController extends Controller
{
public function getProducts(Request $request)
{
$product = $request->input('product');
$fromDate = $request->input('fromDate');
$toDate = $request->input('toDate');
$asortment = $request->input('asortment');
$query = DB::select('exec test.dbo.Products #startDate = ?, #endDate = ?, #asortment = ?, #produkt = ?', [$fromDate, $toDate, $asortment, $product]);
return datatables($query)->make(true);
}
}
Problem: Ajax takes 3 values (product, fromDate, toDate) but doesn't accept asortment, which is in select form.
I need a little help on why...:)
Instead of Using $('input[name=asortment]').val(); change it to $("#asortment").val(); (Pure jQuery way!).
$('input[name=YOUT_NAME]').val(); doesn't work with Radio Button/Select/Checbox.
val() allows you to pass an array of element values. This is useful
when working on a jQuery object containing elements like , , and s inside of a
. In this case, the inputs and the options having a value that
matches one of the elements of the array will be checked or selected
while those having a value that doesn't match one of the elements of
the array will be unchecked or unselected, depending on the type. In
the case of s that are part of a radio group and
s, any previously selected element will be deselected.
Setting values using this method (or using the native value property)
does not cause the dispatch of the change event. For this reason, the
relevant event handlers will not be executed. If you want to execute
them, you should call .trigger( "change" ) after setting the value.
This is mentioned in jQuery's documentation.
Problem: Although from the Vue DevTools I am passing the prop correctly and the router-view component has access to the data that it needs and in the correct format, whenever I try to access any of the data properties from within the template I get Uncaught TypeError: Cannot read property 'name' of null. It's really confusing because from the DevTools everything is a valid object and the properties are not null.
App.js
const game = new Vue({
el: '#game',
data: function() {
return {
meta: null,
empire: null,
planets: null
};
},
created: () => {
axios.get('/api/game').then(function (response) {
game.meta = response.data.meta;
game.empire = response.data.empire;
game.planets = response.data.planets;
});
},
router // router is in separate file but nothing special
});
main.blade.php
<router-view :meta="meta" :empire="empire" :planets="planets"></router-view>
script section of my Component.vue file
export default {
data: function() {
return {
}
},
props: {
meta: {
type: Object
},
empire: {
type: Object
},
planets: {
type: Array
}
}
}
Any ideas? Thanks in advance.
Because of your data is async loading so when my Component.vue renders your data in parent component may not be there. So you need to check if your data is loaded. You can try this code:
{{ meta != null && meta.name }}
PS: Your created hook should be:
created() {
axios.get('/api/game').then((response) => {
this.game.meta = response.data.meta;
this.game.empire = response.data.empire;
this.game.planets = response.data.planets;
});
},
router-view is a component from view-router which can help render named views. You can not pass empire and planets to it as those are props of your component.
You have to have following kind of code to pass empire and planets to your component:
<my-component :meta="meta" :empire="empire" :planets="planets"></my-component>
You can see more details around this here.
i am working in extjs. i have view as-
QbqnsResultmain.js
Ext.define('Balaee.view.qb.qbqns.QbqnsResultmain',
{
extend:'Ext.form.Panel',
requires:[
'Balaee.view.qb.qbqns.QbqnsResult'
],
id:'QbqnsResultmainId',
alias:'widget.QbqnsResultmain',
title:'Result',
height:400,
items:[
{
xtype:'QbqnsResult',
},
],
buttons:[
{
xtype:'button',
fieldLabel:'review',
action:'getreview',
name:'review',
formBind:true,
text:'Review',
},
{
xtype:'button',
fieldLabel:'papers',
action:'getpapers',
name:'papers',
formBind:true,
text:'Get all papers',
},
]});
and QbqnsResult.js-
Ext.define('Balaee.view.qb.qbqns.QbqnsResult',
{
extend:'Ext.view.View',
id:'QbqnsResultId',
alias:'widget.QbqnsResult',
//store:'kp.PollStore',
store:'qb.QbqnsStore',
config:
{
tpl:'<tpl for="1">'+
'<div id="main">'+
'</br>'+
//'<b>Question :-</b></br>'+
'<h1 id="q">Total number of Questions are:-</h1>{TotalQuestions}</br>'+
'<h1 id="q">Number of attempted Questions:-</h1> {Attempted}</br>'+
'<h1 id="q">Number of correct answers:-</h1> {CorrectAnswers}</br>'+
'<h1 id="q">Total score:-</h1> {Total}</br>'+
'<h1 id="q">Score you got is:-</h1> {Score}</br>'+
'<h1 id="q">percentage you got is:-</h1> {percentage}</br>'+
'<p>---------------------------------------------------------</p>'+
'</div>'+
'</tpl>',
itemSelector:'div.main',
}
});
On click of submit button,i want to show above view. So i had written code in controller as-
check:function()
{
var resultStore=Ext.create('Balaee.store.qb.QbqnsStore');
proxy=resultStore.getProxy();
Ext.apply(proxy.api,{
read:'index.php/QuestionBank/qbpaper/getResult',
create:'index.php/QuestionBank/qbpaper/getResult'
});
Ext.apply(proxy.reader,{
type:'json',
//root:'polls',
root:'questions'
});
Ext.apply(proxy.writer,{
type:'json',
//root:'polls',
root:'data'
});
var getdata=this.getLocalvalue();
console.log(getdata.data);
Paperno=getdata.data.questionPaperNo;
UserId=getdata.data.userId;
var answers = '{"data":[';
answers = answers + '{"paperNo":"'+Paperno+'","userId":"'+UserId+'"}';
answers =answers+']}';
console.log(answers);
resultStore.load({
params:{
data: answers
},
callback: function(records,operation,success){
console.log(records);
console.log("Successfully data send");
},
scope:this
});
var temp= Ext.getCmp('qbqnsId');
temp.removeAll();
var worldChaptor3 =temp.add({xtype:'QbqnsResultmain',
id:'QbqnsResultmainId',
store:resultStore});
},
So i want to bind resultStore to QbqnsResult view's tpl which i have included as xtype in QbqnsResultmain view. But resultStore get binded to Qbqnsresultmain view but not to Qbqnsresult which is included as item in it by means of its xtype. So how to bind store to it. Can someone guide me
One way to do it can be:
var worldChaptor3 =temp.add({xtype:'QbqnsResultmain',
id:'QbqnsResultmainId',
store:resultStore});
worldChaptor3.down('QbqnsResultmain > QbqnsResult').bindStore(resultStore);
But, it would be better if you do it in your QbqnsResultmain, i'll do it on the afterrender event, that way, you can bind the store automatically on creation.