Check if the value change in loop Vuejs - vue.js

I'm making chat app by Vuejs and want to handle if messages in loop belong to new user for styling color/background-color of user's message.
<template v-for="msg in allMsgs">
<li :key=msg.id> //I want to add some class to handle if next message belong to new user.
<span class="chatname">{{msg.user.name}}</span>
{{msg.content}}
</li>
</template>
https://prnt.sc/114ynuq
Thank you so much

You can use a computed property to determine the position of each message according to the sequence, and then, use class-binding as follows:
new Vue({
el:"#app",
data: () => ({
allMsgs: [
{ id:1, user: { name:'B' }, content:'contentB' },
{ id:2, user: { name:'A' }, content:'contentA' },
{ id:3, user: { name:'A' }, content:'contentA' },
{ id:4, user: { name:'B' }, content:'contentB' },
{ id:5, user: { name:'B' }, content:'contentB' },
{ id:6, user: { name:'A' }, content:'contentA' },
{ id:7, user: { name:'A' }, content:'contentA' }
]
}),
computed: {
messages: function() {
let pos = 1, prev = null;
return this.allMsgs.map((msg, index) => {
// if msg is not the first, and it belongs to a new user, opposite pos
if(index !== 0 && msg.user.name !== prev.user.name) pos *= -1;
msg.position = pos;
prev = msg;
return msg;
});
}
}
});
.chatname { font-weight:bold; }
.left { text-align:left; }
.right { text-align:right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="(msg, index) in messages">
<li
:key=msg.id
:class="{ 'right': msg.position === 1, 'left': msg.position === -1 }"
>
<span class="chatname">
{{msg.user.name}}
</span>
{{msg.content}}
</li>
</template>
</div>

Related

Handle interaction between vue fields

I have prepared a functional code example in JSFiddle of VUE field interaction.
https://jsfiddle.net/JLLMNCHR/2a9ex5zu/6/
I have a custom autocomplete component that works properly, a normal input field, and a 'Load' button which objetive is to load the value entered in the normal input in the autocomplete field.
This 'load' button is not working.
HTML:
<div id="app">
<p>Selected: {{test1}}</p>
<br>
<div>
<label>Test1:</label>
<keep-alive>
<autocomplete v-model="test1" v-bind:key="1" :items="theItems">
</autocomplete>
</keep-alive>
</div>
<br>
<label>Display this in 'test1':</label>
<input type="text" v-model=anotherField>
<button type="button" v-on:click="loadField()">Load</button>
<br>
<br>
<button type="button" v-on:click="displayVals()">Display vals</button>
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #input="onChange" v-model="search"
#keyup.down="onArrowDown" #keyup.up="onArrowUp" #keyup.enter="onEnter" />
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li v-else v-for="(result, i) in results" :key="i" #click="setResult(result)"
class="autocomplete-result" :class="{'is-active':i === arrowCounter}">
{{ result }}
</li>
</ul>
</div>
</script>
VUE.JS:
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => []
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
// Let's warn the parent that a change was made
this.$emit("input", this.search);
// Is the data given by an outside ajax request?
if (this.isAsync) {
this.isLoading = true;
} else {
// Let's search our flat array
this.filterResults();
this.isOpen = true;
}
},
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
setResult(result) {
this.search = result;
this.$emit("input", this.search);
this.isOpen = false;
},
onArrowDown(evt) {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
handleClickOutside(evt) {
if (!this.$el.contains(evt.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
}
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
},
methods: {
displayVals() {
alert("test1=" + this.test1);
},
loadField() {
this.test1=this.anotherField;
}
},
data: {
test1: '',
anotherField: '',
theItems: [ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']
}
});
Any help will be appreciated.
See this new fiddle where it is fixed.
When you use v-model on a custom component you need to add a property named value and watch it for changes, so it can update the local property this.search.

dynamic interpolation computed method name in v-for element in vuejs

In my project vuejs a create a list element with ul li and v-for directive vuejs like this:
<ul>
<li :class="{active: 'isActive+index'}" v-for="(car, index) in cars"></li>
</ul>
Those elements are dynamics. Sometimes are 2 sometimes are 3 or 4 elements
But I need to have a specific logic active class css for each like this:
'isActive+index'
Where this represent a dynamic computed name (already exist). But obviously this code not run and generate basic string word not a link to computed method. I want to execute those computed methods:
computed:
{
isActive1: function ()
{
return myLogic
},
isActive2: function ()
{
return myLogic
},
isActive3: function ()
{
return myLogic
},
isActive4: function ()
{
return myLogic
},
}
How can I link element with dynamic method name for execute computed with vuejs ?
new Vue({
el: '#app',
template: `
<div>
<ul>
<li v-for="(item, index) in cars" :key="index" :class="{ active: statusActive[index] }">
<strong>Car:</strong> {{item.name}} ,
</li>
</ul>
<button #click="changeCars">Change cars</button>
</div>
`,
data() {
return {
cars1: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
],
cars2: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
{
name: "car4",
},
],
cars3: [{
name: "car1",
},
{
name: "car2",
},
],
carsIndex: 1,
};
},
computed: {
cars() {
return this["cars" + this.carsIndex];
},
statusActive() {
return {
0: this.statusActive0,
1: this.statusActive1,
2: this.statusActive2,
3: this.statusActive3,
};
},
statusActive0() {
return false;
},
statusActive1() {
return true;
},
statusActive2() {
return false;
},
statusActive3() {
return true;
},
},
methods: {
changeCars() {
if (this.carsIndex < 3) {
this.carsIndex++;
} else {
this.carsIndex = 1;
}
},
},
})
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>
or
new Vue({
el: '#app',
template: `
<div>
<ul>
<li v-for="(item, index) in cars" :key="index" :class="{ active: isActive(index) }">
<strong>Car:</strong> {{item.name}} ,
</li>
</ul>
<button #click="changeCars">Change cars</button>
</div>
`,
data() {
return {
cars1: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
],
cars2: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
{
name: "car4",
},
],
cars3: [{
name: "car1",
},
{
name: "car2",
},
],
carsIndex: 1,
};
},
computed: {
cars() {
return this["cars" + this.carsIndex];
},
statusActive0() {
return false;
},
statusActive1() {
return true;
},
statusActive2() {
return false;
},
statusActive3() {
return true;
},
},
methods: {
changeCars() {
if (this.carsIndex < 3) {
this.carsIndex++;
} else {
this.carsIndex = 1;
}
},
isActive(index) {
return this["statusActive" + index];
},
},
})
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>

