Yup: deep validation in array of objects - formik

I have a data structure like this:
{
"subject": "Ah yeah",
"description": "Jeg siger...",
"daysOfWeek": [
{
"dayOfWeek": "MONDAY",
"checked": false
},
{
"dayOfWeek": "TUESDAY",
"checked": false
},
{
"dayOfWeek": "WEDNESDAY",
"checked": true
},
{
"dayOfWeek": "THURSDAY",
"checked": false
},
{
"dayOfWeek": "FRIDAY",
"checked": false
},
{
"dayOfWeek": "SATURDAY",
"checked": true
},
{
"dayOfWeek": "SUNDAY",
"checked": true
}
],
"uuid": "da8f56a2-625f-400d-800d-c975bead0cff",
"taskSchedules": [],
"isInitial": false,
"hasChanged": false
}
In daysOfWeek I want to ensure that at least one of the items has checked: true.
This is my validation schema so far (but not working):
const taskValidationSchema = Yup.object().shape({
subject: Yup.string().required('Required'),
description: Yup.string(),
daysOfWeek: Yup.array()
.of(
Yup.object().shape({
dayOfWeek: Yup.string(),
checked: Yup.boolean(),
})
)
.required('Required'),
taskSchedules: Yup.array(),
})
Is it possible to validate the values of daysOfWeek ensuring that at least one of them has checked: true?

I solved it using compact() (filtering out falsely values) together with setTimeout after the FieldArray modifier function:
const validationSchema = Yup.object().shape({
subject: Yup.string().required(i18n.t('required-field')),
description: Yup.string(),
daysOfWeek: Yup.array()
.of(
Yup.object().shape({
dayOfWeek: Yup.string(),
checked: Yup.boolean(),
})
)
.compact((v) => !v.checked)
.required(i18n.t('required-field')),
taskSchedules: Yup.array(),
});
And in form:
<Checkbox
value={day.dayOfWeek}
checked={day.checked}
onChange={(e) => {
replace(idx, { ...day, checked: !day.checked });
setTimeout(() => {
validateForm();
});
}}
/>;

Base on #olefrank's answer. This code work with me.
const validationSchema = Yup.object().shape({
subject: Yup.string().required(i18n.t('required-field')),
description: Yup.string(),
daysOfWeek: Yup.array()
.of(
Yup.object().shape({
dayOfWeek: Yup.string(),
checked: Yup.boolean(),
})
)
.compact((v) => !v.checked)
.min(1, i18n.t('required-field')), // <– `.min(1)` instead of `.required()`
taskSchedules: Yup.array(),
});

I have done this type of validation in my Node.js(Express.js) project. You can try validation in this way.
const validationSchema = yup.object({
subject: yup.string().required(),
description: yup.string().required(),
daysOfWeek: yup.array(
yup.object({
dayOfWeek: yup.string().required(),
checked: yup.boolean().required()
})
)
})

If you trying in latest version it should be used like this
Yup.array().min(1, "At least one option is required").required()

In my case I have used formik with yup for this,
I want to select only one value in an array if not selected I need to display the error
array = [{"label": "Option 1", "selected": false, "value": "option-1"}, {"label": "Option 2", "selected": false, "value": "option-2"}, {"label": "Option 3", "selected": false, "value": "option-3"}]
Yup.mixed().test({
message: 'Required',test:
val => val.filter(i => i.selected === true).length === 1})
it worked for me
Yup.mixed().test({
message: 'Required',test:
val => val.filter(i => i.selected === true).length !== 0})

Related

JSGRID - Excel-like keyboard navigation

