Moment Duration - expected Number got Object - vue.js

My backend returns timespan in the following format:
"00:34:49.4073541"
I have read and I can use moment.duration to return that data.
I want to display minutes and seconds and disable buttons based on this timeout.
<template>
<countdown :time="this.time">
<p>Time Remaining: {{ this.time.minutes }} minutes, {{ this.time.seconds }} seconds.</p>
</countdown>
</template>
import moment from 'moment'
export default {
data () {
return {
otp: '',
initialOtpSent: false,
time: 0,
}
},
methods: {
createOtp (bvModalEvt) {
if (res.data.resetOtpRetriesTimestamp === undefined) {
this.initialOtpSent = true
this.$bvToast.toast('The otp has been sent!', {
title: 'SUCCESS!',
variant: 'success',
solid: true,
static: true,
noAutoHide: false
})
// check on retries timestamp and disable sendotp and resend otp
} else {
const timeDuration = moment.duration(res.data.resetOtpRetriesTimestamp)
this.time = timeDuration
The error is:
Invalid prop: type check failed for prop "time". Expected Number, got
Object

Refer to the example in the usage docs for the component you referenced, which shows the :time attribute as a number, in terms of milliseconds.
You can change your Moment code to this.time = timeDuration.asMilliseconds();
Then change the template to match the format shown in the docs. For example:
<countdown :time="this.time">
<template slot-scope="props">Time Remaining:{{ props.minutes }} minutes, {{ props.seconds }} seconds.</template>
</countdown>

Related

Waiting time counter for each object in array Vue js

To get in context, I have a table that shows incoming calls and the waiting time for every call. The data array looks like:
[
{
id: 1,
patient_name: lorem ipsum,
created_at: 2022-02-02 09:10:35,
...
},
{
id: 2,
patient_name: dolor ipsum,
created_at: 2022-02-02 09:00:35,
...
}
]
I'm trying to figure out how to assign a setTimeout for each object, but I'm completely lost.
So far, I found that a counter can be made through a watcher, but of course this only acts as a "global" counter.
watch: {
timerCount: {
handler (value) {
if (value > 0) {
setTimeout(() => {
this.timerCount++
}, 1000)
}
},
immediate: true // This ensures the watcher is triggered upon creation
}
},
Is there a way to use a function to show a counter on each object? I was thinking in something like this:
<template>
<span v-for="call in calls" :key="call.id">
Requested at: {{ call.created_at }}
waiting time: {{ showWaitingTime(call.created_at) }} // <- Not sure if this can be done
</span>
</template>
...
<script>
....
methods: {
showWaitingTime (created_at) {
// get diff in seconds between created_at and now
// do something here with a timeOut() to increment the object property...
}
}
</script>
In addition, I would like to return the waiting time in HH:mm:ss format.
One solution is to wrap the {{ showWaitingTime(call.created_at) }} with a <span> that is keyed on timerCount, so that the <span> is re-rendered when timerCount changes (thus calling showWaitingTime again to compute the new time string):
In the template, wrap the timestamp string with a <span> that has its key bound to timerCount:
waiting time: <span :key="timerCount">{{ showWaitingTime(call.created_at) }}</span>
In a watcher on calls, use setInterval to start a periodic timer. Be sure to stop the timer with clearInterval before starting a new timer and when unmounting the component.
export default {
beforeUnmount() {
clearInterval(this._updateTimer)
},
// Vue 2 equivalent of beforeUnmount()
beforeDestroy() {
clearInterval(this._updateTimer)
},
watch: {
calls: {
handler(calls) {
clearInterval(this._updateTimer)
if (calls.length) {
this._updateTimer = setInterval(() => this.timerCount++, 1000)
}
},
immediate: true,
},
},
}
The watcher you have on timerCount is effectively implementing setInterval. Remove that code since it's obviated by the code in step 2.
export default {
watch: {
// timerCount: {⋯} // ⛔️ remove this watch
}
}
In showWaitingTime(), calculate the HH:mm:ss from the difference between the given time and now:
export default {
methods: {
showWaitingTime(created_at) {
const diff = new Date() - new Date(created_at)
const twoD = x => `${Math.floor(x)}`.padStart(2, '0')
const HH = twoD((diff / (60 * 60 * 1000)) % 24)
const mm = twoD((diff / (60 * 1000)) % 60)
const ss = twoD((diff / 1000) % 60)
return `${HH}:${mm}:${ss}`
},
},
}
demo

Unexpected side effect in "isExpiryComing" computed property

I tried to make the nearExpiry attribute to become TRUE if it is within the range of 30 days. But I hit an error which is Unexpected side effect in "isExpiryComing" computed property, is there any way I can overcome this?
I'm not sure how do I use slice at the isExpiryComing computed properties. Is there any workaround for this error?
<template>
<div class="container wrapper d-flex flex-column justify-content-center align-items-center">
<h1 class="text-info">Ingredients List</h1>
<ingredients-list class="justify-content-center"
v-for="(ingredient,index) in sortedItems"
:key="index"
:index='index'
:food-name="ingredient.food"
:food-expiry="ingredient.expiryDate"
:is-expiry="isExpiryComing.nearExpiry"></ingredients-list>
</div>
</template>
<script>
export default {
data() {
return {
ingredients: [
{
food: 'CARROT',
expiryDate: '2020-12-12',
nearExpiry: false
},
{
food: 'PAPAYA',
expiryDate: '2018-1-15',
nearExpiry: false
},
{
food: 'ORANGE',
expiryDate: '2021-10-13',
nearExpiry: false
},
{
food: 'CHICKEN',
expiryDate: '2019-4-23',
nearExpiry: false
},
{
food: 'MEAT',
expiryDate: '2021-5-23',
nearExpiry: false
},
],
}
},
computed: {
sortedItems() {
return this.ingredients.slice().sort((a, b) => {
return new Date(a.expiryDate) - new Date(b.expiryDate);
});
},
isExpiryComing() {
const now = new Date().getTime()
const expiryDate = new Date(this.expiryDate).getTime()
if (now - expiryDate > (30 * 24 * 60 * 60 * 1000)) {
this.nearExpiry = false
} else {
this.nearExpiry = true
}
return this.nearExpiry
}
},
}
</script>
It's because you're chagning another variable inside a computed property, this.nearExpiry - your use case doesn't need it.
I assume you want to check the expiration date for every product inside v-for.
I would suggest removing isExpiryComing entirely and changing the sortedItems to:
return this.ingredients
.slice()
.sort((a, b) => {
return new Date(a.expiryDate) - new Date(b.expiryDate)
})
.map((ingredient) => {
const now = new Date().getTime()
const expiryDate = new Date(ingredient.expiryDate).getTime()
return { ...ingredient, nearExpiry: now - expiryDate > 30 * 24 * 60 * 60 * 1000 }
})
Might be also a good idea to move const now = new Date().getTime() higher, to data().
A couple of issues I see with this:
isExpiryComing is scoped to your current component (like ingredients and sortedItems), not to the iteration of your v-for (like ingredient and index).
Setting to this.nearExpiry = true is likely what is causing the error. Computed properties should be pure-functions, i.e. they should not affect the state (normally this wouldn't be the case, as this should be scoped to the function with the method signature you used, but this is Vue so good luck with upholding that). If you want to affect state, you should use a watched property. (and since you are just returning this.nearExpiry, why not just make nearExpiry a local variable?).
To fix the error, just make nearExpiry a local variable instead of a vue-instance variable (this.nearExpiry). But, per my first point, that's probably not what you want either. Probably move the computed property to your ingredients-list component.

Vuejs track input field

I need to check whether my input field is empty or not.
Logic
if form.name has value, use increase function
if form.name is empty, use decrease function
do not use increase, decrease functions on each character that user inputs or removes
Code
<el-form-item label="Product name *">
<el-input v-model="form.name"></el-input>
</el-form-item>
methods: {
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
any idea?
Update
Script
data() {
return {
form: {
name: '', // require
slug: '',
price: '', // require
supplier_id: '', // require
new_price: '',
base_price: '',
sku: '',
qty: 1, // require
active: '', // require
photo: '',
photos: [],
shortDesc: '',
longDesc: '',
origin: '',
tags: [],
brand_id: '', // require
categories: [],
user_id: '',
seoTitle: '',
seoTags: '',
seoPhoto: '',
seoDescription: '',
variations: [],
options: [],
condition: '', // require
isbn: '',
ean: '',
upc: '',
height: '',
weight: '',
lenght: '',
width: '', // require
},
}
},
methods: {
onSubmit(e) {
e.preventDefault();
axios.post('/api/admin/products/store', this.form)
.then(res => {
// do my things
})
.catch(function (error) {
console.log('error', error);
});
},
}
HTML
<el-form ref="form" :model="form" label-width="120px" enctype="multipart/form-data">
// my inputs (listed in form part in script above)
<el-button type="primary" #click="onSubmit" native-type="submit">Create</el-button>
</el-form>
One possible solution would be to use #focus and #blur events to check if form.name has a value before increasing or decreasing, this would be fired on focus or on blur events, so you will not have the methods fired on each character input or deletion.
for example:
<el-form-item label="Product name *">
<el-input #focus="checkName" #blur="checkName" v-model="form.name"></el-input>
</el-form-item>
methods: {
checkName() {
//If form.name has a value then run increase method, otherwise run decrease method
!!this.form.name ? this.increase() : this.decrease()
},
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
You can see a working fiddle HERE
UPDATE
Alright so i did follow the rules you state on your question, and i didn't know you wanted to get the percentage of completion of the form, so in order to do that, i would suggest to use a computed property, you can read more about computed properties in the VueJS Documentation, this way the percentage is calculated based on the criteria we can give it, and only if the form has values.
computed: {
formProgress: function () {
let totalInputs = Object.keys(this.form).length;
let filledInputs = 0;
Object.values(this.form).forEach(val => {
if (!!val){
filledInputs++;
}
});
return (filledInputs/totalInputs)*100
}
},
As you can see in one single computed property you can handle the complex logic and return the value reactively, to explain it better, i'm counting the lenght of the form object, to get total number of inputs in your form, so it's important to have all your form data inside the form data object, then i convert that object to an array to iterate it, and i check if each property has a value on it, if does it, i add 1 to the filledInputs counter, and finally just return a simple math to get the percentage. please check the new Fiddle here to see it in action:
FORM PROGRESS FIDDLE
If you have any other doubt just let me know.
UPDATE 2:
All right in order to only count for specific inputs for the form progress, i have modified the code to work based on an array that contains the names of the properties that are required. here is the full code:
data() {
return {
form: {
name: '',
lastName: null,
categories: [{}],
},
requiredFields: ['name', 'categories']
};
},
computed: {
formProgress: function () {
let totalInputs = this.requiredFields.length;
let filledInputs = 0;
Object.entries(this.form).forEach(entry => {
const [key, val] = entry;
if (this.requiredFields.includes(key)){
switch (val.constructor.name) {
case "Array":
if (val.length !== 0){
if (Object.keys(val[0]).length !== 0){
filledInputs++;
}
}
break
case "Object":
if (Object.keys(val).length !== 0){
filledInputs++;
}
break
default:
if (!!val){
filledInputs++;
}
}
}
});
return Number((filledInputs/totalInputs)*100).toFixed(1)
}
},
And here is the updated FIDDLE
As you can see now i'm using Object.entries to get the key and value of the form object, so you can have a single form object to send to your backend, this way i'm checking first if the key is in the required fields, and if has a value, so all you need to do is update the requiredFields data array with the same names as your inputs data property to make the validation work, also there is a validation depending if is array, array of objects, or object, that way it will validate input on each data type.
Hope this works for you.

I get different result from Graphql playground and front-end

I am using graphql and Vue.js and apollo
Here is my DateBank
const sensorsdb = [
{
name: "sensor 1",
id: "s-1",
timestamp: 1582021371,
value: 100
},
{
name: "sensor 1",
id: "s-1",
timestamp: 1579451703,
value: 150
},
{
name: "sensor 2-1",
id: "s-2-1",
timestamp: 1582021371,
value: 200
},
{
name: "sensor 2-2",
id: "s-2-2",
timestamp: 1579451703,
value: 350
},
{
name: "sensor 2-2",
id: "s-2-2",
timestamp: 1582021371,
value: 300
},
{
name: "sensor 3",
id: "s-3",
timestamp: 1582021371,
value: 400
},];
I want to get all objects according to object id. sensorId is an array. because I want to get multiple objects with multiple ids.
The following is my API function to get object.
async getDataById({ sensorId }) {
let sensorsData = [];
for (let i = 0; i < sensorId.length; i++) {
let sensorData = this.sensorDataStore.sensors.filter(sensor => sensor.id === sensorId[i]);
sensorsData = sensorsData.concat(sensorData);
}
return sensorsData;
}
In Front-end, gql file is following:
query sensorData($id: [String]){
sensorData(id: $id){
name
id
value
timestamp
}}
and with my apollo query code in vue.js, in this case selectedSensorId is ["s-2-1", "s-2-2"]
<ApolloQuery :query="require('../graphql/sensorDataById.gql')" :variables="{ id: selectedSensorId }">
<template v-slot="{ result: { loading, error, data } }">
<b-loading :is-full-page=true :active.sync=loading :can-cancel="false"/>
<div v-if="error">
<no-data-error />
</div>
<div v-if="data">
{{ data }}
<bar-chart-view :sensorInfo="data.sensorData"/>
</div>
</template>
</ApolloQuery>
But I got the following different result:
Graphql playground which has correct result
The front-end result with duplicated sensor-s-2
Apollo Client normalizes results according to the id and __typename fields as described in the docs. If an array returns multiple items with the same id, by default they will share the same cache key, which means what's returned by the client will be the same object.
You should provide a custom dataIdFromObject function to your InMemoryCache constructor that accommodates your specific use case. Something like:
const dataIdFromObject = object => {
switch (object.__typename) {
case 'SensorDataPoint': return `SensorDataPoint:${object.id}:{value}`;
default: return defaultDataIdFromObject(object);
}
}
Note that if you use the same type elsewhere, you may experience issues with the cache updated correctly after mutations because we are now keying off both the value and id. You might want to consider a different approach to your schema where the ids are actually unique :
type SensorDataPoint {
id: ID!
sensorId: ID!
sensorName: String!
value: Int!
timestamp: Int!
}
or even better
type SensorDataPoint {
id: ID!
value: Int!
timestamp: Int!
sensor: Sensor!
}
type Sensor {
id: ID!
name: String!
}
I know it its been a while but what Daniel Rearden mentioned above, I included the { addTypename: false } as options for InMemoryCache
const client = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, extensions }) => {
console.log(`[GraphQL error]: Message: ${message}, Code: ${extensions?.code}`)
})
if (networkError) {
console.log(`[Network error] ->: ${networkError}`)
Toast.show('Connection Error', {
position: Toast.positions.TOP,
type: 'danger',
duration: 3000,
})
}
}),
authMiddleware,
link,
]),
cache: new InMemoryCache({ addTypename: false }),
});