How to use an object not an array for an autocomplete form

I am new to Vue JS and having a mind blank when I've been creating my first Auto complete comp with VueCLI.
Here is the working code with an array:
https://pastebin.com/a8AL8MkD
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.toLowerCase().startsWith(this.state.toLowerCase())
})
},
I am now trying to get it to work with JSON so I can use axios to get the data.
In the filterStates method I understand I need to get the name of the item and do the lowercase on that, but it keeps erroring out when I try this:
https://pastebin.com/HPYyr9QH
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
Vue is erroring this:
[Vue warn]: Error in v-on handler: "TypeError: state.toLowerCase is not a function"
Do I need to pass in a key or something to identify each record?
Let's take your second pastebin :
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data() {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
setState(state) {
this.state = state
this.modal = false
}
}
}
</script>
You are calling : this.state.name.toLowerCase().
But this.state returns '' initially. So this.state.name is undefined.
You should initialize this.state with an object :
data() {
return {
state: {
name: ''
}
...
EDIT 17/03/2020
Here is another working solution :
What I did :
state is a string again. so I check this.state.toLowerCase()
In the setState function, I just pass the name : this.state = state.name
And to fix another error I changed this line : :key="filteredState.id" because a key should not be an object
<template>
<div>
<div class="AboutUs">
<PageBanner>
<template slot="title">Search</template>
</PageBanner>
<div class="container-fluid tarms-conditions">
<div class="row">
<div class="container">
<input
id
v-model="state"
type="text"
name
autocomplete="off"
class="form-control z-10"
placeholder="Search for a state..."
#input="filterStates"
#focus="modal = true"
>
<div
v-if="filteredStates && modal"
class="results z-10"
>
<ul class="list">
<li
v-for="filteredState in filteredStates"
:key="filteredState.id"
#click="setState(filteredState)"
>
{{ filteredState }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data () {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates () {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.toLowerCase())
})
},
setState (state) {
this.state = state.name
this.modal = false
}
}
}
</script>

Vue does not delete proper component from list