I have a jsgrid gride and I want to activate edit mode in next line after hit enter key (or arrow down key) in "inline edition" and focus on the same column but next line, as Excel -like keyboard navigation.
The only achievement is to fire update with enter key but how to activate edition in the next line in the same column?
var clients = [
{ "Name": "Otto Clay", "Age": 25, "Country": 1, "Address": "Ap #897-1459 Quam Avenue", "Married": false },
{ "Name": "Connor Johnston", "Age": 45, "Country": 2, "Address": "Ap #370-4647 Dis Av.", "Married": true },
{ "Name": "Lacey Hess", "Age": 29, "Country": 3, "Address": "Ap #365-8835 Integer St.", "Married": false },
{ "Name": "Timothy Henson", "Age": 56, "Country": 1, "Address": "911-5143 Luctus Ave", "Married": true },
{ "Name": "Ramona Benton", "Age": 32, "Country": 3, "Address": "Ap #614-689 Vehicula Street", "Married": false }
];
var countries = [
{ Name: "", Id: 0 },
{ Name: "United States", Id: 1 },
{ Name: "Canada", Id: 2 },
{ Name: "United Kingdom", Id: 3 }
];
$("#jsGrid").jsGrid({
width: "100%",
height: "400px",
inserting: false,
editing: true,
sorting: true,
paging: true,
data: clients,
fields: [
{ name: "Name", type: "text", width: 150, validate: "required" },
{ name: "Age", type: "number", width: 50 },
{ name: "Address", type: "text", width: 200 },
{ name: "Country", type: "select", items: countries, valueField: "Id", textField: "Name" },
{ name: "Married", type: "checkbox", title: "Is Married", sorting: false },
{ type: "control" }
],
onError: function (args) {
console.log("ERR");
console.log(args);
},
});
$('#jsGrid').off().on('keydown', 'input[type=text], input[type=number]', (event) => {
if (event.which === 13) { // Detect enter keypress
$('#jsGrid').jsGrid("updateItem"); // Update the row
}
});
UPDATE: I developed an alternative that use Enter (or arrow down key) con updateitem, activate the edit mode in the next row and focus on the same previous column control. But I think there is an easy way.
var LastIndexInputs = -1;
var LastIndexSelect = -1;
function ActivateEditNextLine(NewIndex) {
var gridBody = $("#jsGrid").find('.jsgrid-grid-body');
gridBody.find('.jsgrid-table tr').each(function (index, tr) {
if (index == NewIndex) {
$(tr).trigger('click');
RowTarget = gridBody.find(".jsgrid-edit-row");
if (LastIndexInputs != -1) {
RowTarget.find('.Campo')[LastIndexInputs].focus();
RowTarget.find('.Campo')[LastIndexInputs].select();
}
else {
if (LastIndexSelect != -1) {
RowTarget.find('select')[LastIndexSelect].focus();
}
}
}
});
};
var clients = [
{ "Name": "Otto Clay", "Age": 25, "Country": 1, "Address": "Ap #897-1459 Quam Avenue", "Married": false },
{ "Name": "Connor Johnston", "Age": 45, "Country": 2, "Address": "Ap #370-4647 Dis Av.", "Married": true },
{ "Name": "Lacey Hess", "Age": 29, "Country": 3, "Address": "Ap #365-8835 Integer St.", "Married": false },
{ "Name": "Timothy Henson", "Age": 56, "Country": 1, "Address": "911-5143 Luctus Ave", "Married": true },
{ "Name": "Ramona Benton", "Age": 32, "Country": 3, "Address": "Ap #614-689 Vehicula Street", "Married": false }
];
var countries = [
{ Name: "", Id: 0 },
{ Name: "United States", Id: 1 },
{ Name: "Canada", Id: 2 },
{ Name: "United Kingdom", Id: 3 }
];
$("#jsGrid").jsGrid({
width: "100%",
height: "400px",
inserting: false,
editing: true,
sorting: true,
paging: true,
data: clients,
fields: [
{ name: "Name", type: "text", width: 150, validate: "required" },
{ name: "Address", type: "text", width: 200 },
{ name: "Country", type: "select", items: countries, valueField: "Id", textField: "Name" },
{ name: "Age", type: "text", width: 50 },
{ name: "Married", type: "checkbox", title: "Is Married", sorting: false },
{ type: "control" },
],
onItemUpdating: function (args) {
console.log("confirm edition");
// pointer to last control index for jump to the same colum
var gridBody = $("#jsGrid").find('.jsgrid-grid-body');
Registro = gridBody.find(".jsgrid-edit-row");
var inputs = Registro.find('.Campo');
LastIndexInputs = inputs.index(inputs.filter(':focus'))
var selects = Registro.find('select');
LastIndexSelect = selects.index(selects.filter(':focus'))
},
onItemUpdated: function (args) {
if (args.grid.data.length != (args.itemIndex + 1)) {
ActivateEditNextLine(args.itemIndex + 1);
}
},
editItem: function (item) {
console.log("Enter in edition mode")
var $row = this.rowByItem(item);
if ($row.length) {
//console.log('$row: ' + JSON.stringify($row));
this._editRow($row);
}
},
onError: function (args) {
console.log("ERR");
console.log(args);
},
});
$('#jsGrid').off().on('keydown', 'input[type!=hidden], select', (event) => {
if (event.which === 13) { // Detect enter keypress
$('#jsGrid').jsGrid("updateItem"); // Update the row
}
if (event.which === 40) { // arrow down keypress
$('#jsGrid').jsGrid("updateItem"); // Update the row
}
});

