New to Kendo UI for jQuery? Start a free 30-day trial
Handling Hidden Columns in Grid with Drag and Drop
Updated on Feb 16, 2026
Environment
| Product | Kendo UI for jQuery Grid |
| Version | 2025.4.1321 |
Description
I am using the row reorderable feature in the Kendo UI for jQuery Grid with hidden columns and column filtering/sorting. When dragging rows, the drag-and-drop hint shows the content of hidden columns, which is undesirable. Hidden columns contain data used to control context-related actions, but they should not appear in the drag-and-drop hint.
Solution
To exclude hidden columns from the drag-and-drop hint in the Kendo UI for jQuery Grid, customize the internal _draggableRows function of the Grid. In the handler check if a column is visible when constructing the hint.
Code Example
<div class="k-d-flex k-flex-wrap">
<div class="k-flex-grow">
<h4 class="mb-sm">Available Products</h4>
<div id="inStockProductsGrid"></div>
</div>
<div class="k-flex-grow">
<h4 class="mb-sm">Not Available Products</h4>
<div id="discontinuedGrid"></div>
</div>
</div>
<script>
kendo.ui.Grid.fn._draggableRows = function () {
var that = this,
selectable =
that._checkBoxSelection || (that.options.selectable && !kendo.ui.Selectable.parseOptions(that.options.selectable).cell),
clickMoveClick = false,
isMobile = !!(that._isMobile || kendo.support.mobileOS),
ITEMROW = "tr:not(.k-grouping-row):not(.k-detail-row):not(.k-footer-template):not(.k-group-footer):visible";
if (that._draggableRowsInstance) {
that._draggableRowsInstance.destroy();
}
if (
this.options.reorderable.rows.clickMoveClick !== false &&
this._hasDragHandleColumn
) {
clickMoveClick = true;
}
that._draggableRowsInstance = that.tbody
.kendoDraggable({
holdToDrag: isMobile,
showHintOnHold: isMobile,
preventOsHoldFeatures: isMobile,
group: "row-draggable",
autoScroll: true,
filter: (selectable ? " > .k-selected" : " > " + ITEMROW) + (that._hasDragHandleColumn ? " > [ref-grid-drag-cell]" : ":not(:has([data-container-for]))"),
hint: function (target) {
var hint = $('<div class="k-reorder-clue k-drag-clue">' + kendo.ui.icon({ icon: "cancel", iconClass: "k-drag-status", }) + "</div>",);
if (
selectable &&
that.select().length > 1 &&
that.lockedContent
) {
hint.append(
"<span>" +
that.select().length / 2 +
" " +
encode(that.options.messages.itemsSelected) +
"</span>",
);
} else if (
selectable &&
that.select().length > 1 &&
!that.lockedContent
) {
hint.append(
"<span>" +
that.select().length +
" " +
encode(that.options.messages.itemsSelected) +
"</span>",
);
} else {
/* exclude invisible conlums */
var clone = target.closest(ITEMROW).find("td:visible:not(.k-command-cell)").clone();
clone.each(function (index, elm) {
hint.append(
"<span>" +
elm.innerText.replace(/<(\/?)script([^>]*)>/gi, "") +
" </span>",
);
});
}
return hint;
},
clickMoveClick: clickMoveClick,
cursorOffset: { top: 0, left: 0 },
/* do not allow drag and drop if the datasource filters or sorts the data */
dragstart: function (e) {
let kendoGrid = e.currentTarget.closest(".k-grid").data("kendoGrid");
if (kendoGrid != undefined) {
let ds = kendoGrid.dataSource;
if (ds.filter() != undefined) {
e.preventDefault();
}
let sort = ds.sort();
if (sort != undefined && sort.length > 0) {
e.preventDefault();
}
}
},
}).data("kendoDraggable");
};
$(document).ready(function () {
var crudServiceBaseUrl = "https://demos.telerik.com/service/v2/core";
var inStockData = [],
discontinuedData = [];
var discontinuedDS = new kendo.data.DataSource({
data: discontinuedData,
schema: {
model: {
id: "ProductID"
} },
pageSize: 10
});
var inStockDS = new kendo.data.DataSource({
data: inStockData,
schema: {
model: {
id: "ProductID"
}
},
pageSize: 10,
});
$.ajax({
type: "READ",
url: crudServiceBaseUrl + "/Products",
success: function (data) {
data.forEach(function (item) {
item.Discontinued === false ? inStockData.push(item) : discontinuedData.push(item);
});
discontinuedDS.data(discontinuedData);
inStockDS.data(inStockData);
}
});
$("#inStockProductsGrid").kendoGrid({
dataSource: inStockDS,
pageable: true,
height: 400,
width: 550,
columns: [
{ draggable: true, width: "40px" },
{ field: "ProductName", title: "Product Name", width: "200px" },
{ field: "UnitPrice", title: "Unit Price", format: "{0:c}", width: "140px", hidden: true },
{
field: "Discontinued", title: "In Stock",
template: "<span id='badge_#=ProductID#' class='badgeTemplate'></span>",
attributes: { style: "text-align: center;" },
width: "130px"
}],
dataBound: onDataBound,
navigatable: true,
reorderable: {
rows: true
},
rowReorder: onRowRеordered
});
$("#discontinuedGrid").kendoGrid({
dataSource: discontinuedDS,
pageable: true,
height: 400,
width: 550,
columns: [
{ draggable: true, width: "40px" },
{ field: "ProductName", title: "Product Name", width: "200px" },
{ field: "UnitPrice", title: "Unit Price", format: "{0:c}", width: "140px" },
{
field: "Discontinued", title: "In Stock",
template: "<span id='badge_#=ProductID#' class='badgeTemplate'></span>",
attributes: { style: "text-align: center;" },
width: "130px"
}],
dataBound: onDataBound,
navigatable: true,
reorderable: {
rows: true
},
rowReorder: onRowRеordered
});
});
const tr = {
ProductID: 999,
ProductName: "Placeholder",
UnitPrice: "Placeholder",
dummy: true,
};
function onDataBound(e) {
var grid = this;
if (grid.dataSource.get(tr.ProductID) && grid.dataSource.view().length > 1) {
grid.dataSource.remove(grid.dataSource.get(tr.ProductID));
};
if (grid.dataSource.view().length === 0) {
grid.dataSource.add(tr);
};
grid.table.find("tr").each(function () {
var dataItem = grid.dataItem(this);
var themeColor = dataItem.Discontinued ? 'error' : 'success';
var text = dataItem.Discontinued ? 'not available' : 'available';
$(this).find(".badgeTemplate").kendoBadge({
themeColor: themeColor,
text: text,
});
});
};
function onRowRеordered(ev) {
var grid = ev.sender,
dataSource = grid.dataSource,
externalGrid, externalDataItem;
if (ev.oldIndex === -1) { // Row dropped from external grid
ev.preventDefault();
externalGrid = ev.row.parents(".k-grid").data("kendoGrid");
externalDataItem = externalGrid.dataItem(ev.row);
if (!externalDataItem.dummy) {
externalDataItem.Discontinued === true ? externalDataItem.Discontinued = false : externalDataItem.Discontinued = true;
externalGrid.dataSource.remove(externalDataItem);
dataSource.insert(ev.newIndex, externalDataItem.toJSON());
const dummyRow = dataSource.get(tr.ProductID);
if (dummyRow) {
dataSource.remove(dummyRow);
};
};
};
if (externalGrid && externalGrid.dataSource.total() === 0) {
externalGrid.dataSource.add(tr);
};
};
</script>