In my jqGrid, I have a checkbox which is also available for editing, i.e. a user can click on the checkbox and that checkbox's value will be updated in the database. That is working fine. However when I click on the checkbox and if I try clicking on it again, nothing happens. The row does not get saved. Theoretically the unchecked value of the checkbox should be saved. But this does not happen.
I have tried referring to this answer of Oleg but it does not help.
The weird problem is if I select another row and then try to unselect the checkbox again, I do see a save request going.
I am guessing this is because I am trying to edit a row which is currently selected. But I am not sure what I am doing wrong here.
This is what I am doing in my beforeSelectRow
beforeSelectRow: function (rowid, e) {
var $target = $(e.target),
$td = $target.closest("td"),
iCol = $.jgrid.getCellIndex($td[0]),
colModel = $(this).jqGrid("getGridParam", "colModel");
if (iCol >= 0 && $target.is(":checkbox")) {
if (colModel[iCol].name == "W3LabelSelected") {
console.log(colModel[iCol].name);
$(this).setSelection(rowid, true);
$(this).jqGrid('resetSelection');
$(this).jqGrid('saveRow', rowid, {
succesfunc: function (response) {
$grid.trigger('reloadGrid');
return true;
}
});
}
}
return true;
},
Configuration:
jqGrid version: Latest free jqGrid
Data Type: Json being saved to server
Minimal Grid Code: jsFiddle
EDIT: After Oleg's answer this is what I have so far:
beforeSelectRow: function (rowid, e) {
var $self = $(this),
iCol = $.jgrid.getCellIndex($(e.target).closest("td")[0]),
cm = $self.jqGrid("getGridParam", "colModel");
if (cm[iCol].name === "W3LabelSelected") {
//console.log($(e.target).is(":checked"));
$(this).jqGrid('saveRow', rowid, {
succesfunc: function (response) {
$grid.trigger('reloadGrid');
return true;
}
});
}
return true; // allow selection
}
This is close to what I want. However if I select on the checkbox the first time and the second time, the console.log does get called everytime. However the saveRow gets called only when I check the checkbox and then click on it again to uncheck it the first time and never after that. By default the checkbox can be checked or unchecked based on data sent from server.
In the image, the request is sent after selecting the checkbox two times instead of being sent everytime.
UPDATE: As per #Oleg's suggestion, I have implemented cellattr and called a function inside. In the function I simply pass the rowid and update the checkbox of that rowid on the server.
Here's the code I used:
{
name: 'W3LabelSelected',
index: 'u.W3LabelSelected',
align: 'center',
width: '170',
editable: false,
edittype: 'checkbox',
formatter: "checkbox",
search: false,
formatoptions: {
disabled: false
},
editoptions: {
value: "1:0"
},
cellattr: function (rowId, tv, rawObject, cm, rdata) {
return ' onClick="selectThis(' + rowId + ')"';
}
},
and my selectThis function:
function selectThis(rowid) {
$.ajax({
type: 'POST',
url: myurl,
data: {
'id': rowid
},
success: function (data) {
if (data.success == 'success') {
$("#list").setGridParam({
datatype: 'json',
page: 1
}).trigger('reloadGrid');
} else {
$("<div title='Error' class = 'ui-state-error ui-corner-all'>" + data.success + "</div>").dialog({});
}
}
});
}
FIDDLE
I think there is a misunderstanding in the usage of formatter: "checkbox", formatoptions: { disabled: false }. If you creates non-disabled checkboxs in the column of the grid in the way then the user just see the checkbox, which can be clicked and which state can be changed. On the other side nothing happens if the user changes the state of the checkbox. On the contrary the initial state of the checkbox corresponds to input data of the grid, but the changed checkbox makes illusion that the state is changed, but nothing will be done automatically. Even if you use datatype: "local" nothing is happens and even local data will be changed on click. If you do need to save the changes based on the changing the state of the checkbox then you have to implement additional code. The answer demonstrates a possible implementation. You can change the state of some checkboxes on the corresponding demo, then change the page and go back to the first page. You will see that the state of the checkbox corresponds the lates changes.
Now let us we try to start inline editing (start editRow) on select the row of the grid. First of all inline editing get the values from the rows (editable columns) using unformatter, saves the old values in internal savedRow parameter and then it creates new editing controls inside of editable cells on the place of old content. Everything is relatively easy in case of using standard disabled checkbox of formatter: "checkbox". jqGrid just creates enabled checkboxs on the place of disabled checkboxs. The user can change the state of the checkboxs or the content of any other editable columns and saves the changes by usage of Enter for example. After that the selected row will be saved and will be not more editable. You can monitor the changes of the state of the checkbox additionally (by usage editoptions.dataEvents with "change" event for example) and call saveRow on changing the state. It's important that the row will be not editable after the saving. If you do need to hold the row editable you will have to call editRow once more after successful saving of the row. You can call editRow inside of aftersavefunc callback of saveRow, but I recommend you to wrap the call of editRow inside of setTimeout to be sure that processing of previous saving is finished. It's the way which I would recommend you.
On the other side if you try to combine enabled checkboxs of formatter: "checkbox" with inline editing then you will have much more complex processing. It's important that enabled checkbox can be changed first of all before processing of onclick and onchange event handlers. The order of 3 events (changing the state of the checkbox, processing of onclick and processing of onchange) can be different in different web browsers. If the method editRow be executing it uses unformatter of checkbox-formatter to get the current state of the checkbox. Based of the value of the state editRow replace the content of the cell to another content with another enabled checkbox. It can be that the state of the checkbox is already changed, but editRow interprets the changes state like the initial state of the checkbox. In the same way one can call saveRow only after editRow. So you can't just use saveRow inside of change handler of formatter: "checkbox", formatoptions: { disabled: false }, because the line is not yet in editing mode.
UPDATED: The corresponding implementation (in case of usage formatter: "checkbox", formatoptions: { disabled: false }) could be the following:
editurl: "SomeUrl",
beforeSelectRow: function (rowid, e) {
var $self = $(this),
$td = $(e.target).closest("tr.jqgrow>td"),
p = $self.jqGrid("getGridParam"),
savedRow = p.savedRow,
cm = $td.length > 0 ? p.colModel[$td[0].cellIndex] : null,
cmName = cm != null && cm.editable ? cm.name : "Quantity",
isChecked;
if (savedRow.length > 0 && savedRow[0].id !== rowid) {
$self.jqGrid("restoreRow", savedRow[0].id);
}
if (cm != null && cm.name === "W3LabelSelected" && $(e.target).is(":checkbox")) {
if (savedRow.length > 0) {
// some row is editing now
isChecked = $(e.target).is(":checked");
if (savedRow[0].id === rowid) {
$self.jqGrid("saveRow", rowid, {
extraparam: {
W3LabelSelected: isChecked ? "1" : "0",
},
aftersavefunc: function (response) {
$self.jqGrid("editRow", rowid, {
keys: true,
focusField: cmName
});
}
});
}
} else {
$.ajax({
type: "POST",
url: "SomeUrl", // probably just p.editurl
data: $self.jqGrid("getRowData", rowid)
});
}
}
if (rowid) {
$self.jqGrid("editRow", rowid, {
keys: true,
focusField: cmName
});
}
return true; // allow selection
}
See jsfiddle demo http://jsfiddle.net/OlegKi/HJema/190/
Related
I would like to add a context menu to each of my DataTable rows.
I want to get the row that was clicked on and then some way to identify it (I suppose the first cell value which contains the primary key would work) and then send an AJAX request containing the PK and option clicked.
I have figured out how to get the row by using "tr" as a selector, but how can I get the 1st cell's value (which contains the primary key). This prints out all of the cells:
$(function(){
$.contextMenu({
selector: 'td',
trigger: 'right',
callback: function(key, options) {
var m = $(options.$trigger).text();
window.console && console.log(m) || alert(m);
},
items: {
"delete": {name: "Delete", icon: "delete"},
});
});
Also, is this the best way to do this? I plan to have ~10 options in the context menu that interact with the rows. I am using Django as the backend.
Always use the API when you want to interact with DT. If you have an instance
var table = $('#example').DataTable( {..} )
then retrieve the current row by passing options.$trigger which holds the <tr> node :
$.contextMenu({
selector: 'tr',
trigger: 'right',
callback: function(key, options) {
var row = table.row(options.$trigger)
switch (key) {
case 'delete' :
row.remove().draw()
break;
case ...
}
},
items: {
'delete': { name: 'Delete', icon: 'delete' },
...
}
})
but how can I get the 1st cell's value (which contains the primary
key).
row.data()[0]
demo -> http://jsfiddle.net/z2q5scgr/
I am using ExtJS 2.3. I have 3 comboboxes with following stores.
Here, combo2 and combo3 share same store.
Following are the combo stores-
Combo1 Store:
Vice President
Manager
Employee
Student
Combo2 and Combo3 Store:
Assignments
Meetings
Salary
Now my requirement is, if 'Student' is selected from Combo1, 'Salary' should be filtered out from combo2 and 3 (It should not display 'Salary' option)
I am doing following code on change listener of combo1-
listeners: {
change: function(combo, record, index) {
var combo1Val = combo.value; // Give the selected value correctly
this.filterCombo(combo1Val , combo2);
this.filterCombo(combo1Val , combo3);
}
}
and this is function body
filterCombo: function (combo1Val , combo) {
if (combo1Val == 'Student') {
combo.store.filterBy(function (record) {
return record.get('text') != 'Salary';
});
}
else {
combo.store.clearFilter();
}
}
The problem here is for first time when I select Student from combo1, 'Salary' option disappears from combo2. Then, when I expand combo3, it displays salary option there, and again whwn I click on combo2, again it displays 'salary' option. somehow this filter is not working.
Can anybody tell what I am doing wrong here.
you can try this in combo2
listeners: {
expand: function(combo) {
combo.store.clearFilter();
if (combo1Val == 'Student') {
//filter
}
}
}
Though the answer provided by raghavendra is correct and works well but in the scenario if there are many combos(more than two) that need to be filtered, we have to write expand listener for each combo.
So thought of writing the code in one place that is in combo1 change event.
The problem was somehow after entering the function it was going into else statement i.e. combo.store.clearFilter(); so clearing out the filter.
I have solved my problem by checking the condition in combo1 change event itself instead of inside the function for each combo.
listeners: {
change: function(combo, record, index) {
var combo1Val = combo.value; // Give the selected value correctly
if (combo1Val == 'Student') {
this.filterCombo(combo1Val , combo2);
this.filterCombo(combo1Val , combo3);
}
else {
combo2.store.clearFilter();
combo3.store.clearFilter();
}
}
}
filterCombo: function (combo1Val , combo) {
combo.store.filterBy(function (record) {
return record.get('text') != 'Salary';
});
}
What I'm trying to accomplish is to allow multiple rows inside a table to toggle on or off without affecting the other rows in that same table.
It works fine when I only have one row. But the moment I add another row , the switch starts turning off other rows.
Here's a video clip of what I mean->
https://www.youtube.com/watch?v=uLBrZND69Ps
And here's the code ->
// ClIENT CODE
Template.orionMaterializeLayout.events({
"change .switch input": function (event) {
var change = event.target.checked;
Meteor.call('toggleHidden', change);
}
});
// SERVER CODE
Meteor.methods({
'toggleHidden' : function(change){
console.log(change);
Banner.update({}, {$set:{hidden: change }});
}
});
// COLLECTIONS CODE, WHAT RENDERS THE ON/OFF SWITCH ON THE TABLE
Banner = new orion.collection('slideshow', {
title: 'Add Images', // The title of the page
link: {
title: 'Slideshow',
section: 'top',
image: '<i class="fa fa-picture-o"></i>'
},
tabular: {
columns: [
{ data: 'hidden', title: 'Visibility',
render: function(doc){
if (doc === true ){
return '<div class="switch"><label>Off<input type="checkbox" checked="checked"><span class="lever"></span>On</label></div>'
} else {
return '<div class="switch"><label>Off<input type="checkbox"><span class="lever"></span>On</label></div>'
}
}
}
]
}
});
It looks like you intend the toggling to write the change to the database on the backend (Mongo collection on the server). However, your Banner.update() call does not specify which document to update - so it updates every document in your collection!
You need to do two things (with your code as-is). First, capture the data context that has triggered the event handler. Normally, that will be this within your handler. So this._id should return the document ID. Second, you need to pass that ID to your method, to ensure it only updates that document.
Without all of your code, it is hard to guarantee a correct answer (especially not knowing the data context within the template) but the below is likely to work:
// ClIENT CODE
Template.orionMaterializeLayout.events({
"change .switch input": function (event) {
var change = event.target.checked;
Meteor.call('toggleHidden', change, this._id);
}
});
// SERVER CODE
Meteor.methods({
'toggleHidden' : function(change, docId){
console.log(change);
Banner.update({_id: docId}, {$set:{hidden: change }});
}
});
I have a JQGrid.I need to take some Id to the OnClick function.In my scenario i wanted to get BasicId to the OnClick function.
MyCode
function grid() {
//JqGrid
$('#griddata').html('<table class="table" id="jqgrid"></table>')
$('#jqgrid').jqGrid({
url: '/Admin/GetBasicData/',
datatype: 'json',
mtype: 'GET',
//columns names
colNames: ['BasicId','Images'],
//columns model
colModel: [
{ name: 'BasicId', index: 'BasicId', resizable: false },
{
name: 'Images',
width: 120,
formatter: function () {
return "<button class='btn btn-warning btn-xs' onclick='OpenDialog()' style='margin-left:30%'>View</button>";
}
},
//Some Code here
Open Dialog Function
function OpenDialog(BasicId)
{
//Some code here
}
You can use onclick='OpenDialog.call(this, event)' instead of onclick='OpenDialog()'. You will have this inside of OpenDialog initialized to the clicked <button> and the event.target. Thus your code could be like the following
function OpenDialog (e) {
var rowid = $(this).closest("tr.jqgrow").attr("id"),
$grid = $(this).closest(".ui-jqgrid-btable"),
basicId = $grid.jqGrid("getCell", rowid, "BasicId");
// ...
e.stopPropagation();
}
One more option is even better: you don't need to specify any onclick. Instead of that you can use beforeSelectRow callback of jqGrid:
beforeSelectRow (rowid, e) {
var $td = $(e.target).closest("td"),
iCol = $.jgrid.getCellIndex($td[0]),
colModel = $(this).jqGrid("getGridParam", "colModel"),
basicId = $(this).jqGrid("getCell", rowid, "BasicId");
if (colModel[iCol].name === "Images") { // click in the column "Images"
// one can make additional test for
// if (e.target.nodeName.toUpperCase() === "button")
// to be sure that it was click to the button
// and not the click on another part of the column
OpenDialog(rowid);
return false; // don't select the row - optional
}
}
The main advantages of the last approach: one don't need to make any additional binding (every binding get memory resources and it take time). There are already exist on click handler in the grid and one can use it. It's enough to have one click handler because of event bubbling. The e.target provide us still full information about the clicked element.
Writing js event in your buttons html is not a good idea, its against 'un-obtrusive javasript' principle. You can instead add a click event on the entire grid in the render function and in the callback, filter out based on whether the button was clicked.
//not sure of the syntax of jqgrid, but roughly:
render: function(){
$('#jqgrid').unbind('click').on('click', function(){
if($(e.target).hasClass('btn-warning')){
var tr = $(e.target).parent('tr');
//retrieve the basicId from 'tr'
OpenDialog(/*pass the basicId*/)
}
})
}
I want to prevent from adding a category to the Select2 element if it fails creating the row first in my db. The action is not prevented when i call ev.preventDefault(); Nothing happens.. what is wrong?
$('#sel2').select2({
placeholder: 'Enter categories',
minimumInputLength: 3,
multiple: true,
ajax: {
url: 'async/get_categories.php',
dataType: 'json',
quietMillis: 250,
data: function (term, page) {
return {
q: term,
};
},
results: function (data, page) {
return {
results: data.items
};
},
cache: true
},
formatResult: format,
formatSelection: format
}).on('select2-selecting', function(e) {
console.log(e);
if (e.val == 4) {
// if category id equals 4
// do not add this category to select 2
// e.preventDefault();
// the above works just fine and its just for testing
}
// Is something wrong here?
var ev = e;
$.ajax({
type: 'POST',
url: 'async/create_profile_category.php',
data: {
profile_id: '1',
category_id: ev.val
},
success: function(response) {
console.log(response);
if (response.error === false) {
// category assigned successfully
} else {
// failed to assign category
// so i want now to prevent from adding to select2
console.log('should not add this category');
ev.preventDefault();
// the above is not working
}
},
error: function() {
alert('Failed to assign category!');
}
});
});
The AJAX request is made asynchronusly, so by the time it has finished the element has already been added. Even though you are calling ev.preventDefault(), it is too late for it to make a difference. So this leaves you with two options:
Make the request synchronusly, which will allow preventDefault to make the difference.
Make the request asynchronusly, and manually remove the element if it fails.
Both options have their pros and cons, and it's up to you to decide which option you go with.
Making the request synchronusly
Pros
The value will never be added if the request fails.
Works well in cases where the element cannot be added quite often.
Cons
Blocks the UI - So the user is potentially left with an unresponsive page while the request is made.
Making the request asynchronusly
Pros
Does not block the UI.
Works well in cases where elements typically can be added.
Cons
The value will always show up for the user, even if it fails later.
You must manually unset the new option.
What's important to consider here is the user experience of both options. When making synchronus requests, it's not uncommon for the browser to stop relaying events - which gives the illusion that the UI has locked up and the page has gone unresponsive. This has the benefit of ensuring that the value never shows up if it isn't allowed. But if users typically can add the elements, it also has the downside of complicating the most common use case.
If users can usually add elements, then it is a better experience to add the element while the request is being made, and then notifying the user later (while removing the element) if there was an issue. This is very common is web applications, and you can see it being used in many places, such as the Twitter and Facebook like buttons (where requests usually work), as well as places on Stack Overflow.
There is a way to get around this with version4 of the select2 library.
on select2:selecting we cancel the preTrigger event. Which will stop the select2:select event. We do our ajax call. On success we then get out Select2 instance then call the trigger of the Observer that way it by passes overwritten trigger method on your select2 instance.
The call method needs your select2 instance as the context so that the existing listeners are available to call.
var sel = $('#sel');
sel.select2(config);
sel.on('select2:selecting', onSelecting);
function onSelecting(event)
{
$.ajax({
type: 'POST',
url: 'async/create_profile_category.php',
data: {
profile_id: '1',
category_id: event.params.args.data.id
},
success: function(event, response) {
console.log(response);
if (response.error === false) {
// category assigned successfully
// get select2 instance
var Select2 = $users.data('select2');
// remove prevented flag
delete event.params.args.prevented;
// Call trigger on the observer with select2 instance as context
Select2.constructor.__super__.trigger.call(Select2, 'select', event.params.args);
} else {
// failed to assign category
// so i want now to prevent from adding to select2
console.log('should not add this category');
}
}.bind(null, event),
error: function() {
alert('Failed to assign category!');
}
});
event.preventDefault();
return false;
}
here how I did it for yii2 Select2 integrated into Gridview:
'pluginEvents' => [
'select2:selecting' => "
function(event)
{
var select2 = $('#types-" . $model->id . "');
select2.select2('close');
$.post('update',{id: " . $model->id . ", type_id: event.params.args.data.id})
.done (function(response)
{
select2.val(event.params.args.data.id);
select2.trigger('change');
})
.fail(function(response)
{
krajeeDialog.alert('Error on update:'+response.responseText);
});
event.preventDefault();
return false;
}",
],
it allows to asynchoronous update data in the grid using select2 and ajax and return it to previous value if there was an error on updating.