Comparing with previous iteration in v-for - vue.js

Here's my code:
<div v-for="(message) in messages">
<div class="message-divider sticky-top pb-2" data-label="Test"> </div>
...
What I need to achieve is, if current iteration's message.createdAt.seconds differs by day from the previous one to show message-divider element.
So the time format is in seconds like this: 1621515321
How can I achieve this?

You can do variable assignment as part of the template, so you could assign the previous value and compare, but this is not a very good idea. Instead you can use a computed to prepare your array to only have the objects you want, with the data you need. So in this case, you could use a computed to create a new array with objects that have additional flags like className or showDivider etc.
example:
computed:{
messagesClean(){
let lastCreatedAt;
return this.messages.map(message => {
const ret = {
...message,
showDivider: lastCreatedAt + 3600*24 < message.createdAt.seconds // your custom logic here
}
lastCreatedAt = message.createdAt.seconds
return ret
}
}
}
the logic to determine when the divider gets shown is up to you there, I suspect it may be a bit more complicated than differing by a day.

You need something like this:
<div v-if="compEqRef">
OK
</div>
<div v-else >!!OK!!</div>
Here compEqRef could be in computed:
computed: {
compEqRef() {
//do something
}
},

Related

Can I pass a computed property and an object inside a v-bind:class in Vuejs?

I want to understand if I can do something like this, because I am trying but only getting erros... Forgive my bad english.
HTML file would be something like
<p :class="{mycss: isActive}, myComputedProperty" > My text </p>
and the component file would have something like
export default {
data () {
return {
isActive: true
}
},
computed: {
myComputedProperty () {
// do something
}
}
}
class value is an expression. If it doesn't make sense in raw JavaScript, it doesn't make sense there. Here comma operator is used, so the expression evaluates to myComputedProperty, and {mycss: isActive} part is discarded.
The format for combined class value is documented:
:class="[{mycss: isActive}, myComputedProperty]"
Since computed values are involved, defining the whole class object as a computed will result in cleaner template code.
I think the error is in your HTML - the comma is probably the cause. There are lots of ways to format strings, but this is one option:
<p :class="isActive ? 'mycss ' + myComputedProperty : myComputedProperty" > My text </p>

Vue + Vuex: input value not set by store if no store change

<template>
<input
#input="formatValue"
type="text"
:value="formattedValue"
/>
</template>
<script type="text/javascript">
import {formatPhoneNumber} from '~/utils/string';
export default {
computed: {
formattedValue: function(){
return formatPhoneNumber(this.value)
},
},
methods: {
formatValue(e) {
this.$emit('input', formatPhoneNumber(e.target.value))
}
},
props: ['value']
}
</script>
As long as the formatPhoneNumber(value) produces a different value, every thing works fine, but once the max length is reached (Since formatPhoneNumber('xx xx xx xx xx whatever') == 'xx xx xx xx xx'), the emitted value is the same as the current store one.
It is totally fine, except that as a consequence, state is not mutated and component is not re-rendered, hence formattedValue() is not called.
So I end up with xx xx xx xx xx in the store, but the input displays xx xx xx xx xx whatever as local input value varies from the store one.
How can I avoid this unexpected behavior? Moving formatPhoneNumber() to the store would not solve my issue since it would still prevent mutation, and only using formatPhoneNumber() in formattedValue() would make me end up with an un-formatted value in the store which is not what I want either.
How come Vue's input with dynamic value set still manages a local state?
To achieve what you want (I think), you could change your formatValue method to
formatValue(e) {
this.$emit('input', e.target.value = formatPhoneNumber(e.target.value));
}
So that it sets the input to the formatted phone number value. One way or another you're going to be overriding what the input produces so you might as well do it on the input event.
I would use a v-model instead of a v-value since that would give me full control over what I want to display in the input field.
In this way, you can format the input value, and then set it back in the model. It would look something like this:
<template>
<input #input="formatValue" type="text" v-model="inputModel">
</template>
<script type="text/javascript">
export default {
data() {
return {
inputModel: this.value
};
},
methods: {
formatValue() {
this.inputModel = formatPhoneNumber(this.inputModel);
this.$emit("input", this.inputModel);
}
},
props: ["value"]
};
</script>
Here's a working example I created to test this.
I think the easiest approach is a simple one-line modification to the parent's #input event, that clears the prop value before it updates it.
You still only need to emit the one value, but before working with the emitted value, clear the prop.
I've provided a snippet below (but note the additional differences in the snippet):
Instead of specifying the input field value, I opted to use v-model to bind it to a computed property that has a get and set method. This allowed me to use different logic when accessing vs modifying the data (quite handy in many situations).
By separating this logic, I was able to move the functionality from inside the input event to the set method, and eliminate the input event entirely.
new Vue({
el: "#app",
// props: ['valueProp'],
data: {
valueProp: "" //simulate prop data
},
computed: {
// --Value input element is binded to--
inputValue:{
get(){ //when getting the value, return the prop
return this.valueProp;
},
set(val){ //when the value is set, emit value
this.formatValue(val);
}
}
},
methods: {
// --Emit the value to the parent--
formatValue(val) {
this.parentFunction(this.formatPhoneNumber(val)); //simulate emitting the value
// this.$emit('input', formatPhoneNumber(val));
},
// --Simulate parent receiving emit event--
parentFunction(emittedValue){
console.log("emitted:" + emittedValue);
this.valueProp = null; //first clear it (updates the input field)
this.valueProp = emittedValue; //then assign it the emitted value
},
// --Simulate your format method--
// THIS LOGIC CAN BE IGNORED. It is just a quick implementation of a naive formatter.
// The "important" thing is it limits the length, to demonstrate exceeding the limit doesn't get reflected in the input field
formatPhoneNumber(val){
var phoneSpaces = [2,4,6,8]; //specify space formatting (space locations)
var maxLength = 10; //specify the max length
val = val.replace(/ /g,''); //remove existing formatting
if(val.length > maxLength) //limits the length to the max length
val = val.substring(0, maxLength);
// for the number of desired spaces, check each space location (working backwards) ... if value is longer than space location and space location is not a space ... add a space at the location.
for(var i = phoneSpaces.length-1; i >= 0; i--){
if(val.length > phoneSpaces[i] && val[phoneSpaces[i]] != " "){
val = val.substring(0, phoneSpaces[i]) + " " + val.substring(phoneSpaces[i], val.length);
}
}
return val
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="inputValue"/>
<label style="float: right;">
Prop Value: <span>{{valueProp}}</span>
</label>
<br>
<label >format (xx xx xx xx xx)</label>
</div>

Set a variable inside a v-for loop on Vue JS

I have a v-for loop with vue.js on a SPA and I wonder if it's posible to set a variable at the beginning and then just print it everytime you need it, because right now i'm calling a method everytime i need to print the variable.
This is the JSON data.
{
"likes": ["famiglia", "ridere", "caffè", "cioccolato", "tres leches", "ballare", "cinema"],
"dislikes":["tristezze", "abuso su animali", "ingiustizie", "bugie"]
}
Then I use it in a loop:
<template>
<div class="c-interests__item" v-for="(value, key) in interests" :key="key" :data-key="key" :data-is="getEmotion(key)" >
// NOTE: I need to use the variable like this in different places, and I find myself calling getEmotion(key) everythime, is this the way to go on Vue? or there is another way to set a var and just call it where we need it?
<div :class="['c-card__frontTopBox', 'c-card__frontTopBox--' + getEmotion(key)]" ...
<svgicon :icon="getEmotion(key) ...
</div>
</template>
<script>
import interests from '../assets/json/interests.json'
... More imports
let emotion = ''
export default {
name: 'CInfographicsInterests',
components: {
JSubtitle, svgicon
},
data () {
return {
interests,
emotion
}
},
methods: {
getEmotion (key) {
let emotion = (key === 0) ? 'happy' : 'sad'
return emotion
}
}
}
</script>
// Not relevanty to the question
<style lang='scss'>
.c-interests{...}
</style>
I tried adding a prop like :testy="getEmotion(key)" and then { testy } with no luck...
I tried printing { emotion } directly and it doesn't work
So, there is anyway to acomplish this or should i stick calling the method every time?
Thanks in advance for any help.
It's not a good idea to use methods inside a template for non-user-directed actions (like onClicks). It's especially bad, when it comes to performance, inside loops.
Instead of using a method, you can use a computed variable to store the state like so
computed: {
emotions() {
return this.interests.map((index, key) => key === 0 ? 'happy' : 'sad');
}
}
This will create an array that will return the data you need, so you can use
<div class="c-interests__item"
v-for="(value, key) in interests"
:key="key" />`
which will reduce the amount of times the item gets re-drawn.

VuesJS, generate randomkey in v-for loop

Good evening,
My problem is this: I have a loop that displays simple divs.
I have a method that specifies the dimensiosn of my div (mandatory in my case). However, when I call the method a second time by changing the sizes of the divs, it does not work because there is no re-render.
To overcome this, I generate a guid on my: key of v-for with a variable such as:
<div v-for="task in tasks" :key="task.id + guid()">...blabla...</div>
Is it possible to generate this code directly during the loop to avoid concatenation?
<div v-for="(task, maVar=guid()) in tasks" :key="maVar">...blabla...</div>
PS : code for guid() method :
guid() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16))
}
Thanks
You could create a computed property that returns an array of task with a guid added, or if you want to leave tasks untouched, return an object containing each task plus a guid,
computed: {
tasksWithGuid: function() {
return this.tasks.map(task => { return {task, key: task.id + guid() } })
}
}
<div v-for="taskWithGuid in tasksWithGuid" :key="taskWithGuid.key">
{{taskWithGuid.task.someProperty}}
</div>
There is a simpler, more concise technique shown below. It avoids polluting the iterated object with a redundant property. It can be used when there is no unique property in the objects you iterate over.
First in your viewmodel add the method to generate a random number (e.g. with Lodash random)
var random = require('lodash.random');
methods: {
random() {
return random(1000);
}
}
Then in your template reveal the index in v-for and randomize it in v-bind:key with your random() method from the viewmodel by concatenation.
<div v-for="(task, index) in tasks" v-bind:key="index + random()">
// Some markup
</div>
This is as clean as easy.
However note this approach would force redrawing each item in the list instead of replacing only items that differ. This will reset previously drawn state (if any) for unchanged items.
I do like this
function randomKey() {
return (new Date()).getTime() + Math.floor(Math.random() * 10000).toString()
}

Setting a value in v-if block

How do I use v-if to do two things
Show The message and
2) set message.hasSublocationOutage to true.
So if there is an outage show the message and set the flag to true message.hasSublocationOutage Or pass true to a method
<div v-if="!subLocation.outageTag.length - 1">
There is a problem
</div>
There seems to be some inherent flaws in your design, but you can invoke a method that calculates whether or not to display and sets the message at the same time.
HTML
<div v-if="canShowAndCalculate()">
There is a problem
</div>
JS
export default {
methods: {
canShowAndCalculate() {
if (subLocation.outageTag.length - 1) return false;
// else
message.hasSublocationOutage = true
return true
}
}
}
As Andrey mentioned, this is highly unadvisable. Having side effects in your conditional logic hides core logic. Rather, you should update a boolean condition when your data changes, not the other way around.
As a side note, you could use a computed property like V Sambor suggested for better performance, but that hides the "wrong" implementation even further since computed properties should always be cached and flowing out, whereas you could expect a method to do both, even though in this case it is inadvisable.
You can do a computed method for this is pretty much the same as #David L answer only that this will cache your display result until some related variables changes their values.
computed: {
display() {
if (this.subLocation.outageTag.length - 1) {
return false;
}
this.message.hasSublocationOutage = true;
return true;
}
}
Then in Html you can do:
<div v-if="display()">
There is a problem
</div>