AG Grid: Better way for validation row - valueSetter? - ag-grid

Is there a better way to validate a row in ag-grid than with valueSetter?
I can achieve the validation with that but I am not sure, if there is a better way.
https://www.ag-grid.com/javascript-grid-value-setters/#properties-for-setters-and-parsers
I want to validate two fields in the row. DateFrom and DateUntil (they are not allow to be null and DateFrom must be lower than DateUntil).

There are two ways of possible validation handling:
First: via ValueSetter function
and
Second: via custom cellEditor component
I suggest that it would be better to split the logic between custom components, but as you said you need to validate two cell-values between themselves.
On this case from UI perspective you could try to combine them inside one cell and it would be easily to work with values via one component only.

You could override the valueSetter and call the grid api transaction update instead.
Here is pseudo-code that shows how you could implement this.
valueSetter: params => {
validate(params.newValue, onSuccess, onFail);
return false;
};
validate = (newvalue, success, fail) => {
if (isValid(newValue)) {
success();
} else {
fail();
}
};
onSuccess = () => {
// do transaction update to update the cell with the new value
};
onFail = () => {
// update some meta data property that highlights the cell signalling that the value has failed to validate
};
This way you can also do asynchronous validation.
Here is a real example of an async value setter that has success, failure, and while validating handlers that do transaction updates when validation is done.
const asyncValidator = (
newValue,
validateFn,
onWhileValidating,
onSuccess,
_onFail
) => {
onWhileValidating();
setTimeout(function() {
if (validateFn(newValue)) {
onSuccess();
} else {
_onFail();
}
}, 1000);
};
const _onWhileValidating = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: true
};
params.api.applyTransaction({ update: [data] });
};
const _onSuccess = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: false,
lastValidation: true,
value: params.newValue
};
params.api.applyTransaction({ update: [data] });
};
const _onFail = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: false,
lastValidation: params.newValue
};
params.api.applyTransaction({ update: [data] });
};
const asyncValidateValueSetter = validateFn => params => {
asyncValidator(
params.newValue,
validateFn,
_onWhileValidating(params),
_onSuccess(params),
_onFail(params)
);
return false;
};
Here is a code runner example showing this in action: https://stackblitz.com/edit/async-validation-ag-grid-final

Have a look at this two snippets, these come from our internal knowledge base (accessible to customers)
When editing a value in column 'A (Required)', you will see that it does not allow you to leave it empty. If you leave it empty and return the edit, it will be cancelled.
//Force Cell to require a value when finished editing
https://plnkr.co/edit/GFgb4v7P8YCW1PxJwGTx?p=preview
In this example, we are using a Custom Cell Editor that will also validate the values against a 6 character length rule. While editing, if the value is modified outside of 6 characters, it will appear in red, and when you stop editing the row, the value would be reset, so it only accepts a complete edit if the value is valid.
//Inline Validation while editing a cell
https://plnkr.co/edit/dAAU8yLMnR8dm4vNEa9T?p=preview

Related

AG-Grid: updateRowData({update: ItemsArray}) affecting all rows instead of selected rows

I'm using Ag-Grid and Angular for this project, and what I'm trying to do is set up shortcut keys to change the 'Status' value of selected rows by pressing associated keys('1'= 'status complete', etc.). There is a built in function called onCellKeyPress() that listens for keystrokes after a row, or rows, has been selected. That's working great, I have a switch case that sends off a value depending on which key is pressed like so:
onCellKeyPress = (e: any) => {
switch(e.event.key) {
case '0': this.rowUpdates('New'); break;
case '1': this.rowUpdates('Completed'); break;
case '2': this.rowUpdates('Needs Attention'); break;
case '3': this.rowUpdates('Rejected'); break;
}
}
It sends a string to my custom function rowUpdates(), that takes the value, goes though the existing Nodes, looks for any that are selected, sets the value on those selected, and pushes them to an array.
Now here's where the trouble starts. updateRowData takes 2 params, first is the type of updating it's doing(add, remove, update), in my case I'm using the latter, and an array of rows to change.
rowUpdates = (value: String) => {
let itemsToUpdate = [];
this.gridOptions.api.forEachNode(rowNode => {
if(rowNode.isSelected() === true) {
const selected = rowNode.data;
selected.status.name = value;
itemsToUpdate.push(selected);
console.log(itemsToUpdate);
}
});
this.gridOptions.api.updateRowData({update: itemsToUpdate});
}
However when I press a key to change the row value it updates every row in my grid. What's extra strange is that I have a method that adds a class to the row depending on the 'Status' value, and only the rows I intended to change receive that class.
I'm stumped. I've console.logged everything in this function and they all return with their intended values. The array always contains the nodes that have been selected and the return value of updateRowData is always that same array. I've tried switching out 'forEachNode' with getSelectedNodes and getSelectedRows to no avail. Any ideas?
Please try this.
updateItems(value: String) {
var itemsToUpdate = [];
this.gridApi.forEachNodeAfterFilterAndSort(function(rowNode, index) {
if (!rowNode.selected) {
return;
}
var data = rowNode.data;
data.status.name = value;
itemsToUpdate.push(data);
});
var res = this.gridApi.updateRowData({ update: itemsToUpdate });
this.gridApi.deselectAll();//optional
}

