Primitive array validation with vuelidate - vue.js

I'm using vuelidate 2.0 to validate a form.
I'm trying to validate a primitive array, and I can't find a way to do it.
When using helpers.forEach({ required }) it works perfectly with array of objects, but it doesn't seem to be working with array of primitives.
For example form values can be:
const values = reactive({
name: "",
labels: []
})
Is there a way we can define labels validation?
I have tried to use:
const validations = {
name: { required },
labels: {
minLength: minLength(2),
$each: helpers.forEach({
required,
}),
}
}
But it seems to be working only on labels and not for each.
So what happens is that if I have less than 2 items in the array, I get the correct error, but each item is not getting the required error as should.

Related

Vuetify / Vue (2) data table not sorting / paginating upon new bound prop

Keeping the table as basic as possible to figure this out. I am struggling to learn how to create server side sorting/pagination to function in vuetify. When I add the :options or :server-items-length bind the table no longer sorts or paginates.
Without either of those, I get a default listing of 10 items per row - and the sorting all works perfectly fine as well the pagination. However, parameters in the api require a page item count thus forcing a hardcoded number or using the :options bind. If i just place a hard coded number things work fine, but when I bind I get proper items per page but no sorting and pagination.
Very simple data table:
<v-data-table
:items="ItemResults.items"
:headers="TableData.TableHeaders"
:loading="TableData.isLoading"
>
</v-data-table>
//Base data returns, with headers and options as well the array that items are stored in.
data() {
return {
ItemResults:[],
TableData: {
isLoading: true,
TableHeaders: [
{ value: "title", text: "Title" },
{ value: 'artist', text: 'Artist' },
{ value: 'upc', text: 'UPC' },
{ value: "retailPrice", text: "Price/Quantity"},
],
},
options:
{
page: 1,
itemsPerPage: 15
},
}
},
//Then last, my async method to grab the data from the api, and place it in the itemresults array.
async getProducts(){
this.TableData.isLoading = true;
const { page, itemsPerPage } = this.options;
var temp = await this.$axios.get(`Inventory/InventoryListing_inStock/1/${page}/${itemsPerPage}`);
this.ItemResults = temp.data;
this.TableData.isLoading = false;
return this.ItemResults;
},
I have tried following Vuetify Pagination and sort serverside guide, but I'm not sure where they are recommending to make the axios call.
The lead backend dev is working on setting a sorting function up in the api for me to call paramaters to as well - Im not sure how that will function along side.
but I dont know how to have this controlled by vuetify eithier, or the best practice here.
EDIT:
I've synced the following:
:options.sync="options"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
but i think I dont need to sync the last two. My options:
options:
{
page: 1,
itemsPerPage: 15,
sortBy: ['title'],
sortDesc: [false]
},
and in my data I put the array for sort by and sort desc
sortBy: [
'title', 'artist', 'upc', 'retailPrice'
],
sortDesc:[true, false],
pagination is now working, and sort ascending is now working, but when I click to descend the header I get an error that the last two params are empty on update to / / instead of /sortBy/sortDesc result. So its not listing the values on changes.
When your component mounts, you need to fetch the total number of items available on the server and the first page of data, and set these as :items and :server-items-length. The latter will (as you observed) disable paging and sorting, as your server will take care of it, and it is used to calculate the total number of pages given a selected page size.
Now you need to know when to reload data. For this, you either bind options to the v-data-table with two-way binding (.sync) and set a watcher on it, or you listen to the update:options event. And whenever options change, you fetch the selected data from the server.
That's basically all it takes.
In your template:
<v-data-table
:items="items"
:headers="headers"
:loading="true"
:server-items-length="numberOfItemsOnServer"
#update:options="options => loadPage(options)"
/>
In your component:
methods: {
async loadPage(options){
this.items = [] // if you want to show DataTable's loading-text
this.items = await fetch('yourUrl' + new URLSearchParams({
// build url params from the options object
offset: (options.page - 1) * options.itemsPerPage,
limit: options.itemsPerPage,
orderBy: options.sortBy.map((sb,ix) => [sb, options.sortDesc[ix] ? 'desc' : 'asc'])
}))
}
}

