MomentJS + VueJS: Getting Relative Time From Now To A Certain Point In The Past - vue.js

I use MomentJS.
in my VueJS-Code I want to get the relative time from now to that point in the past. In my template I incorporate the result of this short piece of JavaScript:
<template>
<div>{{ moment(message.createdAt, 'YYYYMMDD').fromNow() }}</div>
</template>
the object receives the date as follows:
message: { createdAt: Date.now() }
the result is always: a few seconds ago ...
how can I get the correct result (not always "a few seconds ago"):
EDIT:
this is my full template:
<template v-for="message in messages">
<div class="message">
<div class="text">{{ message.text }}</div>
<div class="date">{{ moment(message.createdAt).format('D.M.YYYY') }}</div>
<div class="date">{{ moment(message.createdAt).fromNow() }}</div>
</div>
</template>

Well, you can't use moment directly in your template, as it's not white-boxed (not accessible in the template).
Template expressions are sandboxed and only have access to a whitelist of globals such as Math and Date. You should not attempt to access user defined globals in template expressions.
Source: https://v2.vuejs.org/v2/guide/syntax.html#Using-JavaScript-Expressions
I would advise you to use some filter instead (you can also do it with methods in a very similar way).
Here is a working example.
new Vue({
el: "#app",
data() {
return {
messages: [
{
text: 'Message1',
createdAt: new Date() // Now
},
{
text: 'Message2',
createdAt: new Date(2016, 3, 1) // 1 April 2017
}
],
interval: null
};
},
filters: {
format(date) {
return moment(date).format('D.M.YYYY')
},
fromNow(date) {
return moment(date).fromNow();
}
},
created() {
this.interval = setInterval(() => this.$forceUpdate(), 1000);
// Trigger an update at least each second
// You should probably raise this duration as refreshing so often
// may be not useful
},
beforeDestroy() {
clearInterval(this.interval);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script>
<div id="app">
<template v-for="message in messages">
<div class="message">
<div class="text">{{ message.text }}</div>
<div class="date">{{ message.createdAt | format }}</div>
<div class="date">{{ message.createdAt | fromNow }}</div>
</div>
</template>
</div>

Cobaltway's reply is working, but I would advise to extract the whole logic into a small component of its own, so you don't force Vue to re-render the whole component each time.
I created exactly this as an example a while ago, please see this fiddle:
https://jsfiddle.net/Linusborg/meovg84x/
Vue.component('dynamic-from-now',{
name:'DynamicFromNow',
props: {
tag: {type: String, default: 'span'},
value: {type: String, default: ()=> moment().toISOString() },
interval: {type: Number, default: 1000}
},
data() {
return { fromNow: moment(this.value).fromNow() }
},
mounted () {
this.intervalId = setInterval(this.updateFromNow, this.interval)
this.$watch('value', this.updateFromNow)
},
beforeDestroy() {
clearInterval(this.intervalId)
},
methods: {
updateFromNow() {
var newFromNow = moment(this.value).fromNow(this.dropFixes)
if (newFromNow !== this.fromNow) {
this.fromNow = newFromNow
}
}
},
render(h) {
return h(this.tag, this.fromNow)
}
})
Usage:
<dynamic-from-now :value="yourTimeStamp" :interval="2000" :tag="span" class="red" />

Related

Vue3 updating data value is updating prop too

Don't know if this is the normal behaviour, I'm kind of new to Vue, but it's driving me nuts. Hope someone here have any clue about what's happpening...
This is my export:
props: [
'asset', //--- asset.price = 50
],
data() {
return {
local_asset: this.asset
}
}
Then, I update the value of a local_asset value with v-model:
<input type="number" v-model="local_asset.price" #change="test" />
And on filling the input with i.e. 100, it results in prop asset being changed too:
methods: {
test() {
console.log(this.local_asset.price) //--- console >> 100
console.log(this.asset.price) //--- console >> 100
}
}
Am I doing it wrong? Sorry if my code is a nonsense. Please help...
You need to copy value , not reference:
Vue.component('Child', {
template: `
<div class="">
<input type="number" v-model="local_asset.price" />
<div>data: {{ local_asset }}</div>
</div>
`,
props: [
'asset',
],
data() {
return {
local_asset: {...this.asset}
}
},
})
new Vue({
el: '#demo',
data() {
return {
asset: {price: 50}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div>prop: {{ asset }}</div>
<Child :asset="asset" />
</div>
If your data in primitive (String, Number, BigInt, Boolean, undefined, and null) you can use
data() {
return {
local_asset: JSON.parse(JSON.stringify(this.asset))
}
}

vue created doesn´t fire when a new element is inserted

I´m new to vue, and struggle with a - for me - strange behaviour.
I expected that every time a new "singleMessage" is inserted in the dom, the functions in "created" will be executed. But it doesn´t.
My goal is that every time a new single message is inserted - that works - after 5 seconds the function setMessageRead is called.
<div class="singleMessage" :class="{ unread: unread }">
<div class="messageData">
<span>{{ user }}</span> {{ timestamp | formatDate }}
</div>
<div class="messageContent">{{ text }} {{ uid }}</div>
</div>
</template>
<script>
export default {
name: "SingleMessage",
props: {
user: String,
text: String,
timestamp: Number,
channel: String,
unread: Boolean,
uid: Number,
},
created() {
setTimeout(() => {
this.setMessageRead(this.uid);
}, 5000);
},
methods: {
setMessageRead(key) {
this.$emit("update:unread", false);
},
},
};
</script>
<style>
.singleMessage.unread {
background: #fdfdc4;
}
</style>

How to make a function from another brother component be called when pressed?

Good afternoon, tell me please, I wrote a calendar displaying the events that the user sets, and now I want to make a detailed display of the event when I click it. The problem is that the events are in my component and the whole display logic is in another, how can I use them together.I want to make it so that the getDetailInformation() function in the ModalWindowDetail component component is called by clicking on an event in another component Calendar.vue. I use the event bus, but nothing works for me, I don’t understand why. Please help me solve this problem.
Screenshot of Calendar and error in console
Code of Calendar on GitHub
App.vue:
<template>
<div class="all">
<app-calendar #sendTextEvent="text = $event"></app-calendar>
<app-detail v-if="modalWindowDetail"
:eventText="text"></app-detail>
</div>
</template>
<script>
import appCalendar from './components/Calendar.vue'
import appDetail from './components/ModalWindowDetail.vue'
export default {
data(){
return{
text: String
}
},
components: {
appCalendar,
appDetail
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
}
}
};
</script>
Calendar.vue component which display calendar:
<template>
<div class="overflow-div">
<transition :name="nameOfClass" >
<div :key="currentPage" class="fade_wrapper">
<div v-for="(week, i) in getCalendar" class="d_day">
<li v-for="day in week" class="li_day">
<div class="day">{{ day }}</div>
<div v-for="event in buildEvents(i, day)"
class="event"
v-bind:class="{ 'eventBrown': eventBrown(event),
'eventPurple': eventPurple(event),
'eventOrange': eventOrange(event),
'eventBlue': eventBlue(event) }"
v-on:click="openModalDetail(event)">{{ event }}
</div>
</li>
</div>
</div>
</transition>
</div>
</template>
<script>
import json from './Calendar_data.json'
import { mapState } from "vuex";
import { eventBus } from './../main.js'
export default {
mounted(){
eventBus.$on('getDetailInformation', this.openModalDetail())
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
methods: {
openModalDetail(text){
this.$emit('sendTextEvent', text);
}
};
</script>
The component in which the getDetailInformation() is located ModalWindowDetail.vue:
<template>
<div class="underModalWindow">
<div class="modalWindow">
<img src="src/assets/x.png" width="20px" height="20px">
<div class="nameofModal">Вся детальная информация о событии</div>
<div v-for="(key, name) in eventDetail" class="detailEvent">{{ name }}: {{ key }}</div>
<button>Окей</button>
</div>
</div>
</template>
<script>
import { eventBus } from './../main.js'
export default {
props: ['eventText'],
data(){
return{
options: [
{ text: 'Встреча', value: '8' },
{ text: 'День Рождения', value: '4' },
{ text: 'Праздник', value: '1' },
{ text: 'Другое', value: '16' }
],
eventDetail: Object,
}
},
computed: {
eventsData() {
return this.$store.state.eventData;
},
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
created(){
eventBus.$emit('getDetailInformation', this.getDetailInformation())
},
methods: {
getDetailInformation(){
let arrOfEvents = this.eventsData.events;
for(let z = 0; z < arrOfEvents.length; z++){
let memo = arrOfEvents[z].memo;
console.log(this.memo)
if(memo === this.eventText){
let dataStartOfEvent = arrOfEvents[z].starts_at;
let getStartDataOfEvent = new Date(dataStartOfEvent);
let dataEndOfEvent = arrOfEvents[z].ends_at;
let getEndDataOfEvent = new Date(dataEndOfEvent);
if((getStartDataOfEvent.getHours() - 3) > 0){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleTimeString(),
'Конец события': getEndDataOfEvent.toLocaleTimeString(),
'Тип события': this.getType(arrOfEvents[z].type)
}
}else if(getStartDataOfEvent.getDate() != getEndDataOfEvent.getDate()){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleDateString(),
'Конец события': getEndDataOfEvent.toLocaleDateString(),
'Тип События': this.getType(arrOfEvents[z].type)
}
}
}
}
}
}
};
</script>
You should remove the () from the function name in eventBus.$on('getDetailInformation', this.openModalDetail()) - you want to reference the function, not to call it and use the result as a reference.
Also, your function getDetailInformation() does not return anything - but you seem to expect that it returns a text. You should correct this.
And finally, I think that #sendTextEvent="text = arguments[0]" would be more appropriate - and using a dedicated method/function will be the best.

How i can validate data() value without input with vee-validate

I have a button, for load files or add some text
After load it pushed in data() prop
How i can validate this prop, if them not have input
Im found only one solution - make watch for data props. and set validate in
Maybe exist more beautiful way?
I try validator.verify() - but it dont send error in main errorBag from validateAll
This is example
<div id="app">
<testc></testc>
</div>
<script type="text/x-template" id="test">
<div>
<input type="text" v-validate="'required'" name="test_vee">
{{errors.first('test_vee')}}
<hr>
<button #click="addRow">Add</button>
<input type="text" v-model="inputValue" name="test_input"/>
<hr>
{{rows}}
<hr>
{{errors.first('rows')}}
<button #click="validateForm">Validate</button>
</div>
</script>
and script
Vue.component('testc', {
template: '#test',
data() {
return {
inputValue: '',
rows: []
}
},
watch: {
rows: {
handler: function(newVal, oldVal) {
this.$validator.errors.remove('rows');
if (this.rows.length < 2) {
this.$validator.errors.add({
id: 'rows',
field: 'rows',
msg: 'Need 2 rows!',
});
}
}
}
},
methods: {
addRow: function() {
this.rows.push(this.inputValue);
this.inputValue = '';
},
validateForm: function(){
this.$validator.validateAll();
}
}
});
Vue.use(VeeValidate);
new Vue({
el: '#app'
})
https://codepen.io/gelid/pen/YBajER
First input in example: default validate - its ok
Second input: for add items - dont need validate or has self validate (not empty for example)
In data of component i have prop rows - it is need validate before ajax request to backend for save data

vue.js – get new data information

I'm building a chrome extension using vue.js. In one of my vue components I get tab informations of the current tab and wanna display this information in my template. This is my code:
<template>
<div>
<p>{{ tab.url }}</p>
</div>
</template>
<script>
export default {
data() {
return {
tab: {},
};
},
created: function() {
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function(tabs) {
this.tab = tabs[0];
});
},
};
</script>
The Problem is, that the template gets the data before it's filled through the function. What is the best solution for this problem, when the tab data doesn't change after it is set once.
Do I have to use the watched property, although the data is only changed once?
// EDITED:
I've implemented the solution, but it still doesn't work. Here is my code:
<template>
<div>
<div v-if="tabInfo">
<p>set time limit for:</p>
<p>{{ tabInfo.url }}</p>
</div>
<div v-else> loading... </div>
</div>
</template>
<script>
export default {
data() {
return {
tabInfo: null,
};
},
mounted() {
this.getData();
},
methods: {
getData() {
chrome.tabs.query({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT }, function(tabs) {
console.log(tabs[0]);
this.tabInfo = tabs[0];
});
},
},
};
</script>
The console.log statement in my getData function writes the correct object in the console. But the template only shows the else case (loading...).
// EDIT EDIT
Found the error: I used 'this' in the callback function to reference my data but the context of this inside the callback function is an other one.
So the solution is to use
let self = this;
before the callback function and reference the data with
self.tab
You could initialize tab to null (instead of {}) and use v-if="tabs" in your template, similar to this:
// template
<template>
<div v-if="tab">
{{ tab.label }}
<p>{{ tab.body }}</p>
</div>
</template>
// script
data() {
return {
tab: null,
}
}
new Vue({
el: '#app',
data() {
return {
tab: null,
}
},
mounted() {
this.getData();
},
methods: {
getData() {
fetch('https://reqres.in/api/users/2?delay=1')
.then(resp => resp.json())
.then(user => this.tab = user.data)
.catch(err => console.error(err));
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<div v-if="tab">
<img :src="tab.avatar" width="200">
<p>{{tab.first_name}} {{tab.last_name}}</p>
</div>
<div v-else>Loading...</div>
</div>