I'm new to Kendo and the Kendo grid but I'm trying to learn how to use the master detail Kendo grid where the detail grid is supposed to support batch editing. The data is available in a local JavaScript object.
This jsFiddle demonstrates the problems I'm seeing.
Here's how the grid is being created - see the jsFiddle for the complete snippet -
$("#grid").kendoGrid({
dataSource: items,
detailInit: createDetail,
columns: [
{ field: "Item", width: "200px" },
]
});
function createDetail(e) {
$("<div/>")
.appendTo(e.detailCell)
.kendoGrid({
dataSource: {
batch:true,
transport: {
read: function (options) {
options.success(e.data.SubItems);
}
}
},
editable:true,
pageable:true,
toolbar: ["save", "cancel"],
columns: [
{ field: "SubItem", title: "Sub Item", width: 200 },
{ field: "Heading1", title: "Heading 1", width: 100 }
]
});
}
When you edit an item in the grid and click to the next cell, the details grid automatically collapses not matter where I click, even in an adjacent cell. When I open it again, I don't see the change indicator in the cell (red notch) but the new value is there.
If I were to hook up the save to an ajax call, Kendo sends the right detail item(s) that were edited.
Nothing happens when I click cancel changes.
How do I get the grid to not collapse and see the change indicators ?
How do I get canceling of changes to work correctly ?
[Update] - Further investigation reveals that if I use an older Kendo version 2011.3.1129 , this works as expected. But if I use the newer 2012.3.1114, it doesn't. Dont know if this is a bug or a change in behavior.
After much effort, I found that the cause seems to be that the master grid is rebinding automatically causing the behavior I observed. I was able to get around this by handling the dataBinding event in the master grid and within that, checking if any of the detail datasources were dirty and if so, calling preventDefault.
Here are relevant code snippets :
dataBinding: function (e) {
if (masterGrid.AreChangesPending()) {
e.preventDefault();
}
}
AreChangesPending : function () {
var pendingChanges = false;
// I gave each detail div an id so that I can get a "handle" to it
$('div[id^="detail_"]').each(function (index) {
var dsrc = $(this).data("kendoGrid").dataSource;
$.each(dsrc._data, function () {
if (this.dirty == true || this.isNew()) {
pendingChanges = true;
}
});
// For some reason, Kendo did not detect new rows in the isNew()
// call above, hence the check below
if (dsrc._data.length != dsrc._total) {
pendingChanges = true;
}
});
return pendingChanges;
}
Related
I have jQGrid v4.6.0 (I can reproduce the same issue with Free jqGrid) on my page which I can reload using:
$('#jqGrid').trigger('reloadGrid');
It loads data by posting to a server. It posts correctly, gets the returned data correctly, and shows the correct data in the grid.
On the same page I have a link which opens a Bootbox dialog. Calling reloadGrid from within a Bootbox callback seems to make $('#jqGrid').jqGrid "not a function"
bootBoxWindow = bootbox.dialog({
title: "title",
message: "foo",
size: "large",
closeButton: false,
buttons: {
ok: {
label: "Save 2",
className: "btn-success",
callback: function () {
$('#jqGrid').trigger('reloadGrid');
}
}
}
});
When this calls reloadGrid, jqGrid properly posts to my server and retrieves the latest data. However, inside the loadComplete event I can no longer refer to jQuery("#jqGrid").jqGrid.
Example:
jQuery("#jqGrid").jqGrid('showCol', 3);
... calling this inside loadComplete gives error: TypeError: jQuery("#jqGrid").jqGrid is not a function"
I get the same error when using $(this) instead of jQuery("#jqGrid").
I cannot reproduce the issue without Bootbox. If I load the grid, then use this:
setTimeout(function () {
$('#jqGrid').trigger('reloadGrid', [{ current: true }]);
}, 10000);
... there is no error.
The first that I think is that the modal function is outside the jqGrid scope.
Example:
(function($) {
$(function() {
....
var $grid = $("#grid").jqGrid({...});
.....
console.log($grid);
});
})(jQuery);
console.log($grid);
The first console.log will output grid object, the second will throw error.
You may want to look at this article
I have a grid control which I wish to export the content of. Upon initialisation, the pageSize attribute is set to 10 items however I want to increase the number of items per page during exportToPDF.
I have tried to modify the pageSize of the dataSource prior to carrying out the export but this seems to have no effect on the end product.
var grid = $("#grid").data("kendoGrid");
var filter = grid.dataSource.filter;
grid.dataSource.query({
filter: filter,
pageSize: 20, // has no effect on the exported PDF
page: 1,
group: [{
field: "groupField1",
dir: "asc"
}, {
field: "groupField2",
dir: "asc"
}, {
field: "groupField3",
dir: "asc"
}]
});
var progress = $.Deferred();
grid._drawPDF(progress)
.then(function (root) {
return kendo.drawing.exportPDF(root, { forcePageBreak: ".page-break", multiPage: true });
})
.done(function (dataURI) {
// ... do some manual manipulation of dataURI
kendo.saveAs({
dataURI: manipualtedDataURI
});
progress.resolve();
Is there something I am missing so I can display more items on each page of the PDF export?
EDIT
Including my grid definition with the pdfExport function suggested by the below answer (which never gets called):
var grid = $("#reportGrid").kendoGrid({
pdf: {
allPages: true,
avoidLinks: true,
repeatHeaders: true,
template: kendo.template($("#page-template").html()),
scale: 0.7,
margin: { top: "2.5cm", right: "1cm", bottom: "1.5cm", left: "1cm" },
paperSize: "A4",
landscape: true
},
// *this function is not getting called*
pdfExport: function(e) {
e.sender.dataSource.pageSize(10);
e.promise.done(function() {
e.sender.dataSource.pageSize(20);
});
},
toolbar: kendo.template($("#template").html()),
...
});
Note: A template is used to include a header / footer on each page of the PDF export.
Another note: 'Manual manipulation of dataURI' includes going out to the server to perform a merge with another PDF file, so I cannot use the default export via the grid :(
EDIT 2
I have extended the Dojo example from #R.K.Saini's answer to use the method by which I need to generate the PDF export (as per original post).
The snippet logs the URI of the grid being exported and when the pdfExport function is called. As you will see, when using the built in grid 'Export to PDF' button, the pdfExport function is triggered but when using the additional button below the grid, it is not.
You can use pdfExport event to change page size of your grid datasource before pdf export started and then when the export finish you just need to revert back the previous page size.
The call back function of this event receive grid instance as e.sender and and a promise as e.promise which can be used to set back the page size when exporting finish.
For more info check http://docs.telerik.com/kendo-ui/api/javascript/ui/grid#events-pdfExport
$("#grid").kendoGrid({
dataSource: dataSource,
pdf: {
allPages: true
},
pdfExport: function(e) {
e.sender.dataSource.pageSize(10);
e.promise.done(function() {
e.sender.dataSource.pageSize(20);
});
},
...
//Other configguration
});
Here is a working demo http://dojo.telerik.com/UzOXO
Edit
You can also change grid page size in your custom export function just change grid page size before calling _drawPdf() function and change it back when done.
$("#btnPdfExport").kendoButton({
click: function () {
var grid = $("#grid").data("kendoGrid");
var progress = $.Deferred();
// Change grid datasource pagesize before calling _drawPDF
grid.dataSource.pageSize(20);
grid._drawPDF(progress)
.then(function (root) {
return kendo.drawing.exportPDF(root, { forcePageBreak: ".page-break", multiPage: true });
})
.done(function (dataURI) {
console.log("Exporting " + dataURI);
kendo.saveAs({
dataURI: dataURI,
fileName: "export.pdf"
});
progress.resolve();
// Change grid datasource pagesize when done
grid.dataSource.pageSize(10);
});
}
});
Check Update DOJO here http://dojo.telerik.com/UzOXO/8
I'm having an issue similar to the issues reported both here and here, with a only a few changes in how my form data is loaded.
The solution provided in the second link seemingly resolves my issue, but removing the show/hide scaling effects should not be required in order for CKEditor to instantiate properly. There's bound to be a much better alternative to resolving this conflict.
My issue:
When I open my page, and click the edit button, a jQueryUI Dialog pops up, loads its data via ajax, and then I attempt to replace the textarea added to the dialog with a CKEditor instance. The first time I load the page, the dialog works without a hitch. I'm able to modify the data within the editor, save my form data, and get on with life. However, if I close the dialog, then open it again, the editor is no longer enabled. The buttons still have hover effects, and are clickable, but do nothing. The text area of the editor is disabled and set to "style: visibility: hidden; display: none;". Nearly all the information I can find regarding this issue is from many years ago, and the fixes involve using functions/techniques that no longer exist or are applicable.
Control Flow
I open the page containing a text link 'Edit Update', which calls my Javascript function openEditTicketUpdateDialog.
function openEditTicketUpdateDialog(tup_id, url)
{
simplePost(null, url, new Callback
(
function onSuccess(data)
{
$('#editticketupdatedialog').dialog('option', 'buttons',
[
{
text: 'Save Edits',
click: function()
{
// Save the Update info
var formData = {
tup_update: CKEDITOR.instances.tup_update_edit.getData(),
tup_internal: +$('#tup_internal_edit').is(":checked"),
tup_important: +$('#tup_important_edit').is(":checked")
};
simplePost(formData, data['submitRoute'], new Callback
(
function onSuccess(data)
{
$('#update-' + tup_id).html(data.input['tup_update']);
$('#updateflags-' + tup_id).html(data.flags);
$('#editticketupdatedialog').dialog('close');
},
function onFail(errors)
{
console.log(errors);
}
));
}
},
{
text: 'Cancel',
click: function()
{
$(this).dialog("close");
}
}
]);
$('#editticketupdatedialog').dialog('option', 'title', data['title']);
$('#editticketupdatedialog').html(data['view']);
$('#editticketupdatedialog').dialog('open');
destroyEditor('tup_update_edit');
console.log('CKEDITOR.status: ' + CKEDITOR.status);
createEditor('tup_update_edit');
},
function onFail(errors)
{
console.log(errors);
}
));
}
This function uses three helper functions, simplePost, destroyEditor and createEditor.
function simplePost(data, url, callback)
{
post(data, url, true, false, callback);
}
function createEditor(name)
{
console.log('Create editor: ' + name);
console.log('Current Instance: ');
console.log(CKEDITOR.instances.name);
if (CKEDITOR.status == 'loaded')
{
CKEDITOR.replace(name,
{
customConfig: '/js/ckeditor/custom/configurations/standard_config.js'
});
}
else
{
CKEDITOR.on('load', createEditor(name));
CKEDITOR.loadFullCore && CKEDITOR.loadFullCore();
}
console.log('After instance created: ');
var instance = CKEDITOR.instances.name;
console.log(instance);
}
function destroyEditor(name)
{
console.log('Destroy editor: ' + name);
console.log('Current Instance: ');
console.log(CKEDITOR.instances.name);
if (CKEDITOR.instances.name)
{
console.log('Instance exists - destroying...');
CKEDITOR.instances.name.destroy();
$('#' + name).off().remove();
}
console.log('After instance removed: ');
var instance = CKEDITOR.instances.name;
console.log(instance);
}
This method of creating a CKEditor instance was gathered from here. This method of destroying a CKEditor instance was gathered from here.
As you can see, openEditTicketUpdateDialog fires an AJAX call to my getEditUpdateForm function through Laravel routes.
public function getEditUpdateForm($tup_id, $update_number)
{
$update = Update::find($tup_id);
$data = [
'title' => 'Editing update #' . $update_number . ' of ticket #' . $update->tup_ticket,
'view' => View::make('tickets.ticketupdate-edit')
->with('update', $update)
->render(),
'submitRoute' => route('tickets/update/submit', $tup_id)
];
return Response::json(array('status' => 1, 'data' => $data));
}
From here, a status of 1 is returned, and the onSuccess function is called. I've attempted to add the create/delete calls before the $('#editticketupdatedialog').dialog('open'); call, but to no avail. I've also tried multiple other solutions that I've found surfacing, which involve hacked implementations of jQueryUI's Dialog functions and attributes: _allowInteraction and moveToTop. I was originally successful in resolving this issue the first time it arose by calling this function before doing a CKEDITOR.replace:
function enableCKEditorInDialog()
{
$.widget( "ui.dialog", $.ui.dialog, {
/**
* jQuery UI v1.11+ fix to accommodate CKEditor (and other iframed content) inside a dialog
* #see http://bugs.jqueryui.com/ticket/9087
* #see http://dev.ckeditor.com/ticket/10269
*/
_allowInteraction: function( event ) {
return this._super( event ) ||
// addresses general interaction issues with iframes inside a dialog
event.target.ownerDocument !== this.document[ 0 ] ||
// addresses interaction issues with CKEditor's dialog windows and iframe-based dropdowns in IE
!!$( event.target ).closest( ".cke_dialog, .cke_dialog_background_cover, .cke" ).length;
}
});
}
After updating to Laravel 5, and making a few other changes serverside, this fix no longer works. I have been successful in resolving my issue by removing the show/hide properties from my dialog. I would very much prefer not to have to remove these properties, as half the reasoning for having the dialog is the aesthetics of an animation. Here is my dialog initialization.
$('#editticketupdatedialog').dialog({
modal: true,
draggable: false,
minWidth: 722,
autoOpen: false,
show:
{
effect: "scale",
duration: 200
},
hide:
{
effect: "scale",
duration: 200
},
closeOnEscape: true
});
When I have these animations enabled, the first time I use the dialog, it works perfectly. The second time, I receive the error TypeError: this.getWindow(...).$ is undefined - ckeditor.js:83:18 in the JS console, which refers to this line:
function(a)
{
var d = this.getWindow().$.getComputedStyle(this.$,null);
return d ? d.getPropertyValue(a) : ""
}
Recap
My main goal here is to find a fix for this issue, without having to remove the jQueryUI Dialog animation. I am unsure whom to point fingers at, as I really can't determine if the issue lies in CKEditor, jQueryUI or my implementation.
I finally found a solution that works in my case. losnir updated the outdated solution to a post here, and adding the open function to my dialog initialization resolved my issue.
My initialization is as follows:
$('#editticketupdatedialog').dialog({
modal: true,
draggable: false,
minWidth: 722,
autoOpen: false,
show:
{
effect: "scale",
duration: 200
},
hide:
{
effect: "scale",
duration: 200
},
closeOnEscape: true,
open: function()
{
$(this).parent().promise().done(function ()
{
destroyEditor('tup_update_edit');
console.log('CKEDITOR.status: ' + CKEDITOR.status);
createEditor('tup_update_edit');
});
}
});
Does anyone know if the kendo grid could be always set to edit mode at all times?
We don't want the user to click on a cell or a button to activate the edit mode. We want it the widgets to be displayed and available at all times.
Is it possible at all?
Apart from using batch editing mode you can try setting the template of every column and binding the input elements to the data items using MVVM.
$("#grid").kendoGrid({
dataSource: {
schema: {
model: {
id: "id",
fields: {
id: { editable: false }
}
}
}
data: [
{ id:1, age: 30, name: "John Doe" }
]
},
columns: [
{ field: "id", width: 50 },
{ field: "age", template: "<input data-bind='value: age' data-role='numerictextbox'>" },
{ field: "name", template:"<input data-bind='value: name' >" }
],
dataBound: function() {
var rows = this.tbody.children();
var dataItems = this.dataSource.view();
for (var i = 0; i < dataItems.length; i++) {
kendo.bind(rows[i], dataItems[i]);
}
}
});
Here is a live demo: http://jsbin.com/ApoFobA/2/edit
Out of the box, not exactly. You can enable Batch editing which displays everything normally, but clicking a cell will switch it to an editor automatically.
Example
To enable it, set { batch: true } on the table's datasource. Otherwise you're off to some deeper scripting. Checked and simply calling editRow on all rows doesn't do it. Default behavior is to disable editing on a row when a new one is taken into edit mode.
So, quick look says Batch mode - won't display editors all the time, but works and out of the box.
I found the above answer to be excellent. One issue though, is that Kendo doesn't clean up bindings when it refreshes the grid (such as when sorting or filtering or when refresh() is called) and deletes the grid's DOM elements. The result is the dataItems will have an increasing number of "change" events queued up -- a bit of a memory leak. This can be avoided by unbinding in the dataBinding event, as below:
dataBinding: function() {
var rows = this.tbody.children();
for (var i = 0; i < rows.length; i++) {
kendo.unbind(rows[i]);
}
}
I have a grid where I put a custom button in the navgrid for inserting totally different data in another table so I used editGridRow("new",…) where in the php url I just post data to a different table. Actually I am stuck with the issue if I submit the form, even if I settled reloadAfterSubmit:false, it add a new empty row in the grid…
You can see the piece of code here:
jQuery("#"+child_table_id).navButtonAdd("#"+pager_id,
{
caption:"Insert",buttonicon:"ui-icon-arrow-1-se",
onClickButton:function(){
var rows = jQuery("#"+child_table_id).getRowData();
if ( rows.length != 0 ) {
jQuery("#"+child_table_id).editGridRow("new",{height:140,width:420,url:"http://.../edit_datatable.php?table_name=mytable&fact=insert&q=1&flag=yes&ref_id="+id_row,
reloadAfterSubmit:false,
recreateForm:true,
closeOnEscape:true,
closeAfterAdd:true,
addCaption:'Insert',
savekey: [true,13],
bSubmit:'Save',
afterSubmit:function(response, postdata){ alert('inserting to a total different table...'); return {0:true} }
});
}
},
position:"last",
title:"Insert new step...",
cursor:"pointer"
});
I tried even to add:
onClickButton:function(){ $("#"+child_table_id).setGridParam({ datatype: 'local' }); }
afterSubmit:function(response, postdata){ return {0:true} },
onclickSubmit : function(params, posdata) {return {0:true} },
afterComplete:function(response, postdata, formid) { $("#"+child_table_id).setGridParam({ datatype: 'json' }); }
but it added anyway a row in the grid…
So how I can prevent jqGrid to add a new empty grid and refreshing the grid?…
You wrote about "inserting totally different data in another table", but you add button to the navigator bar of the grid "#"+child_table_id and your code inside of onClickButton callback adds new row to the same grid:
jQuery("#"+child_table_id).navButtonAdd("#"+pager_id, { ...
onClickButton:function(){
...
jQuery("#"+child_table_id).editGridRow("new", ...
}
});
If you really want to add new row to another grid you should use id of another in the editGridRow (like jQuery("#"+another_table_id).editGridRow("new",...).