error : Cannot read property 'querySelectorAll' of null

I'm trying to make a graph with libraries on nuxt
I'm looking to use chartist but it doesn't work for now.
Link chartist: https://gionkunz.github.io/chartist-js/getting-started.html
I'm trying diplay the diagram by following the get started part.
In my component, i'm doing this:
<template>
<canvas class="ct-chart ct-perfect-fourth" />
</template>
export default {
created() {
this.creatChart();
},
methods: {
creatChart() {
const data = {
// A labels array that can contain any sort of values
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
// Our series array that contains series objects or in this case series data arrays
series: [
[5, 2, 4, 2, 0]
]
}
const chart = new Chartist.Line('.ct-chart', data);
return chart;
},
},
}
And i receive this error : Cannot read property 'querySelectorAll' of null
Of course chartist is installed with npm...
Instead of created() {} to init your Chartist, you must use the mounted() {} method to init your chart only on client side.
mounted() {
this.creatChart();
},
The "created" hook will be run twice once on server-side, then once on client-side. The "mounted" will be run only once on client-side.
Chartist is only available on client-side (browser), due to the usage of document.querySelectorAll.
But on server-side (Node.js), document does not exist... which explains your error of Cannot read property 'querySelectorAll' of null.

Is there an easier way of updating nested arrays in react-native (react-redux)?