SAPUI5 oData POST 500 error

I'm trying to do a oData create on checkbox pressed and getting the following errors. Not sure if this is front end or a back end ABAP issue as have got this same function working in another project.
It is failing on the create part but strangely is still passing through the details for SiteId, ArticleNumber, VarianceDate & Confirmed.
// Set CheckBox status, X for true, blank for false
onVarianceChecked: function (oEvent) {
var oEntry = {};
var bindingContext = oEvent.getSource().getBindingContext(this.MODEL_VIEW);
var path = bindingContext.getPath();
var object = bindingContext.getModel("SI").getProperty(path);
// Pass in the Header fields
oEntry.SiteId = this.SiteId;
oEntry.ArticleNumber = object.ArticleNumber;
oEntry.VarianceDate = moment(new Date(object.VarianceDate)).format('YYYY-MM-DDTHH:mm:ss');
// Set X or blank
if (oEvent.getParameter("selected") === true) {
oEntry.Confirmed = "X";
} else {
oEntry.Confirmed = "";
}
// Do the create
var oModel = this.getView().getModel("SI");
oModel.create("/VarianceHeaderSet", oEntry, {
success: function () {
console.log("Variance confirmed");
MessageToast.show("Variance confirmed", {
duration: 1000
});
},
error: function (oError) {
console.log("Error, variance could not be confirmed");
MessageToast.show("Error, variance could not be confirmed", {
duration: 1000
});
}
});
}
'000000000' is the initial value for Edm.DateTime, hence it will fail when you have modelled a DateTime property to not be nullable.
Go to SEGW and change the property to "nullable" or make sure that you always provide a correct Date in the POST.

Dynamically set form field values in React + Redux