Is it possible to iterate through a list of objects and do dynamic tests for each input?

I am currently learning cypress.io, and I have 7 checkboxes representing each day of the week Sun-Sat.
{days.map((day, idx) => (
<input
onChange={(e) => {
const { checked, value } = e.target;
setDays(
days => days.map(data => {
if (data.id === day.id) {
return {
...data,
id: value,
select: !data.select,
};
}
return data;
})
);
setDayId(prev => {
return checked
? [...prev, value] // add if checked
: prev.filter(val => val !== value) // remove if not checked
});
console.log("CHECKING CHECKED VALUE", e.target.checked);
// console.log("WHAT IS THE SET VALUE", values)
}}
key={idx}
name={day?.name}
type="checkbox"
value={day?.id}
checked={day.select}
id="habit-frequency"
/>
))}
And I am trying to avoid doing this 7 times, because I am sure that there is a much better way to do it
cy.get("#habit-frequency")
.should("have.attr", "name", "Sun")
.should("have.attr", "value", "1");
I though about doing this:
const DAYS = [
{ id: 1, name: "Sun", select: false },
{ id: 2, name: "Mon", select: false },
{ id: 3, name: "Tue", select: false },
{ id: 4, name: "Wed", select: false },
{ id: 5, name: "Thu", select: false },
{ id: 6, name: "Fri", select: false },
{ id: 7, name: "Sat", select: false },
];
DAYS.map(day => (
it(`Should have a checkbox for ${day.name}`, () => {
cy.get("#habit-frequency")
.should("have.attr", "name", day.name)
.should("have.attr", "value", day.id)
})
))
But it isn't really working. Any advise? Here is the full test I have written so far in case it helps
describe("Create Habit", () => {
beforeEach(() => {
cy.visit("/");
});
it("Should have a 'Habit' button which can be clicked to display a modal", () => {
cy.get("#modal-btn").should("contain", "Habit").click();
cy.get(".modal-title").should("contain", "Add Habit");
});
it("Should have a 'Add Habit' title, 2 inputs, one for name and one for description, a habit type option, a weekly frequency and an option to choose colors", () => {
cy.get("#modal-btn").click();
cy.get(".modal-title").should("contain", "Add Habit");
cy.get("#habit-name")
.should("have.attr", "placeholder", "Habit name");
cy.get("#habit-description")
.should("have.attr", "placeholder", "Habit description");
cy.get("#habit-todo")
.should("have.attr", "name", "To-Do")
.should("have.attr", "value", "1");
cy.get("#habit-nottodo")
.should("have.attr", "name", "Not-To-Do")
.should("have.attr", "value", "2");
DAYS.map(day => (
it(`Should have a checkbox for ${day.name}`, () => {
cy.get("#habit-frequency")
.should("have.attr", "name", day.name)
.should("have.attr", "value", day.id)
})
))
cy.get("#habit-frequency")
.should("have.attr", "name", "Sun")
.should("have.attr", "value", "1");
});
});
The problem you are having is because you are nesting it calls, Never nest them.
just use your code as is without the internal it call
it(`Should have a checkbox for ${day.name}`, () => { /// this should be removed
..
}/// this should be removed

Yup validation rules issues

Just trying to get a handle on Yup and unfortunately I can't seem to find any examples that point to validating a nested object and a nested array (of objects) within another object.
I have something like this:
"books": [{
"info": {
"dateReleased": null,
"timeReleased": null
},
"reviewers": [
{
"company": "",
"name": ""
}
]
}]
I just have no idea what the required Yup validation syntacx is for info and reviewers as all I want to validate is that the values are not null and are required.
I'vetried this but no validation is firing:
Yup.object().shape({
books: Yup.array(
info: Yup.object({
dateReleased: Yup.date().required('Rquired')
timeReleased: Yup.date().required('Required')
})
reviewers: Yup.array(
Yup.object({
company: Yup.string().required('Required')
name: Yup.string().required('Required')
})
)
)
})
With the above, I'm not getting any console errors but none of my validation rules for info and reviewers are firing.
Yup Validation
const value = {
books: [
{
info: {
dateReleased: null,
timeReleased: null,
},
reviewers: [
{
company: "",
name: "",
},
],
},
],
};
const schema = yup.object().shape({
books: yup.array(
yup.object().shape({
info: yup.object().shape({
dateReleased: yup.date().required('Required'),
timeReleased: yup.date().required('Required')
}),
reviewer: yup.array(
yup.object().shape({
company: yup.string().required('Required'),
name: yup.string().required('Required')
})
)
})
),
});
schema.validate(value).catch(err => {
console.log(err.name); // ValidationError
console.log(err.errors); // [books[0].info.timeReleased must be a `date` type, but the final value was: `Invalid Date`.]
});

RadDataForm Multiple select in nativescript-vue

I have this TKEntityProperty:
<TKEntityProperty v-tkDataFormProperty name="groups" displayName="Groups" index="2" :valuesProvider="retrieveGroups">
and this gets values from below object:
retrieveGroups:[
{key: "1", "label": "Group 1"},
{key: "2", "label": "Group 2"},
{key: "3", "label": "Group 3"}
]
but it does not multi select. I want to select multiple elements.
Is there another type of editor available ?
As #Manoj suggested, you should use AutoCompleteInline
Here is an example, it is available at Nativescript github page
data() {
return {
title: description,
booking: new Booking(),
bookingMetadata: {
'isReadOnly': false,
'commitMode': DataFormCommitMode.Immediate,
'validationMode': DataFormValidationMode.Immediate,
'propertyAnnotations': [{
'name': 'from',
'displayName': 'From:',
'index': 0,
'editor': DataFormEditorType.AutoCompleteInline,
'editorParams': {
'autoCompleteDisplayMode': AutoCompleteDisplayMode.Tokens
},
'valuesProvider': fromProviders,
},
{
'name': 'to',
'displayName': 'To:',
'index': 1,
'editor': DataFormEditorType.AutoCompleteInline,
'editorParams': {
'autoCompleteDisplayMode': AutoCompleteDisplayMode.Plain
},
'valuesProvider': ['New York', 'Washington', 'Los Angeles'],
},
]
}
};
},

kendo ui: no Data in Subgrid

i'm working with kendo ui to create rich user interface, but i'm stuck t the following:
I want a Grid with a subgrid, the first one displays customer info, like name, etc the second shows the available shipping-adresses, the customer saved. Now my problem is, that in the subgrid is not data, although firebug shows, that the data was returned. This is my source:
$(function() {
var main = $("#customer-grid").kendoGrid({
dataSource: {
transport:{
read :"data/users.php",
update :{
url :"data/users.php?action=update",
type:"POST",
data: function (data) {
data.birthday = kendo.toString(data.birthday, "yyyy-MM-dd");
return data;
}
},
destroy:{
url :"data/users.php?action=destroy",
type:"POST"
}
},
schema: {
data: "data",
model: {
id: "id",
fields: {
id: {editable: false},
activated: { type: "boolean" },
birthday: {type: "date"},
gender: {defaultValue: "W"}
}
}
}
},
height: 250,
filterable: true,
sortable: true,
pageable: true,
detailInit: detailInit,
dataBound: function() {
this.expandRow(this.tbody.find("tr.k-master-row").first());
},
columns: [{
field: "id",
title: "ID",
width: "50px"
}, {
field: "gender",
title: "Geschlecht",
width: "100px",
values: data
}, {
field: "firstname",
title: "Vorname"
}, {
field: "lastname",
title: "Nachname"
}, {
field: "birthday",
title: "Geburtstag",
width: "100px",
format: "{0:dd.MM.yyyy}"
},{
field: "mail",
title: "E-Mail"
},{
field: "activated",
title: "Aktiviert",
width: "100px",
values: actval
}, {
command: ["edit", "destroy"],
title: " ",
width: "210px"
}],
editable:{
mode: "popup"
}
});
});
function detailInit(e) {
$("<div/>").appendTo(e.detailCell).kendoGrid({
dataSource: {
transport: {
read: "data/adress.php?uid="+e.data.id
},
serverPaging: true,
serverSorting: true,
serverFiltering: true,
pageSize:6
},
scrollable: false,
sortable: true,
pageable: true,
columns: [
{ field: "id", width: 70 },
{ field: "zipcode", title:"Ship Country", width: 100 },
{ field: "city", title:"Ship Address" },
{ field: "street", title: "Ship Name", width: 200 }
]
});
}
The function gets its data from a php page, which does a mysql-query and then gives back the data as application/json. But nothing is shown.
Hopefully you guys can help me.
Regards
I tested your code and it works fine.
Check:
e parameter format. I've used: detailInit({ detailCell : $("#grid"), data : { id : 1}});
JSON returned by data/adress.php. I've returned:
[
{"city":"Madrid", "id":"1", "zipcode":"31000", "street":"my street 1", "number":5},
{"city":"Sofia", "id":"2", "zipcode":"32000", "street":"my street 2", "number":4},
{"city":"London", "id":"3", "zipcode":"33000", "street":"my street 3", "number":3},
{"city":"San Francisco", "id":"4", "zipcode":"34000", "street":"my street 4", "number":2},
{"city":"Berlin", "id":"5", "zipcode":"35000", "street":"my street 5", "number":1}
]
Using the following (hardcoded) php response:
<?php
header('Content-type: application/json');
$named_array = array(
array("city" => "Madrid", "id" => "1", "zipcode" => "31000", "street" => "my street 1", "number" => 5),
array("city" => "Sofia", "id" => "2", "zipcode" => "32000", "street" => "my street 2", "number" => 4),
array("city" => "London", "id" => "3", "zipcode" => "33000", "street" => "my street 3", "number" => 3),
array("city" => "San Francisco", "id" => "4", "zipcode" => "34000", "street" => "my street 4", "number" => 2),
array("city" => "Berlin", "id" => "5", "zipcode" => "35000", "street" => "my street 5", "number" => 1)
);
echo json_encode($named_array);
?>
EDIT: This is the definition of the grid containing the subgrid.
var outer = $("#outer").kendoGrid({
dataSource:{
type:"json",
transport:{
read:"names.php"
},
pageSize:5
},
sortable:true,
columns:[
{ field:"id", width:70 },
{ field:"name", title:"Name", width:100 },
{ field:"lastname", title:"LastName", width:100 },
{ title:"Address", width:300, attributes:{ class:"ob-address"}}
]
}).data("kendoGrid");
and then on a button click, I run:
$(".ob-address", outer.tbody).each(function (idx, elem) {
detailInit({ detailCell:elem, data:{ id:1}});
});
As you can see I marked a column in the outer grid with a CSS class named ob-address and the function selects all the cell in the body of the outer table for inserting the inner table (subgrid).

Resources