Get index within v-for using computed or method - methods

I was wondering if it is possible in vue to get the index of an object array directly within the v-for in and pass this value to a computed property or a method, similar to this here, or even a computed property
<div v-for="(object, index) in objects(index)"></div>
methods: {
objects(index){
const categoryId = Object.keys(this.data);
return this.data[categoryId[index]].extras;
}
}
I need to have the index as it is more convenient for me to return the correct value based on defined key, is there some way to achieve this?

Transform your data using a computed value and loop over that. I am not sure what your this.data looks like, but something like this should work (tweak it to suit your needs):
<div v-for="object in computed_objects"></div>
computed: {
computed_objects(){
return Object.keys(this.data).map(categoryId => this.data[categoryId].extras)
}
}

You can bind a method call on each element created by the v-for directive, so for example any time a user clicks on <li> element, it will gets the index of that clicked item:
new Vue({
el: '#app',
data: {
clickedIndex: null,
weekDays: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']
},
methods: {
handleClick(i) {
this.clickedIndex = i;
}
}
})
Vue.config.productionTip = false;
Vue.config.devtools = false;
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="(day,i) in weekDays" v-on:click="handleClick(i)">{{day}}</li>
</ul>
<p>Index clicked {{ clickedIndex }}</p>
</div>

Related

How to properly reverse sort a BackboneJs collection?

I have a table where several items are displayed.
<table>
...
<th scope="col">Priority <a id="sort"><i class="fas fa-chevron-down"></i></a> <a id="sort2">
<i class="fas fa-chevron-up"></i></a></th>
...
</table>
The priorities holds an integer value, 1, 2 and 3 respectively.
I'm trying to sort the items on button click. I managed to sort once by using collection.sort() in my view and it sorts perfectly. How can i reverse sort by clicking sort2 button? Any help regarding this is appreciated. Thank you.
app.views.ShareView = Backbone.View.extend({
el: ".page",
initialize: function () {},
events:{
"click #sort" : "sort",
"click #sort2" : "sort2"
},
sort: function (e){
e.preventDefault();
this.collection.comparator = 'priority_id';
this.collection.sort();
this.render();
},
sort2: function (e){
//reverse sort
}
render: function () {
template = _.template($('#share-template').html());
this.$el.html(template(app.userShare.attributes));
}
});
You need to use the sort comparator function instead of the comparator property. This allows you to specify a comparator function instead of just the property. For example;
this.collection.comparator = function(firstModel, secondModel) {
if(firstModel.priority_id > secondModel.priority_id) { // change to < in order reverse the order
return -1;
}
else if(firstModel.priority_id === secondModel.priority_id) {
return 0;
}
else {
return 1;
}
}
this.collection.sort();
In BackboneJS, it is often possible to use a function in place of a value if you require additional functionality.

Use methods and computed properties in child component

In my List component I have a method which count the length of the array within certain categories.
methods: {
getLengthofaCategory(cat) {
const LowerCaseSearch = this.search.toLowerCase();
let categoryCount = this.products.filter(
product =>
(product.name.toLowerCase().includes(LowerCaseSearch) ||
product.category.toLowerCase().includes(LowerCaseSearch)) &&
(!this.checked.length || this.checked.includes(product.category)) &&
product.category === cat
);
return categoryCount.length;
}
}
See here my setup in this sandbox.
But I want the values next to the checkboxes (which are coming from my CheckBox component).
How do I get the logic from the method getLengthofaCategory into my CheckBox component?
So I am able to use {{ getLengthofaCategory('tennis') }} in the v-for loop, inside the CheckBox component. And then maybe I can also use category.value instead of hardcoding e.g 'tennis' as the paramater?
In your list.vue, you can use the already created computed function filteredData instead of doing the filter again. This saves some performance because in Vue, computed properties are "cached" once run.
So you can create a new computed function that creates an object with keys per category and value can either be just the amount or an array of products in this category.
I would then pass this computed value to the CheckBox component via a prop, then inside the CheckBox component, you can display the .length or value regarding how many items each category has:
List.vue:
computed: {
//...
amountPerCategory() {
return this.filteredData.reduce((categories, product) => {
if (!(product.category in categories)) {
categories[product.category] = [];
}
categories[product.category].push(product);
return categories;
}, {});
}
}
CheckBox.vue:
<span class="ml-2 text-gray-700 capitalize">{{ category.value }}</span> -
<span
v-if="count[category.value]"
class="ml-2 text-gray-700 capitalize"
>{{ count[category.value].length }}</span>
count: {
type: Object,
default: () => ({})
}
https://codesandbox.io/s/admiring-ellis-4hojl?file=/src/components/CheckBox.vue

Output user first name