I got :key="index" but it still does not do it as I need.
If you add for example 6 items and select something from select on the third item and hit remove button on that item, it will delete another item, but not this one.
Here is a link to fiddle
https://jsfiddle.net/benderlio/ecyskz83/1/
Vue.config.devtools = true
Vue.component('select-component', {
data: function () {
return {
currentSelectedData: undefined,
}
},
template: `<div>
<select v-model="currentSelectedData">
<option v-for="(item,key) in dataArray" :key="key" :value="key">{{item}}</option>
</select>
<select v-if="currentSelectedData >= 0">
<option v-for="(item,key) in tempData" :key="key" :value="key">{{item}}</option>
</select>
</div>`,
watch: {
currentSelectedData(newValue, oldValue) {
console.log('', newValue, oldValue);
this.$emit("got-selected-value", newValue)
}
},
props: {
'data-array': {
type: Array,
required: true,
},
'temp-data': {
type: Array,
required: true,
},
},
})
new Vue({
el: '#app',
data: {
components: [],
dataFirstSelect: ["sheet3", "sheet4", "sheet5"
],
dataSecondSelect: [1, 2, 3]
},
methods: {
emitedValue(payload) {
console.log('Got the value from child: ', payload);
},
addComp() {
const comp = {
id: new Date().getTime()
};
this.components.push(comp)
},
remove(id) {
// this.components = this.components.filter(i => i.id !== id)
console.log('Remove', id, this.components);
//this.$delete(this.components, id)
this.components.splice(id, 1)
},
log() {
console.log('---------', this.components.map(i => i.id));
}
},
})
It works fine if you use item.id as your key instead of index and put your key on a real DOM element, not a template. Using index as a key is an anti-pattern.
Vue.config.devtools = true
Vue.component('select-component', {
data: function() {
return {
currentSelectedData: undefined,
selectedTemp: undefined
}
},
template: `<div>
<select v-model="currentSelectedData">
<option v-for="(item, key) in dataArray" :value="key">{{item}}</option>
</select>
<select v-if="currentSelectedData >= 0" v-model="selectedTemp">
<option v-for="(item, key) in tempData" :value="key">{{item}}</option>
</select>
<div>{{currentSelectedData}}</div>
<div>{{selectedTemp}}</div>
</div>`,
watch: {
currentSelectedData(newValue, oldValue) {
console.log('', newValue, oldValue);
this.$emit("got-selected-value", newValue)
}
},
props: {
'data-array': {
type: Array,
required: true,
},
'temp-data': {
type: Array,
required: true,
},
},
})
new Vue({
el: '#app',
data: {
components: [],
dataFirstSelect: ["sheet3", "sheet4", "sheet5"],
dataSecondSelect: [1, 2, 3]
},
methods: {
emitedValue(payload) {
console.log('Got the value from child: ', payload);
},
addComp() {
const comp = {
id: new Date().getTime()
};
this.components.push(comp)
},
remove(id) {
// this.components = this.components.filter(i => i.id !== id)
console.log('Remove', id, this.components);
//this.$delete(this.components, id)
this.components.splice(id, 1)
},
log() {
console.log('---------', this.components.map(i => i.id));
}
},
})
.select-component {
display: block;
margin: 10px;
}
.item {
padding: 10px;
margin: 10px;
border: 1px solid gray;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<button #click="addComp">Add</button>
<template v-for="(item,index) in components">
<div class="item" :key="item.id">
id: {{item.id}} {{index}}
<button #click="remove(index)">Remove</button>
<select-component class="select-component" v-on:got-selected-value="emitedValue" :data-array='dataFirstSelect'
:temp-data='dataSecondSelect'>
</select-component>
</div>
</template>
</div>

vuejs start a timer for every record

I have this component rendered, I need to tell the users how much time remaining for each record displayed
I couldn't get anything worked out, I don't know any legible ways to work it out, so just trying calling out methods. I tried child components too but no avail.
<template>
<div>
<ul v-for="question in questions">
{{init_timer(secs)}}
{{start_timer()}}
<li>
{{question.name}}
<span class="question_clock">Validity : -- counter : {{time_remaining}}
</span> </small>
</li>
</ul>
</div>
</template>
<script>
export default {
data: function(){
return {
questions: [],
time_remaining : 1
};
},
methods: {
init_timer: function(secs) {
this.time_remaining = secs
},
start_timer: function() {
console.log('startiem')
setTimeout(function () { this.time_remaining -= 1 }.bind(this), 1000)
},
}
,
created: function(){
$.getJSON('/questions/json', function(response){
this.questions = response
}.bind(this ));
},
}
</script>
any help is much appreciated
I wrote a quick timer component that might accomplish what you want.
Vue.component("timer", {
props: {
interval: {
type: Number,
default: 1
},
initial: {
type: Number
}
},
template: `
<span>{{current}}</span>
`,
data() {
return {
current: this.initial,
timerInterval: null
}
},
methods: {
onInterval() {
this.current = this.current -= this.interval
if (this.current <= 0) {
clearInterval(this.timerInterval)
this.current = 0
}
}
},
mounted() {
setInterval(this.onInterval, this.interval * 1000)
}
})
Basically, you set the initial property which is the time allowed in seconds and the timer will update the DOM every interval seconds.
Here is an example.
console.clear()
const questions = [{
name: "What is your name?",
time: 30
},
{
name: "What is your quest?",
time: 50
},
{
name: "What is your favorite color?",
time: 20
},
{
name: "What is the airspeed velocity of an unladen swallow?",
time: 10
},
]
Vue.component("timer", {
props: {
interval: {
type: Number,
default: 1
},
initial: {
type: Number
}
},
template: `
<span>{{current}}</span>
`,
data() {
return {
current: this.initial,
timerInterval: null
}
},
methods: {
onInterval() {
this.current = this.current -= this.interval
if (this.current <= 0) {
clearInterval(this.timerInterval)
this.current = 0
}
}
},
mounted() {
setInterval(this.onInterval, this.interval * 1000)
}
})
new Vue({
el: "#app",
data: function() {
return {
questions: [],
time_remaining: 1
};
},
created: function() {
setTimeout(() => this.questions = questions, 100)
// $.getJSON('/questions/json', function(response){
// this.questions = response
// }.bind(this ));
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="question in questions">
{{question.name}}
<span class="question_clock">
<timer :initial="question.time"></timer>
</span>
</li>
</ul>
</div>