Displaying random text in Vue.js template - javascript

I have a simple Loader component in my Vue.js app that just displays one of messages, randomly. I did it like that:
Vue.component('Loader', {
data() {
const textEntries = [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
];
return {
text: textEntries[Math.trunc(Math.random() * textEntries.length)]
};
},
template: '<p class="loading">{{ text }}...</p>'
});
I'm not sure if keeping this in data like that is fine. Won't my text ever get re-rendered with another text? Also, having the array in the data() method seems awkward. Would it be more suitable to use lifecycle hooks for that instead? Or computed property?

You should use the lifecycle hooks which Vue provides. More precisely, I believe that for your example, you should use the created hook. So your code will be like this:
Vue.component('Loader', {
created: function () {
this.$options.textEntries = [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
];
this.$options.randomIndex = Math.trunc(Math.random() * textEntries.length);
},
data() {
return {
text: this.$options.textEntries[this.$options.randomIndex]
};
},
template: '<p class="loading">{{ text }}...</p>'
});
If you leave textEntries within the data Vue property, then you will not have access to it as it will be erased from the memory as soon as the data function is processed.
WARNING: The $options, which I am using, cannot be changed since it is read-only. For more information, I would redirect you to this.

As I know data() will called only once, so the text will be one of the textEntries
I think it's better to extract textEntries out of the data method(since it will not be changed, so it should not be generated each data() call.

