Array of inputs, with last field always blank for new add - vuejs2

JSBin and Stackoverflow snippet are below.
I am trying to have a list of input components. If all input components are filled with a value (not blank), then there should be a "new blank field" visible at the end for the user to type into. When he types into it, it should make this field apart of the list above it, maintaining focus in it.
However the problem I'm having is, focus maintains in the new field, and never moves into the array. Here is my code:
JSBIN and stackoverflow snippet - https://jsbin.com/cudabicese/1/edit?html,js,output
const app = new Vue({
el: '#app',
data: {
inputs: [
{ id:'foo', value:'foo' },
{ id:'bar', value:'bar' }
]
},
methods: {
addRow(e) {
this.inputs.push({
id: Date.now(),
value: e.target.value
})
},
deleteRow(index) {
this.inputs.splice(index, 1)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app">
<ul>
<li v-for="(input, index) of inputs">
<input type="text" v-model="input.value">
</li>
<li v-if="inputs.filter(input => !!input.value).length">
<input type="text" #input="addRow">
</li>
</ul>
</div>

I'd recommend you put the input for the list within a computed function vs directly using the data. The examples at https://v2.vuejs.org/v2/examples/ are a good place to start.

Related

Vue - Select all checkbox

I'm relatively new to Vue so bare with me on this.
I have a v-for which produces a list of checkboxes, these all individually function correctly if I were to click on them separately, what I am after, like the title says is a select all checkbox to sit ontop which selects all in the list, but I'm running into a few issues which I don't understand, see relevant code below:
new Vue({
el: "#lister-app",
mixins: [pagination, baseDownloadParent],
components: { selectField, articleItem, documentItem },
data: {
facets: {},
isCheckAll: false,
},
computed: {
getFacets() {
return this.facets;
},
getFacetsLength() {
return this.facets.length;
},
},
methods: {
toggleSelect(item, facet) {
if (!this.params[facet.name]) Vue.set(this.params, facet.name, []);
const existElem = this.params[facet.name].findIndex((el) => {
return el === item.identifier;
});
if (existElem !== -1) this.params[facet.name].splice(existElem, 1);
else this.params[facet.name].push(item.identifier);
},
checkAll(){
console.log('FunctionWhichChecksAll');
},
},
});
<label class="option-checkbox" for="Select all">
<input id="Select all" class="option-checkbox__input" type="checkbox" #click='checkAll()' v-model='isCheckAll'>
<span class="option-checkbox__text">Select all</span>
<span class="option-checkbox__icon"></span>
</label>
<option-field inline-template v-for="(item, i) in facet.items" :for="item.name" :key="i + item.name">
<label class="option-checkbox">
<input :id="item.name" class="option-checkbox__input" type="checkbox" v-model="checked" #change="toggleSelect(item, facet)">
<span class="option-checkbox__text">{{item.name}} </span>
<span class="option-checkbox__icon"></span>
</label>
</option-field>
What I am picturing is as piece of script which is inside the checkAll() function?
Any help would be appreciated, thank you in advance.
Here is a working demo, based on your code (simplified):
https://stackblitz.com/edit/vue-prghrq?file=src%2FApp.vue
Checkboxes in Vue can sometimes be a mess, when you combine v-model with #click handlers and especially if you have a check-all field. That's why I usually don't use v-models for it. Using only the :checked value as a one-way binding makes it easier to read and to maintain. With the click handler you can then update the state of each entry.

Vuejs multiple active buttons

I'm trying to create a list where every list item contains a button and i want a user to be able to click multiple button. I'm generating my list like so:
<ul>
<li v-for="item in items" :key="item.id">
<button type="button">{{item.title}}</button>
</li>
</ul>
but the problem with my code is whenever a user click a button, it turns the rest of the buttons to "unclicked". been trying to play with the focus and active stats but even with just css i cant get to enable multiple select .
i did manage to change the look of the current selected button:
button:focus {
outline: none;
background-color: #6acddf;
color: #fff;
}
any idea how can i allow multiple buttons to be clicked?
to make things a bit clearer, i am going to create an AJAX call later and pass the item.id of each item where it's button is clicked
I would much rather avoid changing the data structure if possible
Well you have to store somewhere that you clicked on the clicked item.
If you can't edit the items array then you can always create a new one, like isClicked where you store those values.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'baz'
}
],
isClicked: []
},
beforeMount() {
// set all values to false
this.items.forEach((item, index) => this.$set(this.isClicked, index, false))
},
methods: {
clicked(index) {
// toggle the active class
this.$set(this.isClicked, index, !this.isClicked[index])
}
}
})
.active {
background: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index">
<button #click="clicked(index)" :class="{'active': isClicked[index]}">{{item.title}}</button>
</div>
</div>
Or you can use vuex for storing those values.
However you can just use Vues event to manipulate the classList property, like:
new Vue({
el: '#app',
data: {
items: [1, 2, 3, 4, 5, 6, 7]
}
})
.active {
color: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-for="i in items" #click="e => e.target.classList.toggle('active')">{{ i }}</button>
</div>
But it doesn't feel right, IMHO.
Also you can use cookies or localStorage to store those states. So it's really up to you.
Use id attribute for list items to make it unique.
<ul>
<li v-for="item in items" :key="item.id" :id="item.id">
<button type="button" #click="doThis">{{item.title}}</button>
</li>
</ul>
new Vue({
el: '#app',
data: {},
methods: {
doThis() {
// Use this to access current object
}
}
});

Vue-bootstrap nested checkboxes

I want to have sidebar that would represent filter and in there I would have checkboxes that would represent what should be shown to user. I found this: https://bootstrap-vue.js.org/docs/components/form-checkbox/#indeterminate-tri-state-support
But this only goes one level deep and I would need to go two levels. An example of what I am trying to achieve:
Select All
-Breakfest
-Eggs
-Bacon
-Sandwich
-Lunch
-Salad
-Chicken
-Fish
-Dinner
-Pancakes
-Tacos
-Beef
Now I want something like example in documentation from link but that goes one level deeper.
It should be simple to adopt this to Bootstrap Vue since Bootstrap Vue only abstracts HTML. I just added the first filter, you should get the idea.
HTML
<script src="https://unpkg.com/vue"></script>
<div id="app">
<input type="checkbox" v-model="all">Select all<br>
<input type="checkbox" v-model="filters.breakfast.breakfast" class="indent">Breakfast<br>
<input type="checkbox" v-model="filters.breakfast.eggs" class="double-indent">Eggs<br>
<hr>
All: {{ all }}<br>
Breakfast: {{ filters.breakfast.breakfast }}<br>
Eggs: {{ filters.breakfast.eggs }}<br>
</div>
JavaScript
You may use a loop at 'filters.breakfast.breakfast' (). I'd use Lodash for this because the properties mustn't be known.
new Vue({
el: '#app',
data: () => ({
all: false,
filters: {
breakfast: {
breakfast: false,
eggs: false
}
}
}),
watch: {
all () {
this.filters.breakfast.breakfast = !this.filters.breakfast.breakfast
},
'filters.breakfast.breakfast' () {
this.filters.breakfast.eggs = !this.filters.breakfast.eggs
}
}
})
CSS
.indent {
margin-left: 1em;
}
.double-indent {
margin-left: 2em;
}
Preview: http://jsfiddle.net/xv3y04j2/
Basically, I created three batches of tri-state groupings (tri-state +3 choices each), and then created another tri-state checkbox at the top that changes it's state based on the number of choices selected in each of the three groupings.

Data attribute bound to external variable not reactive to variable changes

I'm beginning to incorporate Vue.js (v2.5.17) for a few small things on an ecommerce website, but I'm new to Vue and having some troubles. Due to some Laravel blade structure that I don't have the option of refactoring at this time, I have to split the various parts of this functionality into distinct components, and I'm having trouble with the data in one Vue instance reacting to events on the other.
Here's a CodePen with a stripped down version of the issue: https://codepen.io/jgabrielsen/pen/bxRroM/
For easy reference, the JS:
var cartStore = {
state: {
products: [PRODUCT ARRAY],
active: false
},
}
var cartHeader = new Vue({
el: '#cartHeader',
data: {
products: cartStore.state.products,
},
methods: {
setActiveTrue: function() {
cartStore.state.active = true;
console.log('Show Cart');
},
setActiveFalse:function() {
cartStore.state.active = false;
console.log('Hide Cart');
},
},
})
var cartPreview = new Vue({
el: '#cartPreview',
data: {
products: cartStore.state.products,
active: cartStore.state.active,
},
methods: {
total: function() {
var sum = 0;
for (var i = 0; i < this.products.length; i++) {
sum += this.products[i][1]
}
return sum
},
},
})
And the HTML
<div class="header">
<a id="cartHeader" #mouseover="setActiveTrue" #mouseout="setActiveFalse">CART({{ this.products.length }})</a>
</div>
<div id="cartPreview" v-show="active">
<ul>
<li v-for="product in products">
<div class="col-80">
{{ product[0] }}
</div>
<div class="col-20" style="text-align:right;">
${{ product[1] }}
</div>
</li>
</ul>
<div class="row">
<div class="col-100" style="text-align:right;">
Total Products: {{ this.products.length }}<br>
Total: ${{ this.total() }}
</div>
</div>
</div>
Long story short, I need the cartPreview instance to show up when cartHeader instance is hovered over (a basic popover effect).
Here's how I'm currently trying to do this:
I have a var cartStore that contains an array of all the cart preview data, as well as an active state.
cartPreview has a data attribute active bound to cartStore.state.active (which is false by default) and v-show="active", so it's hidden until something sets it's data attribute active to true.
cartHeader has #mouseover="setActiveTrue" and #mouseout="setActiveFalse", to toggle cartPreview's active attribute by way of the bound state in cartStore.
I can tell that the mouseover and mouseout events are firing because cartStore.state.active does change to true and false correctly and the console logs fire, but the corresponding data attribute in the cartPreview is not reactive to these changes.
I feel like I must be overlooking something super simple and/or making some major noob mistakes, but after poring over my code dozens of times and searching high and low, I'm stumped as to why it's not reactive.
I finally figured out a solution.
cartStore.state.products was reactive between cartHeader and cartPreview for adding and removing the products (methods I removed from my example because I didn't think they were relevant), but cartStore.state.active wasn't. The only difference I could see was that the cart data in products was stored in an array and active wasn't, so I made cartStore.state.active an array:
var cartStore = {
state: {
products: [PRODUCT ARRAY],
active: [ false ]
},
}
...updated it with splice:
methods: {
setActiveTrue: function() {
this.active.splice(0,1,true);
},
setActiveFalse:function() {
this.active.splice(0,1,false);
},
},
...and added v-show="active[0]" to the component, and lo and behold, it works.

Adding radio buttons to Laravel Spark - Vue component page

I tried to stay away from the Vue components of Spark as much as possible but I discovered I had to implement a certain mail settings so I can't hold it much longer.
Luckily the Spark documentation contains a small cookbook for adding profile fields:
https://spark.laravel.com/docs/4.0/adding-profile-fields
Most parts are within my (limited PHP) comfort zone:
First the blade php:
Mail settings
<div class="col-md-6">
<label class="radio-inline"><input type="radio" value="profile" v-model="form.type" name="profile">Profile</label>
<label class="radio-inline"><input type="radio" value="website" v-model="form.type" name="website">Website</label>
<label class="radio-inline"><input type="radio" value="combined" v-model="form.type" name="combined">Combined</label>
<span class="help-block" v-show="form.errors.has('mail-settings')">
#{{ form.errors.get('mail-settings') }}
</span>
</div>
</div>
Which is integrated:
<!-- Update Mail settings -->
#include('settings.profile.update-mail-settings')
So as can be seen in the previous code block, I wish to store the result of 3 radio buttons.
However the linked Vue js file is giving my headaches:
Vue.component('update-mail-settings', {
props: ['user'],
data() {
return {
form: new SparkForm({
profile: ''
website: ''
combined: ''
})
};
},
mounted() {
this.form.mailsettings = this.user.mailsettings;
},
methods: {
update() {
Spark.put('/settings/profile/mail-settings', this.form)
.then(response => {
Bus.$emit('updateUser');
});
}
}
});
But how on earth do I integrate the radio buttons in the SparkForm?
In Vue, data binding occurs when you v-model to the object by name. Or in other words, you call v-model="object.property" on an input. When the user fills out the form, the value of form.type will match the form input. So simply change your form object to read:
data() {
return {
form: new SparkForm({
type: '' <- this can now be v-modeled to "form.type"
})
};
},
Your radio buttons don't need to change because they are bound correctly: v-model="form.type"
https://v2.vuejs.org/v2/guide/forms.html#Radio