I am trying to update an array inside of my array (nested array) with react-redux. I found a solution to how to do this but is there any easier way of doing this rather than passing multiple parameter to the action.
[types.HISTORY_UPDATE](state, action){
return {
...state,
budgets: [
...state.budgets.slice(0,action.id),
{
key: action.key,
id: action.idd,
name: action.name,
budgetType: action.budgetType,
startDate: action.startDate,
currency: action.currency,
amount: action.amount,
amountLeft: action.amountLeft,
rollOver: action.rollOver,
color: action.color,
iconName: action.iconName,
history: [
...state.budgets[action.id].history.slice(0,action.histId),
{
note: action.note,
amount: action.amount,
type: action.type,
date: action.date,
fullDate: action.fullDate,
hours: action.hours,
min: action.min,
month: action.month,
year: action.year
},
...state.budgets[action.id].history.slice(action.histId+1)
]
},
...state.budgets.slice(action.id+1)
]
}
},
and the action goes like this
export function updateHistory(id,key,idd,name,budgetType,startDate,currency,amount,amountLeft,rollOver,color,iconName,histId,........){
I don't want to spend time with passing multiple parameter like this while using react-redux and also while I tried to run my application on my phone sometimes it really slows the application. Is it because of the example above?
I would be really appreciated If you guys come up with a solution.
I typically do not store arrays in redux, since updating a single element really is a burden as you noticed. If the objects you have inside your array all have a unique id, you can easily convert that array to an object of objects. As key for each object you take that id.
const convertToObject = (array) => {
let items = {};
array.map((item) => {
items[item.id] = item;
});
return items;
};
In your action you simply just pass the item you want to update as payload, and you can update the redux store very easily. In this example below I am just updating the budgets object, but the same logic applies when you assign a history object to each budget.
[types.BUDGET_UPDATE](state, action){
const item = action.payload;
return {
...state,
budgets: {
...state.budgets,
[item.id]: item
}
}
}
And if you want an array somewhere in your component code, you just convert the redux store object back to an array:
const array = Object.values(someReduxStoreObject);

Input field not reacting to data changes after being written to by a user

While creating a Vue.js application I have become stuck at a weird problem. I want to be able to manipulate an input field (think increment and decrement buttons and erasing a zero value on focus, so the user doesn't have to) and up until a user writes to the input field, everything is fine. After that, however, further changes in the data are no longer represented in the input field.
As I was sure I could not be the only one with this particular problem, I searched extensively, but had no luck. What baffles me the most is that everything works until the field is written to, since I can not really imagine why this would remove the data binding.
The following code should show the same behavior. It is an input field component, which is initialized with a zero value. On focus the zero gets removed. This works, until a user manually writes to the field after which zero values will no longer be removed, even though the focus method fires, the if-condition is met and the data in the amount-variable is changed.
Vue.component('item', {
data: function () {
return {
amount: 0
}
},
render: function (createElement) {
var self = this;
return createElement('input', {
attrs: {
//bind data to field
value: self.amount,
type: 'number'
},
on: {
//update data on input
input: function (event) {
self.amount = event.target.value;
},
//remove a zero value on focus for user convenience
focus: function (event) {
if (self.amount == 0 || self.amount == "0") {
self.amount = '';
}
}
}
})
}
})
I think you need to use domProps instead of attrs to make it reactive. But I would suggest you use vue's template syntax or if you insist on using the render function I would also suggest you to use JSX.

Sharing data from a VueJS component

I have a VueJS address lookup component.
Vue.component('address-lookup',
{
template: '#address-lookup-template',
data: function()
{
return {
address: {'name': '', 'town:': '', 'postcode': ''},
errors: {'name': false, 'town': false, 'postcode': false},
states: {'busy': false, 'found': false},
result: {}
}
},
methods:
{
findAddress: function(event)
{
if( typeof event === 'object' && typeof event.target === 'object' )
{
event.target.blur();
}
$.ajax(
{
context: this,
url: '/lookup',
data:
{
'name': this.address.name,
'town': this.address.town,
'postcode': this.address.postcode
},
success: function(data)
{
this.states.busy = false;
this.states.found = true;
this.address.name = data.name;
this.result = data;
}
});
},
reset: function()
{
this.states.found = false;
this.result = {};
}
}
});
Inside my template I've then bound the result like so:
<p>{{ result.formatted_address }}</p>
There is some extra data returned within the result (like a twitter handle) that isn't part of the address lookup template, and occurs on a separate part of the form. For reasons relating to how my form is structured I can't include these inputs within the same template.
I found a way to bind those inputs, although it felt somewhat 'hacky'.
<input type="text" name="twitter" v-model="$refs.lookupResult._data.result.twitter">
That all works fine.
My problem is that the form is included as part of a larger template sometimes in the context of creating a new record, sometimes in the context of editing. When editing a record, the lookup component is removed (using an if server-side, so the template is no longer loaded at all) and when that happens I get this error.
$refs.lookupResult._data.result.twitter": TypeError: Cannot read property '_data' of undefined
This makes sense. lookupResult is defined when I include the template, and when editing I am removing this line:
<address-lookup v-ref:lookup-result></address-lookup>
I've worked around it by including a version of each extra input without the v-model attribute, again using a server-side if. But there are quite a few of these and it's getting a bit messy.
Is there a cleaner approach I could be using to better achieve this?
So I don't know the hierarchy of your layout, it isn't indicated above, but assuming that address-lookup component is a child of your parent, and you in fact need the results of address lookup in that parent, eg:
<parent-component> <!-- where you need the data -->
<address-lookup></address-lookup> <!-- where you lookup the data -->
</parent-component>
then you can simply pass the data props, either top-down only (default) or bidirectionally by defining 'address' for example on your parent's vue data hook:
// parent's data() function
data = function () {
return {
address: {}
}
}
// parent template, passed address with .sync modifier (to make it bi-directional)
<parent-component>
<address-lookup :address.sync='address'></address-lookup>
</parent-component>
// have the props accepted in the address look up component
var addressComponent = Vue.extend({
props: ['address']
})
Now in your $.ajax success function, simply set the props you need on this.address. Of course you can do this with all the props you need: errors, results, state etc. Even better, if you can nest them into a single key on the parent, you can pass the single key for the object containing all four elements instead of all four separately.