The right way would be to define the array in data and get the random text as computed:
new Vue({
el:"#app",
data() {
return{
textEntries: [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
]
}
},
computed:{
text: function(){
return this.textEntries[Math.trunc(Math.random() * this.textEntries.length)]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p class="loading">{{ text }}...</p>
</div>
If you want the text to be re-rendered every period of time, you can use setTimeout and keep track of the random index:
new Vue({
el:"#app",
data() {
return{
textEntries: [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
],
index: 0
}
},
mounted(){
setInterval(function(){
this.index = Math.trunc(Math.random() * this.textEntries.length)
}.bind(this), 3000);
},
computed:{
text: function(){
return this.textEntries[this.index];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p class="loading">{{ text }}...</p>
</div>

Related

Filtering a stack trace array in vuejs

I am looking for some good ideas on how to filter an array that contains a stack trace. I have a database table that has four columns, one with the stack trace error messages, one that shows the priority of the error, one that shows the date the error was registered and finally a column that displays an custom made error message, which I have placed on multiple try-blocks around my system.
On the frontend I am fetching the data with axios and placing it inside an object called errors. Then in my computed properties I create an array of fields that contain the individual columns from the database and their data. I use the Bootstrap table to output it.
<template>
<b-container>
<b-card class="mt-4">
<h5>{{ $t('events') }}</h5>
<b-table
:items="errors"
:fields="fields"
:per-page="[5, 10]"
sort-desc
primary-key="id"
/>
</b-card>
</b-container>
</template>
<script>
import {errorService} from '#/services/error';
import moment from 'moment';
export default {
components: {
CommonTable,
flapper
},
data() {
return {
errors: null,
};
},
computed: {
fields() {
return [
{
key: 'priority',
label: this.$t('errorLogs.priority'),
sortable: true
},
{
key: 'creationDateTime',
label: this.$t('creationDateTime'),
formatter: date => moment(date).locale(this.$i18n.locale).format('L'),
sortable: true
},
{
key: 'stackTrace',
label: this.$t('errorLogs.stackTrace'),
sortable: true
},
{
key: 'errorMessage',
label: this.$t('message'),
sortable: true
},
]
},
},
methods: {
load(){
errorService.getErrorLogs().then(result => {
this.errors = result.data
})
}
},
created() {
this.load()
}
};
</script>
It works as it should, but the output for the stack trace takes up way too much space in the table column.
Ideally it should only show
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
and then if the user wants more detail they can click on the stack trace and get the full version in a pop up or something.
I am guessing the easiest solution would be to filter the stack trace, so that it does not show any text beyong the : sign.
But how would I implement this in the setup that I currently have?
I am guessing in computed properties I need add a method to the stackTrace field.
So:
{
key: 'stackTrace',
label: this.$t('errorLogs.stackTrace'),
sortable: true
function: this.filteredStackTrace()
},
And then create a new method.
filteredStackTrace(){
this.errors.stackTrace.filter(some filter...)
}
Maybe something like following snippet:
const app = Vue.createApp({
data() {
return {
st: `Exception in thread "main" java.lang.NullPointerException
at com.example.myproject.Book.getTitle(Book.java:16)
at com.example.myproject.Author.getBookTitles(Author.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)`,
expanded: false
};
},
computed: {
firstLine() {
return this.st.split('\n')[0]
},
allLines() {
return this.st.split('\n').filter((item, idx) => idx !== 0).toString()
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
{{ firstLine }}
<button #click="expanded = !expanded">all</button>
<div v-if="expanded">{{ allLines }}</div>
</div>

How can I convert a long list of 'watch' into a functional way in VueJS?

I'm a novice for vueJS.
I have a long list of watch list. It's all same.
But I don't know how to convert them into a functional way.
They are all for adding comma in input tag and v-model.
It works very well. But the codes look very dumb because they are exactly the same, but not their name.
new Vue({
data: {
tmp_price1: '',
tmp_price2: '',
tmp_price3: '',
tmp_a_price: '',
tmp_b_price: '',
},
watch: {
tmp_price1: function(newValue) {
if (newValue != '') {
const result = newValue.replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
Vue.nextTick(() => this.tmp_price1 = result);
}
},
tmp_price2: function(newValue) {
if (newValue != '') {
const result = newValue.replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
Vue.nextTick(() => this.tmp_price2 = result);
}
},
....(repeat)
},
Please help me improve these dumb codes efficient way.
It may seem like over-engineering, but I would probably make a component to encapsulate the commafying behavior. The component would have a settable computed in it that emits the commafied value for the parent to use in updating.
new Vue({
el: '#app',
data: {
tmp_price1: '',
tmp_price2: '',
tmp_price3: '',
tmp_a_price: '',
tmp_b_price: '',
},
components: {
commafiedInput: {
props: ['value'],
template: '<input v-model="commaValue">',
computed: {
commaValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', this.addCommas(newValue));
}
}
},
methods: {
addCommas(v) {
return v.replace(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div> {{tmp_price1}}
<commafied-input v-model="tmp_price1"></commafied-input>
</div>
<commafied-input v-model="tmp_price2"></commafied-input>
<commafied-input v-model="tmp_price3"></commafied-input>
<commafied-input v-model="tmp_a_price"></commafied-input>
<commafied-input v-model="tmp_b_price"></commafied-input>
</div>
Just displaying the formatted version of the value can be done with a simple filter:
new Vue({
el: '#app',
data: {
tmp_price1: '123123',
tmp_price2: '',
tmp_price3: ''
},
filters: {
myFilter(v) {
return v.replace(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div><input v-model="tmp_price1">{{tmp_price1 | myFilter}}</div>
<div><input v-model="tmp_price2">{{tmp_price2 | myFilter}}</div>
<div><input v-model="tmp_price3">{{tmp_price3 | myFilter}}</div>
</div>
...but that's not enough for the input fields; you can't just throw a filter onto the v-model attribute. A sub-component as described in Roy J's answer is probably the best and most reusable way to handle that, but if you're ok with something a bit quick-and-dirty (but not too dirty) you could just throw a change handler at the problem:
new Vue({
el: '#app',
data: {
tmp_price1: '123123',
tmp_price2: '',
tmp_price3: ''
},
methods: {
myFormatter(fieldname) {
/* replace the user's input with the formatted value.
There's probably some clever way to read the v-model
name from the input field instead of passing it to the
method as a string, but I'm not going to mess around
with that for what is after all a quick-and-dirty technique */
this[fieldname] = this[fieldname].replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
},
mounted() {
// if the initial values aren't always empty, you'll need to run the
// formatter function on component load as well as on user input:
['tmp_price1', 'tmp_price2', 'tmp_price3'].forEach(f => {
this.myFormatter(f);
});
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div><input v-model="tmp_price1" #input="myFormatter('tmp_price1')">{{tmp_price1}}</div>
<div><input v-model="tmp_price2" #input="myFormatter('tmp_price2')">{{tmp_price2}}</div>
<div><input v-model="tmp_price3" #input="myFormatter('tmp_price3')">{{tmp_price3}}</div>
</div>
(Or as another variation, this answer to a similar question uses essentially the same technique, but wraps it in a Vue directive to bind the input events instead of attaching #input handlers.)
Note that in the "filter" version, the v-model values remain the original user input; the filter only affects the displayed value. In the second example the formatting is applied to the v-modeled values themselves, so if you pass those values on to somewhere else you'll get the formatted version. Both techniques can be useful, depending on the circumstances. (Or you could even use them in combination -- remove non-digits in the v-model, then add the commas via a filter just for display, for example.)

Is there any way to have multiple Vues have a computed listener working on the same value?

Setup:
I have multiple Vue components, and each component has multiple instances in different dialogs in my web app.
For each type of component I have a global state (handrailOptions in the example below) so that each type of component stays in sync across the dialogs.
I'd like for it so that when a component proceeds beyond step 1, I hide the other components in that dialog.
I have achieved this nicely using the computed / watch combo.
However, my problem is that it seems if I try to listen in with computed through more than 1 Vue instance, it hijacks the other listeners.
Problem
Below is a simplified version of what I'm working with, when the app starts up, the console logs 'computed 1' & 'computed 2'. But then when I change handrailOptions.step, only the second fires. ('computed 2' & 'watched 2')
Is there any way to have multiple Vues have a computed listener working on the same value?
handrailOptions = {
step: 1
};
Vue.component( 'handrail-options', {
template: '#module-handrail-options',
data: function() {
return handrailOptions;
},
});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 2');
}
}
});
This works as expected. I made handrailOptions responsive by making the data object of a new Vue. Making it the data object of a component, as you did, could also work, but the component would have to be instantiated at least once. It makes more sense to have a single object for your global, anyway.
handrailOptions = {
step: 1
};
// Make it responsive
new Vue({data: handrailOptions});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 2');
}
}
});
setInterval(() => ++handrailOptions.step, 1500);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="dialog-estimate-questions">
Main step {{newHandrailStep}}
</div>
<div id="dialog-checkout">
CD step {{newHandrailStep}}
</div>

Vue.js get v-for array value in computed

I'm passing user data to a component via Laravel, this data contains a distance in miles, however the user has the option to set the view distance in km, so I have to pass profile.distance_mi to be computed, how do I accomplish this?
HTML:
<saved profiles="{{json_encode($profiles)}}" unit="{{Auth::user()->settings->unit}}"></saved>
<div v-for="profile in profiles" class="col-xs-12 col-sm-4 col-md-3">
...
<h4>#{{distance}}</h4>
....
</div>
JS
new Vue(
{
el: 'body',
components:
{
saved:
{
template: '#saved',
props: ['profiles', 'unit'],
created: function ()
{
this.profiles = JSON.parse(this.profiles);
},
computed:
{
distance: function ()
{
var unit = this.unit;
return unit == 0 ? Math.round(distance * 1.60934) + ' km' : distance + ' miles';
}
}
}
}
});
A computed property like this won't work in a for loop on a component.
I think the easiest way to achieve what you want to achieve is to make another component. I'm a bit unclear on which properties you're trying to observe and what not, but I think this will get you in the right direction:
Profile Distance Component
var ProfileDistance = Vue.extend({
props: ['profile'],
template: '<span>{{distance}}</span>',
computed: {
distance: function() {
if (this.profile.hasOwnProperty('distance_mi')) {
return this.profile.distance_mi + ' miles away';
} else if(this.profile.hasOwnProperty('distance_km')){
return this.profile.distance_km + ' kilometers away';
} else {
return 'Distance N/A';
}
}
}
})
Of course make sure you use this component inside of your existing saved component
components: {
//....
'profile-distance': ProfileDistance
}
Then just pass your profile as a property to your profile-distance component:
<profile-distance :profile="profile"></profile-distance>
here's a working jsfiddle

How do I display data through components with Vue.js (using Vueify)?

I'm having trouble getting data to display in my Vue components. I'm using Vueify and I'm trying to load an array of listings from the listings.vue component and I keep getting errors. Also, I don't understand how to pull in the data via the computed method. Any help would be appreciated.
This is the error I'm getting in the console:
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
[Vue warn]: $mount() should be called only once.
Here is my app.vue
// app.vue
<style>
.red {
color: #f00;
}
</style>
<template>
<div class="container">
<div class="listings" v-component="listings" v-repeat="listing"></div>
</div>
</template>
<script>
module.exports = {
replace: true,
el: '#app',
components: {
'listings': require('./components/listings.vue')
}
}
</script>
Here is my listings.vue component
<style>
.red {
color: #f00;
}
</style>
<template>
<div class="listing">{{title}} <br> {{description}}</div>
</template>
<script>
module.exports = {
data: {
listing: [
{
title: 'Listing title number one',
description: 'Description 1'
},
{
title: 'Listing title number two',
description: 'Description 2'
}
]
},
// computed: {
// get: function () {
// var request = require('superagent');
// request
// .get('/post')
// .end(function (res) {
// // Return this to the data object above
// // return res.title + res.description (for each one)
// });
// }
// }
}
</script>
The first warning means when you are defining a component, the data option should look like this:
module.exports = {
data: function () {
return {
listing: [
{
title: 'Listing title number one',
description: 'Description 1'
},
{
title: 'Listing title number two',
description: 'Description 2'
}
]
}
}
}
Also, don't put ajax requests inside computed properties, since the computed getters gets evaluated every time you access that value.

Categories