Vue alternate classes in v-for - vue.js

I have an array (history) that is being pushed two items every time a button is pressed. Those two items will need to have different css styles when printed.
HTML
<ul class="info">
<li :class="{ 'style-one' : toggle, 'style-two' : toggle }" v-for="item in history">{{item}}</li>
</ul>
JS (Vue)
methods: {
attack: function() {
this.history.unshift(this.playerDamaged);
this.history.unshift(this.monsterDamaged);
}
The problem with this is there is no way to change the truthiness of toggle during the loop. Is there a better way to approach this?

SOLUTION 1 :
You can use this code :
<ul class="info">
<li v-for="item in history" :key="item"
:class="{ 'style-one' : item.isPlayer, 'style-two' : !item.isPlayer }"
>
{{ item.text }}
</li>
</ul>
methods: {
attack: function() {
this.history.unshift({ text: this.playerDamaged, isPlayer: true });
this.history.unshift({ text: this.monsterDamaged, isPlayer: false });
}
UPDATED - SOLUTION 2 [No use of objects] :
You can use an other solution with no objects :
<ul class="info">
<li v-for="(item, index) in history" :key="item"
:class="'style-' + ((index % numberOfPlayers) + 1)"
>
{{ item }}
</li>
</ul>
//This part don't have to deal with Array of Objects :
methods: {
attack: function() {
this.history.unshift( this.playerDamaged );
this.history.unshift( this.monsterDamaged );
},
computed: {
numberOfPlayers: function() {
return 2;
}
}
If you want to add a player (ex: monster 2) you have to update the computed numberOfPlayers to 3 (or better : listOfPlayers.length if you have) and create a class ".style-3".
Code example :
new Vue({
el: "#app",
data: function() {
return {
myArray: ['player attack', 'monster attack','player attack', 'monster attack']
}
},
computed: {
numberOfPlayers: function() {
return 2;
}
}
});
.style-1 {
color: blue;
}
.style-2 {
color: red;
}
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<div v-for="(item, index) in myArray" :key="item"
:class="'style-' + ((index % numberOfPlayers) + 1)"
>
{{ item }}
</div>
</div>

Related

VueJS renders variable twice in loop

When I press some key in editable <li> it shows this content twice. Out of this v-for loop, it shows only once. So in this array is for example ['a'] but in <li> it shows 'aa'
new Vue({
el: '#app',
data: {
component: {
items: ['']
}
},
methods: {
onKeydown(e, index) {
if(e.key === 'Enter') {
e.preventDefault()
this.component.items.push('')
}
},
onInput(e, index, item) {
this.component.items.splice(index, 1, e.target.innerHTML)
}
}
});
<div id="app">
<ul>
<li contenteditable="true"
v-for="(item, index) in component.items"
:key="index"
#keydown="onKeydown($event, index)"
#input="onInput($event, index, item)"
>{{ item }}</li>
</ul>
{{ component.items }}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Vue.js Autocomplete

I am building a live search component with Vuejs. The search is working as expected. On keyup the method populates an unordered list. I am at a loss but need to:
Click on a selection from the search results and populate the input with that name.
Get the selected id.
Any suggestions?
The View
<label>Vendor:</label>
<input type="text" v-model="vendor" v-on:keyup="get_vendors">
<div class="panel-footer" v-if="vendors.length">
<ul class="list-group">
<li class="list-group-item for="vendor in vendors">
{{ vendor.vendor_name }}
</li>
</ul>
</div>
The Script
export default {
data: function() {
return {
vendor:'',
vendors: []
}
},
methods: {
get_vendors(){
this.vendors = [];
if(this.vendor.length > 0){
axios.get('search_vendors',{params: {vendor: this.vendor}}).then(response =>
{
this.vendors = response.data;
});
}
}
}
}
</script>
The Route
Route::get('search_vendors', 'vendorController#search_vendors');
The Controller
public function search_vendors(Request $request){
$vendors = vendor::where('vendor_name','LIKE','%'.$request->vendor.'%')->get();
return response()->json($vendors);
}
This is what I came up with. Works nicely.
The View
<label>Vendor:</label>
<input type="text" v-model="vendor" v-on:keyup="get_vendors" class="col-xl-6 form-control ">
<div class="panel-footer autocomplete-box col-xl-6">
<ul class="list-group">
<li v-for="(vendor,id) in vendors" #click="select_vendor(vendor)" class="list-group-item autocomplete-box-li">
{{ vendor.vendor_name }}
</li>
</ul>
</div>
The Script
export default {
data: function() {
return {
vendor:'',
vendor_id:'',
vendors: []
}
},
methods: {
select_vendor(vendor){
this.vendor = vendor.vendor_name
this.vendor_id = vendor.id
this.vendors = [];
},
get_vendors(){
if(this.vendor.length == 0){
this.vendors = [];
}
if(this.vendor.length > 0){
axios.get('search_vendors',{params: {vendor: this.vendor}}).then(response => {
this.vendors = response.data;
});
}
},
},
}
</script>
The Route
Route::get('search_vendors', 'vendorController#search_vendors');
The Controller
public function search_vendors(Request $request){
$vendors = vendor::where('vendor_name','LIKE','%'.$request->vendor.'%')->get();
return response()->json($vendors);
}
The CSS
.autocomplete-box-li:hover {
background-color: #f2f2f2;
}
.autocomplete-box{
position: absolute;
z-index: 1;
}
If you want to get the id of the vendor you can do it using vendor.vendor_id which should be present in the vendor object which is returned from the server and if you want to populate the vendors array with new options you can have a watch or a #change (binding) function to add the entered text field as the new vendor in the vendor array. Also, I would suggest that you download the vue extension as it will help you a lot in debugging
<label>Vendor:</label>
<input type="text" v-model="vendor" v-on:keyup="get_vendors">
<div class="panel-footer" v-if="vendors.length">
<ul class="list-group">
<li class="list-group-item for="(vendor,index) in vendors">
{{ index }}
{{ vendor.vendor_id }}
{{ vendor.vendor_name }}
</li>
</ul>
</div>

How to bind the class name of an element to a changing parameter

In my code I have an increment button like so that works as expected with my counter variable set to zero at the start.
<div id="count">
<div>{{counter}}</div>
<v-btn #click="increment">Increment</v-btn>
</div>
<script>
...
data () {
return {
counter: 0
}
},
methods: {
increment () {
this.counter++
}
}
...
</script>
I then have an unordered list of items and I would like to bind the classname of the list items so that they change as the value of counter is updated. My isReached function is shown below.
<div class="container">
<ul class="progressbar">
<li v-for="i in noOfMilestones" :key="i" v-bind:class="isReached(key, counter)"></li>
</ul>
</div>
isReached (key, counter) {
if (counter >= (this.toReach / this.noOfMilestones) * key) {
return 'active'
}
return ''
}
The problem is that the class name is set initially to ' ' as the function is run the first time but as I increment my counter the class name never changes. How can I dynamically bind my classname to the changes in the counter variable?
Using a method within your templates rendering is not efficient (see Computed Caching vs Methods).
This looks like it can be done with a simple class-binding object expression
<li v-for="i in noOfMilestones" :key="i"
:class="{ active: counter >= toReach / noOfMilestones * i }"></li>
Demo...
new Vue({
el: '#app',
data: () => ({
counter: 0,
toReach: 10,
noOfMilestones: 5
}),
methods: {
increment () {
this.counter++
}
}
})
ul {
list-style: none;
}
li {
width: 20px;
height: 20px;
background: grey;
}
li.active {
background: green;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<div id="count">
<div>{{counter}}</div>
<button #click="increment">Increment</button>
</div>
<div class="container">
<ul class="progressbar">
<li v-for="i in noOfMilestones" :key="i" :class="{ active: counter >= toReach / noOfMilestones * i }">
{{i}}
</li>
</ul>
</div>
</div>

Vue: Getting a default value for v-select

I have a list of cartItems and I'm generating a dropdown for each one. Each cartItem has a field called orig_quantity that I'm looking to set the default value of the drop down to. I tried doing :value="item.orig_quantity" but that doesn't seem to be doing it.
computed: {
quantityOptions: function() {
return [1,2,3]
}
}
<div v-for="(item, index) in cartItems"
<div>{item.product_name}</div>
<v-select :options="quantityOptions"
v-on:change="updateQuantity($event,item)">
</v-select>
</div>
Sorry about that - I misunderstood your question at first.. I have updated my answer below.. This should be sufficient to get the idea across (the code stands to be cleaned - its 'pseudo' enough to get the idea across, though)..
In CodePen form, which I find easier to read:
https://codepen.io/oze4/pen/vMLggE
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: "#app",
data: {
cartItems: [{
product_name: "Chair",
original_quantity: 7,
total_quantity: 9,
pending_quantity: null,
price: "$19.99"
},
{
product_name: "Couch",
original_quantity: 3,
total_quantity: 6,
pending_quantity: null,
price: "$29.99"
}
],
},
methods: {
getStock(cartItem) {
let ci = this.cartItems.find(i => {
return i.product_name === cartItem.product_name;
});
return [...Array(ci.total_quantity + 1).keys()].slice(1);
},
updateQty(cartItem) {
alert("this is where you would post");
let ci = this.cartItems.find(i => {
return i.product_name === cartItem.product_name;
});
ci.original_quantity = ci.pending_quantity;
}
}
});
h5,
h3 {
margin: 0px 0px 0px 0px;
}
.reminder {
color: red;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-select#2.6.4/dist/vue-select.js"></script>
<div id="app">
<h4>Cart Items:</h4>
<div v-for="(item, index) in cartItems">
<h3>{{ item.product_name }}</h3>
<h5>Price: {{ item.price }} | InBasket: {{ item.original_quantity }}</h5>
<small>Change Quantity: </small>
<v-select :value="item.original_quantity" :options="getStock(item)" #change="item.pending_quantity = $event"></v-select>
<button #click="updateQty(item)" type="button">Update {{ item.product_name }} Qty</button><small class="reminder">*update after changing quantity</small>
<br/>
<hr/>
</div>
</div>
You should be able to add a v-model attribute to the select.
<div v-for="(item, index) in cartItems"
<v-select v-model="item.orig_quantity" :options="quantityOptions"
v-on:change="updateQuantity($event,item)">
</v-select>
</div>

vue.js - Dynamically render items on scroll when visible

I have list of 100+ items and rendering takes too much time. I want to show just the once that are visible, and rest on scroll.
What's the best approach?
I have this snippet below, but the vue.set() isn't working.
var dbItems = [{name: 'New item'}, {name:'Another'}, {name:'Third'}];
var app = new Vue({
el: '#app',
data: {
// if I put items : dbItems, then for some reason the Vue.set() doesn't work!!
items : [],
},
methods: {
init: function () {
this.items = dbItems; // we add all items
},
makeItemVisible : function(id) {
console.log("Making visible #"+id);
this.items[id].show = 1;
Vue.set(this.items, id, this.items[id]);
}
}
});
app.init();
app.makeItemVisible(1); // this works
$(document).on('scroll', function(){
// function to show elements when visible
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app" v-cloak>
<button v-on:click="makeItemVisible(0)">MAKE VISIBLE - This button doesn't work</button>
<div class="items" v-show="items.length">
<!-- I dont know why, but (key, item) had to be switched compared to VUE documentation! -->
<div v-for="(key, item) in items">
<div v-if="item.show" style="border:2px solid green;height:700px">
You can see me: {{ item.name }} | ID: {{ key }}
</div>
<div class="item-blank" data-id="{{ key }}" v-else style="border:2px solid red;height:700px">
{{ item.name }} invisible {{ key }}
</div>
</div>
</div>
</div>
Solved.
Edit: This Vue.js is only useable in Chrome... otherwise it is incredibly slow (Firefox is slowest), it works better when loading the whole document in HTML at once.
var dbItems = [{name: 'New item'}, {name:'Another'}, {name:'Third'}];
var app = new Vue({
el: '#app',
data: {
items : dbItems
},
methods: {
makeItemVisible : function(id) {
console.log("Making visible #"+id);
Vue.set(this.items[id], 'show', 1);
}
}
});
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return (elemTop <= docViewBottom && elemTop >= docViewTop) || (elemBottom >= docViewTop && elemBottom <= docViewBottom);
}
var fn = function(){
$('.item-blank').each(function(){
if(isScrolledIntoView(this)) {
app.makeItemVisible($(this).attr('data-id'));
}
});
};
$(window).scroll(fn);
fn(); // because trigger() doesn't work
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app" v-cloak>
<div class="items" v-show="items.length">
<div v-for="(item, index) in items">
<div v-if="item.show" style="border:2px solid green;height:700px">
You can see me: {{ item.name }} | ID: {{ index }}
</div>
<div class="item-blank" :data-id="index" v-else style="border:2px solid red;height:700px;position:relative;">
{{ item.name }} invisible {{ index }}
</div>
</div>
</div>
</div>