Able to display the result of a promise but length of the result appears as undefined

I'm new to vue/promise and I am struggling to understand why when I try to display the result of a promise I end up with the expected data but when I try to find out its length, it says undefined
When I try to display the alerts from displayAlerts() , I can see a list of alerts, 2 in total. However in computed within the title function ${this.displayAlerts.length} appears as undefined, I was expecting to see 2.
Does it have something to do with displayAlerts() resulting in a promise? How do I fix the code such that I get 2 instead of undefined?
The code is below:
<template>
<div>
{{displayAlerts}}
<li v-for="alert in alerts" class="alert">
{{alert['name']}}
</li>
</div>
</template>
export default {
data () {
return {
alerts: null,
alert: new Alert(),
updatedAlert: new Alert(),
deletedAlert: new Alert(),
};
},
computed: {
...mapGetters("authentication",['token']),
...mapGetters("user",['profile']),
displayAlerts() {
return getUserAlert({
user_id: this.profile.user_id,
token: this.token
}).then(response => (this.alerts = response.data)).catch(
error => console.log(error)
)
},
title () {
return `My Alerts (${this.displayAlerts.length})`
},
test2() {
return [1,2,3]
},
}
};
</script>
Something like this should work:
<template>
<div v-if="alerts">
<h4>{{ title }}</h4>
<li v-for="alert in alerts" class="alert">
{{ alert.name }}
</li>
</div>
</template>
export default {
data () {
return {
alerts: null
}
},
computed: {
...mapGetters('authentication', ['token']),
...mapGetters('user', ['profile']),
title () {
// Handle the null case
const alerts = this.alerts || []
return `My Alerts (${alerts.length})`
}
},
methods: {
// This needs to be in the methods, not a computed property
displayAlerts () {
return getUserAlert({
user_id: this.profile.user_id,
token: this.token
}).then(response => (this.alerts = response.data)).catch(
error => console.log(error)
)
}
},
// Initiate loading in a hook, not via the template
created () {
this.displayAlerts()
}
}
</script>
Notes:
Computed properties shouldn't have side-effects. Anything asynchronous falls into that category. I've moved displayAlerts to a method instead.
Templates shouldn't have side-effects. The call to load the data should be in a hook such as created or mounted instead.
title needs to access this.alerts rather than trying to manipulate the promise.
While the data is loading the value of alerts will be null. You need to handle that in some way. I've included a v-if in the template and some extra handling in title. You may choose to handle it differently.
I've added title to the template but that's just for demonstration purposes. You can, of course, do whatever you want with it.
I've assumed that your original displayAlerts function was working correctly and successfully populates alerts. You may want to rename it to something more appropriate, like loadAlerts.