How open first tab with vuejs? - javascript

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/

Related

how to bind value of v-for to v-if

I'm working with BootstrapVue. To my problem: I have a v-for in my template in which I have two buttons.
Looping over my v-for my v-if doesn't generate unique IDs and than after clicking one button each button will be triggered (from Open me! to Close me! and other way around).
How can I manage to get each button only triggers itself and doesn't affect the other?
I think I have to use my n of my v-for but I actually don't know how to bind this to a v-if..
Thanks in advance!
<template>
<div>
<div v-for="n in inputs" :key="n.id">
<b-button v-if="hide" #click="open()">Open me!</b-button>
<b-button v-if="!hide" #click="close()">Close me! </b-button>
</div>
<div>
<b-button #click="addInput">Add Input</b-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
id: null,
inputs: [{
id: 0
}],
hide: true,
};
},
methods: {
open() {
this.hide = false
},
close() {
this.hide = true
},
addInput() {
this.inputs.push({
id: this.id += 1;
})
}
}
};
</script>
Everything seems to look fine. In order to handle each button triggers,
you can maintain an object like so:
<script>
export default {
data() {
return {
inputs: [{id: 0, visible: false}],
};
},
methods: {
open(index) {
this.inputs[index].visible = false
},
close(index) {
this.inputs[index].visible = true
},
addInput() {
this.inputs.push({id: this.inputs.length, visible: false});
}
}
};
</script>
and your template should be like
<template>
<div>
<div v-for="(val, index) in inputs" :key="val.id">
<b-button v-if="val.visible" #click="open(index)">Open me!</b-button>
<b-button v-if="!val.visible" #click="close(index)">Close me! </b-button>
</div>
</div>
</template>
Edit:
You don't need to insert an id every time you create a row, instead can use the key as id. Note that the inputs is an object and not array so that even if you want to delete a row, you can just pass the index and get it removed.
I would create an array of objects. Use a boolean as property to show or hide the clicked item.
var app = new Vue({
el: '#app',
data: {
buttons: []
},
created () {
this.createButtons()
this.addPropertyToButtons()
},
methods: {
createButtons() {
// Let's just create buttons with an id
for (var i = 0; i < 10; i++) {
this.buttons.push({id: i})
}
},
addPropertyToButtons() {
// This method add a new property to buttons AFTER its generated
this.buttons.forEach(button => button.show = true)
},
toggleButton(button) {
if (button.show) {
button.show = false
} else {
button.show = true
}
// We are changing the object after it's been loaded, so we need to update ourselves
app.$forceUpdate();
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template>
<div>
<div v-for="button in buttons" :key="button.id">
<button v-if="button.show" #click="toggleButton(button)">Open me!</button>
<button v-if="!button.show" #click="toggleButton(button)">Close me! </button>
</div>
</div>
</template>
</div>

How to set a variable that vue can read that reacts to swiper's event handlers

I'm using vue-awesome-swiper and I want to be able to show or hide the Next/Previous buttons on specific conditions.
Specifically, when the slide has reached the beginning, it should show the NEXT button, then when it reaches the end, it should hide that button and show the PREVIOUS button instead.
If possible, I would want it to be set as a variable that v-if can hook on. How do I go about doing that?
HTML
<div id="swiper">
<div class="menu-tabs-icon-label">
<swiper ref="productMenu" :options="swiperNavMenu">
<swiper-slide>
<div class="nav-item">
Item 1
</div>
</swiper-slide>
<swiper-slide>
<div class="nav-item">
Item 2
</div>
</swiper-slide>
<swiper-slide>
<div class="nav-item ">
Item 3
</div>
</swiper-slide>
<div v-if="reachedEnd" class="swiper-button-prev" slot="button-prev"></div>
<div v-if="reachedBeginning" class="swiper-button-next" slot="button-next"></div>
</swiper>
</div>
</div>
JS
new Vue({
el: "#swiper",
components: {
LocalSwiper: VueAwesomeSwiper.swiper,
LocalSlide: VueAwesomeSwiper.swiperSlide
},
data: {
swiperNavMenu: {
freeMode: true,
slidesPerView: 5.75,
on: {
reachBeginning: function() {
reachedBeginning = true;
},
reachEnd: function() {
reachedEnd = true;
}
}
}
},
computed: {
swiperA() {
return this.$refs.productMenu.swiper;
}
}
});
Your problem is related that reachedBeginning and reachedEnd variables are not defined
...
data() {
return {
reachedBeginning: false,
reachedEnd: false,
swiperNavMenu: {
freeMode: true,
slidesPerView: 5.75,
on: {
reachBeginning: ()=> {
this.reachedBeginning = true;
},
reachEnd: ()=> {
this.reachedEnd = true;
}
}
}
}
},
...
I found that the answer lies in binding the instance to the variable. The final code should look like this:
data() {
const self = this; // this is the line that fixes it all
return {
reachedEnd: false,
swiperNavMenu: {
on: {
reachEnd: function(){
self.reachedEnd=true
}
}
}
};
},
Still thanks to sugars for pointing the way.

Click-and-edit text input with Vue

I'm looking for a click-and-edit Vue component.
I've found a fiddle and made some edits. It works like this:
The fiddle is here.
The problem: I need an additional click to make the input focused. How can I make it focused automatically?
The code from the fiddle. HTML:
<div id="app">
Click the values to edit!
<ul class="todo-list">
<li v-for = "todo in todos">
<input v-if = "todo.edit" v-model = "todo.title"
#blur= "todo.edit = false; $emit('update')"
#keyup.enter = "todo.edit=false; $emit('update')">
<div v-else>
<label #click = "todo.edit = true;"> {{todo.title}} </label>
</div>
</li>
</ul>
</div>
JS:
new Vue({
el: '#app',
data: {
todos: [{'title':'one value','edit':false},
{'title':'one value','edit':false},
{'title':'otro titulo','edit':false}],
editedTodo: null,
message: 'Hello Vue.js!'
},
methods: {
editTodo: function(todo) {
this.editedTodo = todo;
},
}
})
You can use a directive, for example
JS
new Vue({
el: '#app',
data: {
todos: [
{ title: 'one value', edit: false },
{ title: 'one value', edit: false },
{ title: 'otro titulo', edit: false }
],
editedTodo: null,
message: 'Hello Vue.js!'
},
methods: {
editTodo: function (todo) {
this.editedTodo = todo
}
},
directives: {
focus: {
inserted (el) {
el.focus()
}
}
}
})
HTML
<div id="app">
Click the values to edit!
<ul class="todo-list">
<li v-for="todo in todos">
<input
v-if="todo.edit"
v-model="todo.title"
#blur="todo.edit = false; $emit('update')"
#keyup.enter="todo.edit=false; $emit('update')"
v-focus
>
<div v-else>
<label #click="todo.edit = true;"> {{todo.title}} </label>
</div>
</li>
</ul>
</div>
You can find more info here
https://v2.vuejs.org/v2/guide/custom-directive.html
With #AitorDB's help I have written a Vue component for this, I call it Click-to-Edit. It is ready to use, so I'm posting it.
What it does:
Supports v-model
Saves changes on clicking elsewhere and on pressing Enter
ClickToEdit.vue: (vue 2.x)
<template>
<div>
<input type="text"
v-if="edit"
:value="valueLocal"
#blur.native="valueLocal = $event.target.value; edit = false; $emit('input', valueLocal);"
#keyup.enter.native="valueLocal = $event.target.value; edit = false; $emit('input', valueLocal);"
v-focus=""
/>
<p v-else="" #click="edit = true;">
{{valueLocal}}
</p>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
edit: false,
valueLocal: this.value
}
},
watch: {
value: function() {
this.valueLocal = this.value;
}
},
directives: {
focus: {
inserted (el) {
el.focus()
}
}
}
}
</script>
Edit for 3.x: [Breaking changes between 2.x and 3.x]
remove .native from the event handlers
change the focus hook to mounted as described in Custom Directives 3.x.
Built on #Masen Furer's work. I added some protection to handle when a user deletes all of the data. There is probably a way to accomplish this using "update" but I couldn't get it working.
I also added the ability to hit escape and abandon any changes.
<template>
<span>
<input type="text"
v-if="edit"
:value="valueLocal"
#blur="save($event);"
#keyup.enter="save($event);"
#keyup.esc="esc($event);"
v-focus=""/>
<span v-else #click="edit = true;">
{{valueLocal}}
</span>
</span>
</template>
<script>
export default {
props: ['value'],
data () {
return {
edit: false,
valueLocal: this.value,
oldValue: (' ' + this.value).slice(1)
}
},
methods: {
save(event){
if(event.target.value){
this.valueLocal = event.target.value;
this.edit = false;
this.$emit('input', this.valueLocal);
}
},
esc(event){
this.valueLocal = this.oldValue;
event.target.value = this.oldValue;
this.edit = false;
this.$emit('input', this.valueLocal);
}
},
watch: {
value: function() {
this.valueLocal = this.value;
}
},
directives: {
focus: {
inserted (el) {
el.focus()
}
}
}
}
</script>

VueJs watching deep changes in object

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
});

Vue.js 2 bind a class in a variable

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;
}

Categories