After a lot of search in SO without any particular solution, I am compelled to ask this question.
In Simple words - I want to collapse or hide a specific row using Javascript in ag-grid. I have tried several methods explained in ag-grid documentation and also in SO, but none has worked till now.
All the following methods have been tried and none of the codes worked.
Let rowNode = gridOptions.api.getRowNode(params.value);
Method #1. params.api.getDisplayedRowAtIndex(2).setExpanded(false);
Method #2. params.api.getRowNode(params.value).setExpanded(false);
Method #3. gridOptions.api.setRowNodeExpanded(rowNode,false);
Method #4. gridOptions.api.getRowNode(rowId).style.visibility = "collapse";
I have also tried using plain CSS, like this - Data has disappeared but the white blank row is visible
rowNode.setDataValue('class', 'hidden'); //Where “class” is a field
const gridOptions = {
//Other grid options...
getRowClass: params => {
if (params.data.class === "hidden") {
return 'hidden';
}
},
https://stackblitz.com/edit/js-nvtqhz?file=infoCellRenderer.js
setExpand / setRowNode Expanded only works on collapsible rows, i.e it will collapse an expanded row. it will not hide it.
I edited your stackblitz,
I made a couple of changes to make it work.
Selectable Rows
So, when you click a row, I'm marking it as selected. There is a property on ag-grid rowSelection: 'single' | 'multiple. If you want to hide only one row at a time, use 'single' if you can hide multiple rows use 'multiple'
External filtering
So, ag grid can filters rows if we provide a criteria.It can be a check on any of data property as well. For your problem, I have added a filter that says if any row is selected, remove it from the grid.
Following are the changes
/// method called on clicking the button
function hideRow(params) {
let rowNode = gridOptions.api.getRowNode(params.value); // get the clicked row
rowNode.setSelected(true); //mark as selected
gridOptions.api.onFilterChanged(); // trigger filter change
}
Triggering the filter change will call this method for each row
function doesExternalFilterPass(node) {
return !node.selected; // if row node is selected dont show it on grid.
}
You can access the rows hidden any time using
gridOptions.api.getSelectedRows() //Returns an array of data from the selected rows. OR
gridOptions.api.getSelectedNodes() //Returns an array of the selected nodes.
And, if you want to show a row again, just filter from this above mentioned method and do these steps
rowNode.setSelected(false); //mark as unselected
gridOptions.api.onFilterChanged(); // trigger filter change
This will automatically show the row on grid.
Hope this helps! :)
I have a form button in my web app, (purely made with Vanilla Js), The form is used to get data from user and when button is clicked, that data goes to a HTML table and so on.
To edit Table Row, I have placed Edit Button on the table column (at every row), image attached below would help to get clear idea:
The Blue Submit Button is Named as "addButton" , when the user clicks "Edit" of particular row in table, corresponding data is Shown In Input Field, So user can fill in new details.
Up to here is everything fine, now the real problem begins: After user edits the info in Input fields, Blue Submit button is used for saving those changes and spontaneously show change in table as well. (this is the required behaviour).
Instead what is happening is : The modified row is shown in table as well as a new entry is made in row with same details ("addButton" event listener is executed twice, once for saving changes done by user, and once for adding new item in row).
I have placed this event listener (same name, because I do not want separate buttons for saving edited info) twice.
For clarity, please look at code:
(this is first addButton event listener in global scope - used for adding new entry to table)
addButton.addEventListener("click", function(e){
var obj = {
id : object.count,
date : formatDate(inDate.value, inMonth.value),
day : inDay.value,
item : inItem.value,
price : parseInt(inPrice.value),
};
object.info.push(obj);
object.count += 1;
refreshDOM();
});
For "Edit and Delete" buttons in row, I have this:
modifyBtn is tied to table, so I can get click target with that.
(Inside modifyBtn second addButton event listener is present - used for saving changes and show output in table)
modifyBtn.addEventListener('click', function(e){
if(e.target.classList[0] === 'editInfo'){
var eid = e.target.getAttribute('data-id');
var eindex = getIndex(eid);
inDay.value = object.info[eindex]['day'];
inDate.value = parseInt(object.info[eindex]['date'].substr(0,2));
inMonth.value = object.info[eindex]["date"].substr(4);
inItem.value = object.info[eindex]['item'];
inPrice.value = object.info[eindex]['price'];
addButton.addEventListener("click", function(e){
object.info[eindex]['day'] = inDay.value;
object.info[eindex]['date'] = formatDate(inDate.value, inMonth.value);
object.info[eindex]['item'] = inItem.value;
object.info[eindex]['price'] = parseInt(inPrice.value);
refreshDOM();
});
}
if(e.target.classList[0] === 'deleteInfo'){
var did = e.target.getAttribute('data-id');
var dindex = getIndex(did);
console.log(dindex);
object.info.splice(dindex,1);
refreshDOM();
}
});
So after editing when user clicks blue submit button, I want only that addButton event listener to execute which is inside modifyBtn event listener,
Currently both addButton event listeners are getting executed. Sorry for Huge explanation.
The issue, as you're likely aware, is that you're assigning multiple click listeners to the same element (a behaviour that you want) at the same time (a behaviour you don't want). There are a couple of ways you could fix this.
The fastest fix
The quickest way to solve your problem would be to use .removeEventListener(). With this, you can remove the previous click listener (the one that adds a new element to the info array) before you create the second click listener that sets the edited data.
At the end of the second click listener function, you would rebind the first click listener again so the behaviour returned to normal.
Pros of this solution
It's the fastest way to solve the problem you're having
Cons of this solution
Unbinding and rebinding event listeners can make it hard to reason about your code
In order to remove an event listener, you need to supply the original function (like .removeEventListener("click", listenerFunction). As you are currently using an anonymous function expression, you'll have to move the functions currently inside click listeners elsewhere and name them (so that you can pass them to the removeEventListener function
It's not actually clear which event listener is bound to addButton at any one time
The solution
We need to move the function declaration for addButton outside of .addEventListener and give it a name. I've called it addItemClickHandler but you can call it whatever you want:
function addItemClickHandler(e) {
var obj = {
id : object.count,
date : formatDate(inDate.value, inMonth.value),
day : inDay.value,
item : inItem.value,
price : parseInt(inPrice.value),
};
object.info.push(obj);
object.count += 1;
refreshDOM();
}
addButton.addEventListener("click", addItemClickHandler);
This should work exactly the same. Now we need to move the second event listener function you're trying to add into its own named function. As we're only referring to the name of the function from inside itself, we don't even need to move it out, just give it a name. I'm going to give it editItemClickHandler:
addButton.removeEventListener("click", addItemClickHandler);
addButton.addEventListener("click", function editItemClickHandler(e){
object.info[eindex]['day'] = inDay.value;
object.info[eindex]['date'] = formatDate(inDate.value, inMonth.value);
object.info[eindex]['item'] = inItem.value;
object.info[eindex]['price'] = parseInt(inPrice.value);
refreshDOM();
addButton.removeEventListener("click", editItemClickHandler);
addButton.addEventListener("click", addItemClickHandler);
});
As you can see, we first remove the listener addItemClickHandler so that when you click on the "Add" button, it won't do anything
We then bind a different click listener, that we give the name editItemClickHandler so we can remove it after the edit is complete
We do all the edits we need to do
Finally, we remove the new edit click listener we created and re-add the original click listener, so the functionality goes back to normal
The more robust fix
Here is a codepen of your application after applying the following fixes.
The above solution is the fastest way to fix your problem, but there are more robust ways to ensure a working solution. In this solution, I'm going to tidy up some of your code in order to make it cleaner and easier to understand.
Pros of this solution
We won't have to unbind or rebind any click listeners
It's easier to reason about what's happening
Cons of this solution
It'll take longer to implement, as it requires restructuring more of your code
The solution
Step 1: Keep track of whether we're editing or not
Firstly, as we're not rebinding click listeners, we need to keep track of what we're editing. Let's create an object called editing just below object:
var editing = {
mode: false,
index: null
};
This will let us keep track of whether or not we're editing anything (editing.mode), and what the index of the item we're editing is (editing.index).
Step 2: Update the addButton event listener to use the editing object
Next, we need to modify our addButton.addEventListener to use this new editing object:
addButton.addEventListener("click", function(e){
if (editing.mode) {
var info = object.info[editing.index];
info['day'] = inDay.value;
info['date'] = formatDate(inDate.value, inMonth.value);
info['item'] = inItem.value;
info['price'] = parseInt(inPrice.value);
editing.mode = false;
} else {
var obj = {
id : object.count,
date : formatDate(inDate.value, inMonth.value),
day : inDay.value,
item : inItem.value,
price : parseInt(inPrice.value),
};
object.info.push(obj);
object.count += 1;
}
refreshDOM();
});
If editing.mode is true, when the addButton is clicked, it will update the values and then disable editing.mode, putting it back to the way it was before
If editing.mode is false, it will simply add the item to the array (same code as you had before)
No matter what happens, the DOM will get refreshed
Step 3: Update the modifyBtn event listener to use the editing object
Also, I've noticed you're using classes to modify programmatic behaviour instead of data- attributes. This is fine in a lot of cases, but for your exact use case of determining the behaviour, it's recommended to use data- attributes instead. We should also set the href's to #, as we don't need to use them as links:
<td>Edit | Delete</td>
Now, I've restructured your modifyBtn.addEventListener() to handle a these aspects differently:
modifyBtn.addEventListener('click', function(e){
e.preventDefault();
var el = e.target;
var id = parseInt(el.dataset.id);
var index = object.info.findIndex(item => item.id === id);
var info = object.info[index];
var action = el.dataset.action;
if (action === "edit") {
editing.mode = true;
editing.index = index;
inDay.value = info['day'];
inDate.value = parseInt(info['date'].substr(0,2));
inMonth.value = info["date"].substr(4);
inItem.value = info['item'];
inPrice.value = info['price'];
}
if (action === "delete") {
object.info.splice(index, 1);
}
refreshDOM();
});
Using e.preventDefault() means that the browser wont navigate to the # href when clicking on the link
I've moved duplicate code where it retrieved eid and eindex no matter what action you were performing ('editing' or 'adding') outside of those functions
As we are now editing something, we set the editing object to have enabled: true and whatever the index of the current item we're editing is
Instead of using object.info[eindex] every time, I've assigned it to a variable
You no longer need to keep your getIndex function, as you can use Array.prototype.findIndex() instead
The recommended way to get data- attributes is to use element.dataset.name instead of element.getAttribute()
As is standard now, no matter what happens the DOM will get refreshed
Step 4: Add a dynamic form header
Alright, awesome! So this totally works. One final thing I'd like to do is make it more clear what is happening, so in your index.html under your <div class="form-modal">, I'm going to add an ID to your h2:
<h2 id="form-header">Add Item</h2>
Then, back at the top of index.js:
formHeader = getElement('form-header');
And then finally in refreshDOM():
formHeader.textContent = editing.mode ? "Edit Item" : "Add Item";
This will update the text inside <h2 id="form-header"></h2> to depending on whether or not it's being edited. This is a ternary operator, and is often used as a quick way to choose different outcomes depending on a boolean variable.
I hope this wasn't too much information. I've spent a while looking at your code and really wanted to help with best practices and such! Let me know if you have any questions!
I need to edit two columns of my grid using combos as editors, and I need the values shown in the second to be filtered accordingly to the value selected in the first one.
There is also the problem that I need to show the "bound" value (i.e. "description") instead of the Id, in the grid cell.
I prepared a (very simplified) fiddle to show the problem here
Click here for the fiddle
Looking at the fiddle, I'd need to select the brand in the first combo and then a model in the second, but I should obviously find only the models from the selected brand in there.
How can I show the descriptive text in the cell?
How can I filter the second combo?
Thanks
The editing plugin has an beforeedit event you can use, for example:
listeners: {
beforeedit: function(editor, context) {
var record = context.record;
if (context.field !== 'modelId') {
return;
}
models.clearFilter(true);
models.filter({
property: 'brandId',
value: record.getId()
});
}
}
Working example: https://fiddle.sencha.com/#fiddle/12hn
I have a table with a button that adds a row to the bottom by cloning the last row. Each row contains a <select> with a different name (0-9), all <select> objects have the .variable_priority class. When pressing the button, the row gets copied successfully and I manage to change the name correctly (10, 11, 12, etc.), and the row gets the same class since it is a perfect copy.
However, when I execute the following function, I encounter problems with cloned table rows:
$('.variable_priority').change(function() {
var selections = new Array();
$(".variable_priority").each(function() {
selections.push($(this).find('option:selected').val());
});
// Iterating on Options of Select
$('.variable_priority').children('option').each(function() {
$(this).removeAttr('disabled');
if($.inArray($(this).val(), selections) !== -1){
if($(this).val() != 0){
if($(this).is(':selected')){
// nothing
} else {
$(this).attr('disabled', true);
}
}
}
});
});
The function should run each time I change the selected option of a <select>, though it only runs with the 10 original ones. This function is not executed when I change the option in a cloned row. However, if I change the value in one of the originals, the function runs and it also adds the values from the cloned rows to the selections array.
So for some reason, the code recognizes that the clones have the same class, but it doesn't run when changing their values.
EDIT: short description of the code above; if I change the selected value of a <select>,the function will add all selected options to an array and disable every selected option in the selects, except in the box where the value itself is selected (if I select '2' in the first box, '2' gets disabled in all other boxes).
Because your select element is dynamic (It is added to the DOM after the initial page load, or to be specific; after the initial event handler attachment) you will need to delegate your change event handler:
$(document).on('change', '.variable_priority', function() {
});
Rather than use document though, you'll want to attach the event handler to the closest static element to .variable_priority.
JSFiddle
I think the code isn't executed on the new clones.
This script is loaded on start. It has to do it over for each new children.. Maybe after adding a new, run this script specific for that new one.
I'm using Dojo 1.9 and GridX 1.2. I'd like to detect the event that given row (or cell) has been changed by the user. Then additional action would be done, for example a row would change background color and the "Save" button would be enabled.
I was searching for the examples and in documentation, but I haven't found an example. How to register a listener reacting to edits in the grid, that would give me information what row or what cell has been edited, with the possibility of reading actual value after change?
The event is called 'onApply' and is registered on grid.edit (Module-Edit reference)
The example code to achieve that:
grid.edit.connect(grid.edit, "onApply", function(cell, success) {
var item = cell.row.data()
var id = cell.row.id
console.log('Row with ID '+id+' is modified. New value: '+item)
})