I want to get the name of the user to put it on an h1.
What dies this line stand for?
#select="option => selected = option">
I'm using Buefy for the vue components.
<template>
<section>
<div class="field">
<b-switch v-model="keepFirst">
Keep-first <small>(will always have first option pre-selected)</small>
</b-switch>
</div>
<p class="content"><b>Selected:</b> {{ selected }}</p>
<b-field label="Find a name">
<b-autocomplete
v-model="name"
placeholder="e.g. Anne"
:keep-first="keepFirst"
:data="filteredDataObj"
field="user.first_name"
#select="option => selected = option">
</b-autocomplete>
</b-field>
</section>
</template>
<script>
import data from '#/assets/data_test.json'
// Data example
// [{"id":1,"user":{"first_name":"Jesse","last_name":"Simmons"},"date":"2016-10-15 13:43:27","gender":"Male"},
// {"id":2,"user":{"first_name":"John","last_name":"Jacobs"},"date":"2016-12-15 06:00:53","gender":"Male"},
// {"id":3,"user":{"first_name":"Tina","last_name":"Gilbert"},"date":"2016-04-26 06:26:28","gender":"Female"},
// {"id":4,"user":{"first_name":"Clarence","last_name":"Flores"},"date":"2016-04-10 10:28:46","gender":"Male"},
// {"id":5,"user":{"first_name":"Anne","last_name":"Lee"},"date":"2016-12-06 14:38:38","gender":"Female"}]
export default {
data() {
return {
data,
keepFirst: false,
name: '',
selected: null
}
},
computed: {
filteredDataObj() {
return this.data.filter((option) => {
return option.user.first_name
.toString()
.toLowerCase()
.indexOf(this.name.toLowerCase()) >= 0
})
}
}
}
</script>
# is shorthand for v-on:, so it's handling a select event with a function that receives option as a parameter and assigns it to selected.
Since v-model is bound to name, you should be able to do <h1>{{name}}</h1> to have the same value show up in an H1.
The data section has the main variables for your object. name is there. There is also a computed (named filteredDataObj) that should return an array (length of zero or one) with the matching test data. If you want other fields (like id) you would need to look there. Something like
{{filteredDataObj.length ? filteredDataObj.id : ''}}
would give the id if name matched anything in the data set.

Conditional v-if is working only for the first time?

I have this in my view:
<div class="already_voted" v-if="already_voted" >
<p>You already voted or your are not allowed to vote</p>
</div>
This is my method :
upvote: function(com_id) {
var comment_id = {
comment_id :com_id
}
this.$http.post('/blog/article/comment/upvote', comment_id).then(function(response){
upvote_total= response.data.upvote_value;
this.already_voted = response.data.already_voted;
this.$dispatch('child-msg', this.already_voted);
$('.upvote_class_' + com_id ).text(upvote_total);
$('.isDisabledUpvote_' + com_id).addClass('disabled');
$('.isDisabledDownvote_' + com_id).removeClass('disabled');
},function(response){
});
},
Im getting value on click and if its true it need to show this div.
Problem is that this div is showed only for first time when already_voted is true and thats it. Next time when its true nothing happend. Any suggestion?
It looks like you are mixing jQuery and Vue, which should be avoided unless you have a specific reason to do so. Instead you should bind attributes to data. As a basic version of what you are doing you could bind both the disabled attribute and the message to a voted flag:
Markup
<div id="app">
<div v-if="voted">
You have already voted!
</div>
<button v-bind:disabled="voted" #click="vote()">
Vote
</button>
<button v-bind:disabled="!voted" #click="removeVote()">
Un-Vote
</button>
</div>
View Model
new Vue({
el: '#app',
methods: {
vote(){
this.voted = true;
},
removeVote(){
this.voted = false;
}
},
data: {
voted: false
}
});
Here I'm simply binding the disabled attribute using v-bind to the voted flag to disabled the buttons and am using v-if to show a message if the voted flag is true.
Here's the JSFiddle: https://jsfiddle.net/05sbjqLL/
Also be aware that this inside an anonymous function refers to the anonymous function itself, so either assign this to something (var self = this) outside the function or use an arrow function if using ES6.
EDIT
I've updated the JSFiddle to show you how you might handle your situation based on you comments:
https://jsfiddle.net/umkvps5g/
Firstly, I've created a directive that will allow you to initiate your variable from your cookie:
Vue.directive('init', {
bind: function(el, binding, vnode) {
vnode.context[binding.arg] = binding.value;
}
})
This can now be used as:
<div v-init:voted="{{ $request->cookie('voted') }}"></div>
I simply disabled the button to show you how to bind attributes to data, there's loads more that can be done, for example showing the message after a user clicks the button, I've just added a click counter and bound thev-if to that instead, so the message doesn't show until a user clicks the button:
<div v-if="vote_attempts">
You have already voted!
</div>
Then in vote() method:
vote() {
this.voted = true;
this.vote_attempts++;
},
Then data:
data: {
voted: false,
vote_attempts: 0
}

Backbone.Marionette collection iterator as part of itemview classname

I'm still learning Backbone and Marionette so forgive me if this is trivial.
I have a Backbone.Marionette.CompositeView that I iterate over to render a collection of Backbone.Marionette.ItemView.
My ItemView renders with a className: 'column'.
Is there a way to add the iterator as part of the className for each ItemView?
What I'm trying to accomplish is that the elements render as following:
<div class="column column-1"></div>
<div class="column column-2"></div>
<div class="column column-3"></div>
...
I couldn't find a suitable solution in the docs nor other questions here.
Thanks!
Essentially what you need to know is the index of the itemView model in your collection. Something like this will work:
// Create an itemView
var itemView = Backbone.Marionette.ItemView.extend({
template: "#item-template",
onRender: function () {
this.$el.addClass('class-nr-' + this.options.itemIndex);
}
});
// Create a collectionView
var colView = Backbone.Marionette.CollectionView.extend({
collection: colInstance,
itemView: itemView,
itemViewOptions: function (model, index) {
return {
itemIndex: index
};
}
});
See this fiddle: http://jsfiddle.net/Cardiff/VTkB2/2/
Documentation: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#collectionviews-itemviewoptions

Resources