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
}
Related
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
I am trying to look up record using if I have the key then use Find if not use Where
private ApplicationDbContext db = new ApplicationDbContext();
public bool DeactivatePrice(int priceId = 0, string sponsorUserName = "")
{
var prices = db.BeveragePrices;
// if we have an id then find
if (priceId != 0)
{
prices = prices.Find(priceId);
}
else
{
prices = prices.Where(b => b.UserCreated == sponsorUserName);
}
if (prices != null)
{
// do something
}
return true;
I get the following error for
prices = prices.Find(priceId);
Cannot convert app.Model.BeveragePrices from system.data.entity.dbset
I am copying the pattern from this answer but something must be different.
Seems you forgot to put a predicate inside the Find function call. Also you need to do ToList on the collection. The second option is a lot more efficient. The first one gets the whole collection before selection.
Another note commented by #Alla is that the find returns a single element. So I assume another declaration had been made for 'price' in the first option I state down here.
price = prices.ToList.Find(b => b.PriceId == priceId);
Or
prices = prices.Select(b => b.PriceId == priceId);
I assume the field name is PriceId.
I am trying to find a specific element from my page using ExtJS 4 so I can do modifications on it.
I know its id so it should not be a problem BUT
-I tried Ext.getCmp('theId') and it just return me undefined
-I tried to use down('theId') method by passing through the view and I still get a nullresponse.
As I know the id of the element I tried again the two methods by setting manually the id and it didn't work neither.
Do these two methods not function?
How should I do?
Here is the concerned part of the code :
listeners: {
load: function(node, records, successful, eOpts) {
var ownertree = records.store.ownerTree;
var boundView = ownertree.dockedItems.items[1].view.id;
var generalId = boundView+'-record-';
// Add row stripping on leaf nodes when a node is expanded
//
// Adding the same feature to the whole tree instead of leaf nodes
// would not be much more complicated but it would require iterating
// the whole tree starting with the root node to build a list of
// all visible nodes. The same function would need to be called
// on expand, collapse, append, insert, remove and load events.
if (!node.tree.root.data.leaf) {
// Process each child node
node.tree.root.cascadeBy(function(currentChild) {
// Process only leaf
if (currentChild.data.leaf) {
var nodeId = ""+generalId+currentChild.internalId;
var index = currentChild.data.index;
if ((index % 2) == 0) {
// even node
currentChild.data.cls.replace('tree-odd-node', '')
currentChild.data.cls = 'tree-even-node';
} else {
// odd node
currentChild.data.cls.replace('tree-even-node', '')
currentChild.data.cls = 'tree-odd-node';
}
// Update CSS classes
currentChild.triggerUIUpdate();
console.log(nodeId);
console.log(ownertree.view.body);
console.log(Ext.getCmp(nodeId));
console.log(Ext.getCmp('treeview-1016-record-02001001'));
console.log(ownertree.view.body.down(nodeId));
console.log(ownertree.view.body.down('treeview-1016-record-02001001'));
}
});
}
}
You can see my console.log at the end.
Here is what they give me on the javascript console (in the right order):
treeview-1016-record-02001001
The precise id I am looking for. And I also try manually in case...
h {dom: table#treeview-1016-table.x-treeview-1016-table x-grid-table, el: h, id: "treeview-1016gridBody", $cache: Object, lastBox: Object…}
I checked every configs of this item and its dom and it is exactly the part of the dom I am looking for, which is the view containing my tree. The BIG parent
And then:
undefined
undefined
null
null
Here is the item I want to access:
<tr role="row" id="treeview-1016-record-02001001" ...>
And I checked there is no id duplication anywhere...
I asked someone else who told me these methods do not work. The problem is I need to access this item to modify its cls.
I would appreciate any idea.
You are looking for Ext.get(id). Ext.getCmp(id) is used for Ext.Components, and Ext.get(id) is used for Ext.dom.Elements. See the docs here: http://docs.sencha.com/extjs/4.2.1/#!/api/Ext-method-get
Ok so finally I used the afteritemexpand listener. With the ids I get the elements I am looking for with your Ext.get(id) method kevhender :).
The reason is that the dom elements where not completely loaded when I used my load listener (it was just the store) so the Ext.get(id) method couldn't get the the element correctly. I first used afterlayout listener, that was correct but too often called and the access to the id was not so easy.
So, here is how I did finally :
listeners: {
load: function(node, records, successful, eOpts) {
var ownertree = records.store.ownerTree;
var boundView = ownertree.dockedItems.items[1].view.id;
var generalId = boundView+'-record-';
if (!node.tree.root.data.leaf) {
// Process each child node
node.tree.root.cascadeBy(function(currentChild) {
// Process only leaf
if (currentChild.data.leaf) {
var nodeId = ""+generalId+currentChild.internalId;
var index = currentChild.data.index;
if ( (index % 2) == 0 && ids.indexOf(nodeId) == -1 ) {
ids[indiceIds] = nodeId;
indiceIds++;
}
console.log(ids);
}
});
}
},
afteritemexpand: function( node, index, item, eOpts ){
/* This commented section below could replace the load but the load is done during store loading while afteritemexpand is done after expanding an item.
So, load listener makes saving time AND makes loading time constant. That is not the case if we just consider the commented section below because
the more you expand nodes, the more items it will have to get and so loading time is more and more important
*/
// var domLeaf = Ext.get(item.id).next();
// for ( var int = 0; int < node.childNodes.length; int++) {
// if (node.childNodes[int].data.leaf && (int % 2) == 0) {
// if (ids.indexOf(domLeaf.id) == -1) {
// ids[indiceIds] = domLeaf.id;
// indiceIds++;
// }
// }
// domLeaf = domLeaf.next();
// }
for ( var int = 0; int < ids.length; int++) {
domLeaf = Ext.get(ids[int]);
if (domLeaf != null) {
for ( var int2 = 0; int2 < domLeaf.dom.children.length; int2++) {
if (domLeaf.dom.children[int2].className.search('tree-even-node') == -1){
domLeaf.dom.children[int2].className += ' tree-even-node';
}
}
}
}
},
With ids an Array of the ids I need to set the class.
Thank you for the method.
I have a enumeration of objects :
public IOrderedEnumerable<RentContract> Contracts {
get { return RentContracts.OrderByDescending(rc => rc.DateCreated); }
}
I have to compare a given RentContract instance with its previous RenContract instance on the list to highlight changes between the two objects, which is the most correct method to get the previous element ?
This is not possible directly. You can do it like this:
var input = new SomeClass[10]; //test data
var zipped = input.Zip(new SomeClass[1].Concat(input), (a, b) => { a, b });
var result = zipped.Where(x => x.b == null || x.a.DateCreated < x.b.DateCreated.AddHours(-1)); //some example
This solution is zipping the sequence with itself, but offset by one null element.
I'm writing a tinyMce plugin which contains a section of code, replacing one element for another. I'm using the editor's dom instance to create the node I want to insert, and I'm using the same instance to do the replacement.
My code is as follows:
var nodeData =
{
"data-widgetId": data.widget.widgetKey(),
"data-instanceKey": "instance1",
src: "/content/images/icon48/cog.png",
class: "widgetPlaceholder",
title: data.widget.getInfo().name
};
var nodeToInsert = ed.dom.create("img", nodeData);
// Insert this content into the editor window
if (data.mode == 'add') {
tinymce.DOM.add(ed.getBody(), nodeToInsert);
}
else if (data.mode == 'edit' && data.selected != null) {
var instanceKey = $(data.selected).attr("data-instancekey");
var elementToReplace = tinymce.DOM.select("[data-instancekey=" + instanceKey + "]");
if (elementToReplace.length === 1) {
ed.dom.replace(elementToReplace[0], nodeToInsert);
}
else {
throw new "No element to replace with that instance key";
}
}
TinyMCE breaks during the replace, here:
replace : function(n, o, k) {
var t = this;
if (is(o, 'array'))
n = n.cloneNode(true);
return t.run(o, function(o) {
if (k) {
each(tinymce.grep(o.childNodes), function(c) {
n.appendChild(c);
});
}
return o.parentNode.replaceChild(n, o);
});
},
..with the error Cannot call method 'replaceChild' of null.
I've verified that the two argument's being passed into replace() are not null and that their parentNode fields are instantiated. I've also taken care to make sure that the elements are being created and replace using the same document instance (I understand I.E has an issue with this).
I've done all this development in Google Chrome, but I receive the same errors in Firefox 4 and IE8 also. Has anyone else come across this?
Thanks in advance
As it turns out, I was simply passing in the arguments in the wrong order. I should have been passing the node I wanted to insert first, and the node I wanted to replace second.