I am using Vuejs 2 with laravel. Currently i was working with select boxes for a permissions module and it is a box complex structure. The issue i am having is when i try to bind my nested array item in v-model it acts as string. everytime i check a box it overwrites the variable.
workspace.selectedOperations is the model that is overwriting.
This is the html for it :
<b-tab v-for="workspace in addedWorkspaces" :key="workspace.id" :title="workspace.title" active>
<div class="roles-permissions">
<label>Permissions</label>
<div class="permissions-path">
<ul class="pl-0">
<li v-for="entity in workspace.entities">
<b-form-checkbox>{{entity.title}}</b-form-checkbox>
<ul>
<li v-for="operation in entity.operations">{{workspace.selectedOperations}}
<b-form-checkbox v-model="workspace.selectedOperations" :value="operation.id">{{operation.title}}</b-form-checkbox>
</li>
</ul>
</li>
</ul>
</div>
</div>
</b-tab>
This is the script :
<script>
import Multiselect from 'vue-multiselect'
export default {
props : ['showModalProp'],
data () {
return {
title : '',
value: [],
entities : [],
addedWorkspaces : [],
selectedWorkspaces : '',
}
},
methods: {
getOperationsList(){
this.$http.get('entity').then(response=>{
response = response.body.response;
this.entities = response.data;
});
},
showModal() {
this.$refs.myModalRef.show()
},
hideModal() {
this.$refs.myModalRef.hide()
},
onHidden(){
this.$emit('HideModalValue');
},
addWorkspaces(){
this.addedWorkspaces = this.selectedWorkspaces;
this.selectedWorkspaces = [];
for (var i = this.addedWorkspaces.length - 1; i >= 0; i--) {
this.addedWorkspaces[i].entities =
JSON.parse(JSON.stringify(this.entities));
this.addedWorkspaces[i].selectedOperations = [];
}
}
},
mounted(){
this.getOperationsList();
},
components: {
Multiselect
},
watch:{
showModalProp(value){
if(value){
this.showModal();
}
if(!value){
this.hideModal();
}
}
},
computed :{
options(){
return this.$store.getters.getWorkspaces;
}
}
}
</script>
if you want v-model of b-form-checkbox to be an array,you should wrap your b-form-checkbox by b-form-checkbox-group and set v-model on it
see https://bootstrap-vue.js.org/docs/components/form-checkbox
Related
import Vue from 'vue';
export default {
data() {
return {
cities : [
'Bangalore','Chennai','Cochin','Delhi','Kolkata','Mumbai'
],
value: '',
open: false,
current: 0
}
},
props: {
suggestions: {
type: Array,
required: true
},
selection: {
type: String,
required: true,
twoWay: true
}
},
computed: {
matches() {
return this.suggestions.filter((str) => {
return str.indexOf(this.selection) >= 0;
});
},
openSuggestion() {
return this.selection !== "" &&
this.matches.length != 0 &&
this.open === true;
}
},
methods: {
enter() {
this.selection = this.matches[this.current];
this.open = false;
},
up() {
if(this.current > 0)
this.current--;
},
down() {
if(this.current < this.matches.length - 1)
this.current++;
},
isActive(index) {
return index === this.current;
},
change() {
if (this.open == false) {
this.open = true;
this.current = 0;
}
},
suggestionClick(index) {
this.selection = this.matches[index];
this.open = false;
},
}
}
<template>
<div style="position:relative" v-bind:class="{'open':openSuggestion}">
<input class="form-control" type="text" v-model="selection"
#keydown.enter = 'enter'
#keydown.down = 'down'
#keydown.up = 'up'
#input = 'change'
/>
<ul class="dropdown-menu" style="width:100%">
<li
v-for="suggestion in matches"
v-bind:class="{'active': isActive($index)}"
#click="suggestionClick($index)"
>
{{ suggestion }}
</li>
</ul>
</div>
</template>
Getting eslint error [vue/require-v-for-key] Elements in iteration expect to have 'v-bind:key' directives.eslint-plugin-vue.
Tried changing to v-bind:key="suggestion.id" after changing, eslint error is not showing but issue is Autocomplete is not displaying(completely not working).
Can any one correct me if anything is wrong in the code.
When using v-for Vue would like to have a hint on how to identify the items in your list. You don't have to do it, but it is considered best practice and therefore eslint marks it.
To give the hint you add a key attribute with a unique value (id, some text, whatever) to the rendered list item like this:
<li
v-for="suggestion in matches"
v-bind:key="suggestion.id"
>
v-bind:key or :key in short. The value must be of type number | string | boolean | symbol.
See the docs for more info: https://v2.vuejs.org/v2/guide/list.html#Maintaining-State
As you already added the v-bind:key, Now in your code, I believe the issue is with $index, remove that and use the suggestions' index.
Here is the updated code, replace your dropdown-menu and check:
<ul class="dropdown-menu" style="width:100%">
<li
v-for="(suggestion, index) in matches"
v-bind:key="suggestion.id"
v-bind:class="{'active': isActive(index)}"
#click="suggestionClick(index)"
>
{{ suggestion }}
</li>
</ul>
I am using vue #keyup on input, this uses method with axios call:
<template>
<div>
<input type="text" placeholder="Where are you from?" v-model="from" #keyup="getPlace">
<ul>
<li v-for="place in places">{{ place.name }}<li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
places: [],
from: ''
}
},
methods() {
if(this.from.length <= 3) { this.places = []; return; }
getPlace() {
axios.get('/places?name='+this.from).then((res)=>{
this.places = [];
for(var i = 0; i<res.data.length; i++) {
this.places.push(res.data[i]);
}
}).catch((err)=>{console.log(err)});
}
}
};
</script>
Now this works but it has a big problem, each call it updates array of places but it is late, so the method is called and array has been returned to [] but after the response is back it is populating the array for each keyup (if you type fast)... I am switching from jquery front to this and I never ever had a problem with this :O
This doesn't answer the "how to abort method", but you could use a different approach for your example: Fetching all places at the beginning (mounted()) and filter them based on the input in the frontend by using a computed property.
var app = new Vue({
el: '#app',
data() {
return {
places: [],
from: ''
}
},
mounted() {
this.getPlaces();
},
computed: {
placesList() {
let result = []
let places = this.places
let from = this.from
if (from !== '') {
result = this.places.filter(element => {
return element.name.toLowerCase().includes(from.toLowerCase())
})
} else {
result = places
}
return result
}
},
methods: {
getPlaces() {
// axios call..
this.places = [{
id: 1,
name: "Germany"
}, {
id: 2,
name: "USA"
}, {
id: 3,
name: "Spain"
}]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<input type="text" placeholder="Where are you from?" v-model="from">
<br /> input text: {{ from }}
<hr>
<ul>
<li v-for="place in placesList" :key="place.id">
{{ place.name }}
</li>
</ul>
</div>
How do I open the first tab with VueJS? I have made two components but I don't know how to open the first tab automatically. This is what I've tried, but it doesn't work:
In mounted when i do console.log(this.tabs) return only 0 but before i do this.tabs = this.$children
Tabs.vue
<template>
<div>
<div class="tabs-header">
<ul>
<li v-for="tab in tabs" :key="tab.id" :class="{'is-active' : tab.isActive}">
<a :href="tab.href" #click="selectTab(tab)">{{ tab.name }}</a>
</li>
</ul>
</div>
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "Tabs",
data: function(){
return { tabs: null };
},
created: function() {
this.tabs = this.$children;
//this.tabs[0].isActive = true;
},
methods: {
selectTab (selectedTab){
this.tabs.forEach(tab => {
tab.isActive = (tab.href == selectedTab.href);
});
}
}
}
</script>
This is my second components $children
Tab.vue
<template>
<div v-show="isActive">
<slot></slot>
</div>
</template>
<script>
export default {
name: "Tabs",
props: {
name: { require: true },
selected: { default: false }
},
data() {
return { isActive: false }
},
computed: {
href() {
return '#' + this.name.toLowerCase().replace(/ /g,'-');
}
},
mounted() {
this.isActive = this.selected;
},
methods: {
select(){
this.isActive = true;
}
}
}
</script>
This line doesn't work:
//this.tabs[0].isActive = true;
I copy pasted the code from laracasts.com/series/learn-vue-2-step-by-step/episodes/11 and the first tab is opened by default.
It is the case because in the html, you have :selected=true set on the first tab.
If you want to open the second tab by default, move :selected=true to the second tab, like this : https://jsfiddle.net/ahp3zzte/1/
If you want to change the default tab dynamically, remove :selected=true from the html and call the selectTab method in the js. Also note that to do this, you need to use mounted instead of created. Check this other fiddle : https://jsfiddle.net/0402y2ew/
I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});
I`m using the Vue.js 2 and Laravel 5.3 to build a web.
When I click the ajaxbtn, the class do not bind in the variable, Any idea?
*Here is the html.
<div id="root" class="container">
<ajaxbtns>
<ajaxbtn name="get taiwanstay" url="api/taiwanstay" :selected="true" ></ajaxbtn>
<ajaxbtn name="get itwyp" url="api/itwyp" ></ajaxbtn>
</ajaxbtns>
</div>
*Here is the script
Vue.component('ajaxbtns',{
template:
`
<div class="tabs">
<ul>
<slot></slot>
</ul>
</div>
`,
data : function () {
return {
allAjaxBtn : []
};
},
created: function () {
this.allAjaxBtn = this.$children;
}
});
Vue.component('ajaxbtn',{
template:
`
<li :class="{ 'is-active' : btnActive }">
<a #click="ajaxbtnClick(name)" href="#" >#{{ name }}</a>
</li>
`,
props : {
name: { required: true },
url : { required: true },
selected: { default : false }
},
data :function () {
return {
btnActive : false
}
},
created: function () {
this.btnActive = this.selected;
},
methods : {
ajaxbtnClick : function (name) {
this.$parent.allAjaxBtn.forEach( btn => {
this.btnActive = (btn.name == name);
});
}
}
});
new Vue({
el: '#root'
});
The issue is not with the binding of the variable, that works fine. The problem is that btnActive will change for each iteration. You may get lucky in the case that the last btn matches, which would set it to true. However, if the condition was met earlier, it would be switched from true to false anyway.
Instead, conditionalize your query and then assign the btn to the btnActive:
if (btn.name == name) {
this.btnActive = btn;
}