My app's store has a store.authState subtree. In this subtree, there are three things, an authToken, an isFetching boolean and, most importantly a fields object. My form contains two fields : username and password.
I have created an action called SET_FORM_FIELD_VALUE which should generate and update each field's state as they are changed by the user.
I would like my SET_FORM_FIELD_VALUE to update the fields object. If a user normally fills in both username and password fields, my store.authState should look like this:
{
authToken: undefined,
isFetching: false,
fields: {
username: "Brachamul",
password: "azerty123"
}
}
However, it seems that my current code actually just overwrites everything and I end up with :
{
field: {
username: "Brachamul"
}
}
The data in the field changes based on what field I last edited. It's either username or password.
Here is my code :
switch (action.type) {
case 'SET_FORM_FIELD_VALUE':
let field = {} // create a "field" object that will represent the current field
field[action.fieldName] = action.fieldValue // give it the name and value of the current field
return { ...state.fields, field }
How can I change it to fix my issue ?
Your return is wrong, it should be something like this
switch (action.type) {
case 'SET_FORM_FIELD_VALUE':
return {
...state,
fields: {
...state.fields,
[action.fieldName] : action.fieldValue
}
}
}
Hope it helps.
i used change() from 'redux-form'
which only re rendered that specific form input, and isued it pretty often.
everytime the user clicked a dropdown menu it suggested values in 2 input fields
i abstracted away the html from the anwser and some other stuff.
import { FieldArray, Field, change, reduxForm } from 'redux-form';
class WizardFormThirdPage extends react.component{
runInject(target,value){
target.value= value; // sets the client html to the value
// change (formName, Fieldname, Value) in the state
this.props.dispatch(change('spray-record', target.name, value))
}
injectFormvalues(){
var tr = div.querySelector(".applicator-equipment");
var name = div.querySelector("select").value;
if(!name.includes("Select Employee")){
// var inputs = tr.querySelectorAll("input");
var employeeDoc= findApplicatorByName(name); // synchronous call to get info
tractor = tr.querySelector("input")
sprayer = tr.querySelectorAll("input")[1];
// here i send off the change attribute
this.runInject(tractor,Number(employeeDoc.default_t))
this.runInject(sprayer,Number(employeeDoc.default_s));
}
}
// you have to connect it get the dispatch event.
export default connect(state => ({
enableReinitialize: true,
}))(reduxForm({
form: "myFormName", // <------ same form name
destroyOnUnmount: false, // <------ preserve form dataLabel
forceUnregisterOnUnmount: true, // <------ unregister fields on unmount
validate,
})(WizardFormThirdPage));

Assign mongo selectors in find() dynamically

I have the following problem: I have an interface where a user can filter stuff out based on several inputs. There are 5 inputs. When an input is filled out I want to add it's value to the helper returning the collection. The problem I can't solve is how to do this dynamically. Sometimes the user might fill out one input, sometimes three, sometimes all 5. Within the find() method you can only write down meteor's syntax:
mongoSelector: fieldName,
This means you can only hardcode stuff within find(). But just adding all 5 selectors doesn't work, since if one of the values is empty, the find searches for an empty string instead of nothing.
I thought of doing conditionals or variables but both don't work within find because of the required syntax. What could I do to solve this?
var visitorName;
var visitorAge;
Session.set('visitorName', visitorName);
Session.set('visitorAge', visitorAgee);
Template.web.helpers({
visitors: function() {
return Visitors.find({ visitor_name: Session.get('visitorName'), visitor_age: Session.get('visitorAge') });
}
});
Template.web.events({
"change #visitor_name": function (event, template) {
visitorName = $(event.currentTarget).val();
}
});
Template.web.events({
"click #reset_filter": function (event, template) {
return Visitors.find();
$(input).val('');
}
});
http://jsfiddle.net/m5qxoh3b/
This one works
Template.web.helpers({
visitors: function() {
var query = {};
var visitorName = (Session.get('visitorName') || "").trim();
if (visitorName) {
query["visitor_name"] = visitorName;
}
//same thing for other fields
return Visitors.find(query);
}
});
Template.web.events({
"change #visitor_name": function (event, template) {
var visitorName = $(event.currentTarget).val();
Session.set('visitorName', visitorName);
}
});

Does Mongoose provide access to previous value of property in pre('save')?

I'd like to compare the new/incoming value of a property with the previous value of that property (what is currently saved in the db) within a pre('save') middleware.
Does Mongoose provide a facility for doing this?
The accepted answer works very nicely. An alternative syntax can also be used, with the setter inline with the Schema definition:
var Person = new mongoose.Schema({
name: {
type: String,
set: function(name) {
this._previousName = this.name;
return name;
}
});
Person.pre('save', function (next) {
var previousName = this._previousName;
if(someCondition) {
...
}
next();
});
Mongoose allows you to configure custom setters in which you do the comparison. pre('save') by itself won't give you what you need, but together:
schema.path('name').set(function (newVal) {
var originalVal = this.name;
if (someThing) {
this._customState = true;
}
});
schema.pre('save', function (next) {
if (this._customState) {
...
}
next();
})
I was looking for a solution to detect changes in any one of multiple fields. Since it looks like you can't create a setter for the full schema, I used a virtual property. I'm only updating records in a few places so this is a fairly efficient solution for that kind of situation:
Person.virtual('previousDoc').get(function() {
return this._previousDoc;
}).set(function(value) {
this._previousDoc = value;
});
Let's say your Person moves and you need to update his address:
const person = await Person.findOne({firstName: "John", lastName: "Doe"});
person.previousDoc = person.toObject(); // create a deep copy of the previous doc
person.address = "123 Stack Road";
person.city = "Overflow";
person.state = "CA";
person.save();
Then in your pre hooks, you would just need to reference properties of _previousDoc such as:
// fallback to empty object in case you don't always want to check the previous state
const previous = this._previousDoc || {};
if (this.address !== previous.address) {
// do something
}
// you could also assign custom properties to _previousDoc that are not in your schema to allow further customization
if (previous.userAddressChange) {
} else if (previous.adminAddressChange) {
}
Honestly, I tried the solutions posted here, but I had to create a function that would store the old values in an array, save the values, and then see the difference.
// Stores all of the old values of the instance into oldValues
const oldValues = {};
for (let key of Object.keys(input)) {
if (self[key] != undefined) {
oldValues[key] = self[key].toString();
}
// Saves the input values to the instance
self[key] = input[key];
}
yield self.save();
for (let key of Object.keys(newValues)) {
if (oldValues[key] != newValues[key]) {
// Do what you need to do
}
}
What I do is use this.constructor within the pre-save route to access the current value in the database.
const oldData = this.constructor.findById(this.id)
You can then grab the specific key you're looking for from the oldData to work with as you see fit :)
let name = oldData.name
Note that this works well for simple data such as strings, but I have found that it does not work well for subschema, as mongoose has built in functionality that runs first. Thus, sometimes your oldData will match your newData for a subschema. This can be resolved by giving it it's own pre-save route!