vuejs start a timer for every record - vue.js

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>

Related

Check if the value change in loop Vuejs

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>

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 change prop dynamically in vue-status-indicator?

I am new to VueJS and after reading this doc section and this question, I can't figure how to change dynamically the prop active|positive|intermediary|negative and pulse of the following component (it could be another): vue-status-indicator
eg: with user.status = positive and the following wrong code :
<span v-for="user in users" :key="user.id">
<status-indicator {{ user.status }}></status-indicator>
</span>
What is the correct syntax to set theses type of props ?
You could do something like this.. I had to write a wrapper for it to make it functional..
[CodePen Mirror]
Edit To be clear - you cannot interpolate inside an attribute.. This has to do with boolean attributes in Vue..
This:
<status-indicator active pulse />
...is the same exact thing as doing this:
<status-indicator :active="true" :pulse="true" />
The "wrapper" component I wrote allows you to supply a string to set the status (like you are wanting to do):
<v-indicator status="active" pulse></v-indicator>
<!-- OR -->
<v-indicator status="positive" pulse></v-indicator>
<!-- OR -->
<v-indicator status="intermediary" pulse></v-indicator>
<!-- OR -->
<v-indicator status="negative" pulse></v-indicator>
Here is the full "wrapper" component, in .vue format: (added a validator for the 'status' prop)
<template>
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</template>
<script>
export default {
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
</script>
Snippet:
const vIndicator = {
template: "#v-indicator",
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
new Vue({
el: '#app',
components: {
vIndicator
},
data: {
currentStatus: '',
isPulse: '',
},
computed: {
currentJson() {
let cj = {
currentStatus: this.currentStatus,
isPulse: this.isPulse,
};
return JSON.stringify(cj, null, 2);
}
},
mounted() {
let statuses = ["active", "positive", "intermediary","negative"];
let c = 0;
let t = 0;
this.currentStatus = statuses[c];
this.isPulse = true;
setInterval(() => {
t = c + 1 > 3 ? t + 1 : t;
c = c + 1 > 3 ? 0 : c + 1;
this.currentStatus = statuses[c];
this.isPulse = (t % 2 == 0) ? true : false;
}, 2000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-status-indicator#latest/dist/vue-status-indicator.min.js"></script>
<link href="https://unpkg.com/vue-status-indicator#latest/styles.css" rel="stylesheet"/>
<div id="app">
<p>Will alternate status as well as pulsing (pulse changes after each full loop)</p>
<!--
[status]active|positive|intermediary|negative
[pulse]true|false
-->
<v-indicator :status="currentStatus" :pulse="isPulse"></v-indicator>
<pre>{{ currentJson }}</pre>
</div>
<!-- WRAPPER COMPONENT -->
<script type="text/x-template" id="v-indicator">
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</script>

input field prefilled with vuejs and a reactive character count

As a vuejs component, I want to be able to display a character counter next to my input field.
The field is initially set up using a prop (this.initialValue).
When the method this.updateCounter is called the input textfield is blocked : typing into the field won't update its value. If I don't set the maxlength prop, the field is working fine : I can update the textfield.
Usage in a template :
<textfield maxlength="50" name="title" initialValue="Test"></textfield>
Here is the component code :
<template>
<div class="input">
<div class="input__field">
<span class="input__limit f--small">{{ counter }}</span>
<input type="text" :name="name" :maxlength="computedMaxlength" v-model="currentValue" />
</div>
</div>
</template>
<script>
export default {
name: 'Textfield',
props: {
name: {
default: ''
},
maxlength: {
default: 0
},
initialValue: {
default: ''
}
},
computed: {
hasMaxlength: function () {
return this.maxlength > 0;
},
computedMaxlength: function () {
if(this.hasMaxlength) return this.maxlength;
else return false;
},
currentValue: {
get: function() {
return this.initialValue;
},
set: function(newValue) {
this.updateCounter(newValue);
this.$emit("change", newValue);
}
}
},
data: function () {
return {
counter: 0
}
},
methods: {
updateCounter: function (newValue) {
if(this.maxlength > 0) this.counter = this.maxlength - newValue.length;
}
},
mounted: function() {
this.updateCounter(this.initialValue);
}
}
</script>
Edit
I have fixed my issue by not using v-model but instead using a value and an input event.
data: function () {
return {
value: this.initialValue,
counter: 0
}
},
methods: {
updateCounter: function (newValue) {
if(this.maxlength > 0) this.counter = this.maxlength - newValue.toString().length;
},
onInput: function(event) {
const newValue = event.target.value;
this.value = newValue;
this.updateCounter(newValue);
this.$emit("change", newValue);
}
},