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)
})
Related
I am trying to access an individual row from a grid in Kendo UI so that I can run operations on the selected entries in the row. However, my code is not correctly grabbing the row itself and I do not know how to resolve this.
I've tried binding the grid to an event that, when changed, will fire my method in order to grab whichever row was toggled.
const row = arg.sender.element.closest("tr")
const grid = $("#ECUs").getKendoGrid()
const dataItem = grid.dataItem(row)
Results:
I.fn.init [prevObject: I.fn.init(1)]
length: 0
prevObject: I.fn.init [div#ECUs.k-grid.k-widget.k-display-block]
__proto__: w
(Sorry, I apparently don't have enough reputation to post images)
Ideally, I would expect to get a tr object back in the first method, but I'm getting absolutely nothing instead. Does anybody know how to correct this to access the row?
If you have a click event on one of the columns, you can access the table row using some jquery.
function onClickListener(e) {
e.preventDefault();
var row = this.dataItem($(e.currentTarget).closest("tr"));
}
Option 1:
You can use the edit event of a grid to get the currently selected row model.
edit: function(e) { console.log(e.model); }
Here, e.model contains the row data and you can access the particular column value by e.model.columnName.
Option 2: You can get the row model data like below from the other functions.
https://stackoverflow.com/a/56478061/8733214
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 have a kendo grid with custom popup edit window to imitate popup edit, but with batch edit. Everything works fine, but I am experiencing a small issue. Whenever value is changed, the grid cell does not have that red triangle thingy in the corner indicating that this particular value different from original.
As I understand in this post, manually made changes in datasource does not appear on the grid, so I have to add them manually.
This post 'manually maintain dirty cell marker on paging in Kendo grid' gives an idea how to get it working. I could attach some listeners to kendoWindow inputs, track what fields are being edited, compare old and new values...
But is there a less painful way to achieve this functionality? Maybe there is some built in kendo function to achieve that?
Here's a small working example http://dojo.telerik.com/aSaXe/4
The red "dirty" marks appear automatically only when the built-in in-cell editing is used. From this point of view, your scenario requires these to be added manually after the custom editing popup is closed.
You may find the change event of the data item useful in the task. It will be fired each time a value in the popup is changed and the respective textbox is blurred.
var uid = $(e.target).parents('tr').attr('data-uid');
var grid = $('#grid').data("kendoGrid");
var dataItem = grid.dataSource.getByUid(uid);
dataItem.bind("change", function(args) {
// args.field
});
Finally, keep in mind that each change in the Grid dataSource causes the whole table to be redrawn (unless the built-in in-cell editing is used), so you will lose any previously applied custom styling.
You can use the save event on your kendo grid as:
save: function (e) {
addDirtyUid(e.model.uid);
setTimeout(refreshVisualDirtyElements, 100);
}
Other functions and var:
var dirtyIds = [];
function addDirtyUid(currentUid) {
if (dirtyIds.indexOf(currentUid) === -1) {
dirtyIds.push(currentUid);
}
}
function refreshVisualDirtyElements() {
for (var i = 0; i < dirtyIds.length; i++) {
var thisUid = dirtyIds[i];
$("tr[data-uid='" + thisUid + "']").find("td:eq(0)").addClass("k-dirty-cell");
$("tr[data-uid='" + thisUid + "']").find("td:eq(0)").prepend('<span class="k-dirty"></span>');
}
}
I have the rowSelectionModel and the checkBox plugin's set.
How can I check to see if a checkbox is clicked on a row and check a checkbox on another row?
The "subscribe" is a most suitable event for you, and you will have a function where you can use jquery to refer to another row
Documentation is pretty clear about it. Example is below.
gridInstance.onCellChange.subscribe(function(e,args){
//use e, and args to get detais about cell was clicked and in which row is it
$(".another-row .anotherRow").attr('clicked', true);
});
define class anotherRow for column in column options, which contains checkbox suppose to be changed programmatically.
I did this first time, in the same kind of problem. I wrote the dummy function and subscribe to the event
gridInstance.onCellChange.subsribe(function(e, args){
window.e = e;
window.args = args;
});
here, you can examine "e" and "args" objects, until you will understand where you are, and what to do next. Once you will understand delete those lines.
I know from reading the assoc. Google group that there is not currently an event for clicking a specific point when using the marker map (only regionClick is implemented).
But was reading the docs and noticed the event Select which says:
select Fired when the user clicks a visual entity. To learn what has
been selected, call getSelection(). None
and
setSelection() none Selects the specified chart entities. Cancels any
previous selection. Selectable entities are regions with an assigned
value. A region correlates to a row in the data table (column index is
null). For this chart, only one entity can be selected at a time.
Extended description.
Would I be able to use this to get the entry that was clicked?
Example:
data.addRows([
['Rome', 2761477, 1285.31],
['Milan', 1324110, 181.76],
['Naples', 959574, 117.27],
['Turin', 907563, 130.17],
['Palermo', 655875, 158.9],
['Genoa', 607906, 243.60],
['Bologna', 380181, 140.7],
['Florence', 371282, 102.41]
]);
Somehow get that Milan was clicked? How would I do this? Or am I reading this wrong?
Google API for Geomaps: http://code.google.com/apis/chart/interactive/docs/gallery/geochart.html
Google Group stating there is no click event in Marker mode:
https://groups.google.com/forum/?fromgroups#!topic/google-visualization-api/K8uJoes8ZH0
You need call getSelection function when the select event is called. This function returns an array of objects. Each object have row and column attributes (if any). Use the row and the first column (0) to retrieve the label name (Rome, Milan, ...).
Example (http://jsfiddle.net/VtZQh/):
google.visualization.events.addListener(chart, 'select', function() {
var selection = chart.getSelection()[0];
var label = data.getValue(selection.row, 0);
alert(label);
});
Please refer to documentation to know more about getSelection.