Trouble generating "View" and "Delete" columns in dt1.9
Trouble generating "View" and "Delete" columns in dt1.9
aaroncope
Posts: 11Questions: 0Answers: 0
i am using dt1.9.3. my scenario is a bit unique. i am using asp.net mvc3 and to keep the views (html) from being repetitive, i am building up the data table dynamically. i have created a column factory in javascript. i pass the desired columns from each view to this factory to help me generate the "aoColumnDefs" property for initialization.
all of my data appears in each view properly. however, i am having trouble generating the "View" and "Edit" links in a new column to the left of the first column. there are no errors, but these columns just do not appear at all. have any pointers?
view definition:
[code]
$(document).ready(function () {
var options = {
viewcolumn: true,
editcolumn: true,
columns: [
{data: "ID"},
{ data: "Description" }
]};
buildDataGrid(options);
});
[/code]
javascript factory:
[code]
//concrete types...
function AbstractColumns(options) {
this.datakey = options.datakey || "mDataProp";
this.titlekey = options.titlekey || "sTitle";
this.viewcolumn = options.viewcolumn;
this.editcolumn = options.editcolumn;
this.deletecolumn = options.deletecolumn;
this.columns = options.columns // example: [{ data: "ID" }, { data: "Description", title: "Description"}];
var title = "";
var data = '[';
//view column?
if (this.viewcolumn) {
data += '{aTargets:[0], bVisible:true, "fnRender": function(o,val) { makeViewLink()}},';
}
if (this.editcolumn) {
data += '{aTargets:[0], bVisible:true, "fnRender": function(o,val) { makeEditLink()}},';
}
for (var i = 0; i < this.columns.length; i++) {
//no title is provided? just use the data column and separate pascal-casing
title = (!this.columns[i].title) || (this.columns[i].title != '') ? fromPascalCaseToPhrase(this.columns[i].data) : this.columns[i].title;
if (i != this.columns.length - 1) {
data += '{"aTargets":[' + i + '],' + this.datakey + ':"' + this.columns[i].data + '", ' + this.titlekey + ':"' + title + '"},';
}
else {
data += '{"aTargets":[' + i + '],' + this.datakey + ':"' + this.columns[i].data + '", ' + this.titlekey + ':"' + title + '"}';
}
}
data += ']';
this.meta = eval(data);
}
AbstractColumns.prototype.serialize = function () {
return $.toJSON(this.meta);
}
//column factory
function GridColumnFactory() { }
//default concrete
GridColumnFactory.prototype.concrete = AbstractColumns;
//method to create grid columns by type
GridColumnFactory.prototype.createGridColumnsFor = function (options) {
this.concrete = AbstractColumns;
return new this.concrete(options);
}
function fromPascalCaseToPhrase(input) {
if (input == "ID") return input;
return input.replace(/([A-Z])/g, " $1")
}
function makeViewLink(oObj) {
alert('making view link');
var id = oObj.aData[0];
alert(id);
return "View";
}
function makeEditLink(oObj) {
alert('making edit link');
var id = oObj.aData[0];
alert(id);
return "Edit";
}
[/code]
initialization:
[code]
@{
Layout = ""; //this is required here b/c otherwise it will render the default layout file when called by RenderAction
var rawUrl = @Request.RawUrl;
var controllerName = @ViewContext.Controller.ValueProvider.GetValue("controller").RawValue;
}
$(document).ready(function () {
var dataTable = $('#dataTable');
var ajaxSource = "@rawUrl" + "/GetRelatedData?tenantid=";
var type = "@controllerName";
//load of the data grid based on the drop down
buildDataGrid = function (options) {
if ($(dataTable)) {
var tenantid = '';
if ($('select#TenantID')) {
tenantid = $('select#TenantID').val();
$(dataTable).dataTable({
"oLanguage": { "sZeroRecords": "No " + type + "s to display.", "sProcessing": "...loading..." },
"bProcessing": true,
"sAjaxSource": ajaxSource + tenantid,
"sAjaxDataProp": "", //set up the root json object so the response can be parsed appropriately
"aoColumnDefs": buildGridColumns(options)
});
}
}
}
function buildGridColumns(options) {
var factory = new GridColumnFactory();
return $.parseJSON(factory.createGridColumnsFor({
datakey: "mDataProp",
titlekey: "sTitle",
viewcolumn: options.viewcolumn,
columns: options.columns
}).serialize());
}
//change of grid based on change of drop down
$('#TenantID').change(function () { //when the tenant changes,
if (dataTable) { //and a data grid is present,
//rebind the data
var dt = dataTable.dataTable();
dt.fnReloadAjax(ajaxSource + $(this).val());
}
});
});
[/code]
all of my data appears in each view properly. however, i am having trouble generating the "View" and "Edit" links in a new column to the left of the first column. there are no errors, but these columns just do not appear at all. have any pointers?
view definition:
[code]
$(document).ready(function () {
var options = {
viewcolumn: true,
editcolumn: true,
columns: [
{data: "ID"},
{ data: "Description" }
]};
buildDataGrid(options);
});
[/code]
javascript factory:
[code]
//concrete types...
function AbstractColumns(options) {
this.datakey = options.datakey || "mDataProp";
this.titlekey = options.titlekey || "sTitle";
this.viewcolumn = options.viewcolumn;
this.editcolumn = options.editcolumn;
this.deletecolumn = options.deletecolumn;
this.columns = options.columns // example: [{ data: "ID" }, { data: "Description", title: "Description"}];
var title = "";
var data = '[';
//view column?
if (this.viewcolumn) {
data += '{aTargets:[0], bVisible:true, "fnRender": function(o,val) { makeViewLink()}},';
}
if (this.editcolumn) {
data += '{aTargets:[0], bVisible:true, "fnRender": function(o,val) { makeEditLink()}},';
}
for (var i = 0; i < this.columns.length; i++) {
//no title is provided? just use the data column and separate pascal-casing
title = (!this.columns[i].title) || (this.columns[i].title != '') ? fromPascalCaseToPhrase(this.columns[i].data) : this.columns[i].title;
if (i != this.columns.length - 1) {
data += '{"aTargets":[' + i + '],' + this.datakey + ':"' + this.columns[i].data + '", ' + this.titlekey + ':"' + title + '"},';
}
else {
data += '{"aTargets":[' + i + '],' + this.datakey + ':"' + this.columns[i].data + '", ' + this.titlekey + ':"' + title + '"}';
}
}
data += ']';
this.meta = eval(data);
}
AbstractColumns.prototype.serialize = function () {
return $.toJSON(this.meta);
}
//column factory
function GridColumnFactory() { }
//default concrete
GridColumnFactory.prototype.concrete = AbstractColumns;
//method to create grid columns by type
GridColumnFactory.prototype.createGridColumnsFor = function (options) {
this.concrete = AbstractColumns;
return new this.concrete(options);
}
function fromPascalCaseToPhrase(input) {
if (input == "ID") return input;
return input.replace(/([A-Z])/g, " $1")
}
function makeViewLink(oObj) {
alert('making view link');
var id = oObj.aData[0];
alert(id);
return "View";
}
function makeEditLink(oObj) {
alert('making edit link');
var id = oObj.aData[0];
alert(id);
return "Edit";
}
[/code]
initialization:
[code]
@{
Layout = ""; //this is required here b/c otherwise it will render the default layout file when called by RenderAction
var rawUrl = @Request.RawUrl;
var controllerName = @ViewContext.Controller.ValueProvider.GetValue("controller").RawValue;
}
$(document).ready(function () {
var dataTable = $('#dataTable');
var ajaxSource = "@rawUrl" + "/GetRelatedData?tenantid=";
var type = "@controllerName";
//load of the data grid based on the drop down
buildDataGrid = function (options) {
if ($(dataTable)) {
var tenantid = '';
if ($('select#TenantID')) {
tenantid = $('select#TenantID').val();
$(dataTable).dataTable({
"oLanguage": { "sZeroRecords": "No " + type + "s to display.", "sProcessing": "...loading..." },
"bProcessing": true,
"sAjaxSource": ajaxSource + tenantid,
"sAjaxDataProp": "", //set up the root json object so the response can be parsed appropriately
"aoColumnDefs": buildGridColumns(options)
});
}
}
}
function buildGridColumns(options) {
var factory = new GridColumnFactory();
return $.parseJSON(factory.createGridColumnsFor({
datakey: "mDataProp",
titlekey: "sTitle",
viewcolumn: options.viewcolumn,
columns: options.columns
}).serialize());
}
//change of grid based on change of drop down
$('#TenantID').change(function () { //when the tenant changes,
if (dataTable) { //and a data grid is present,
//rebind the data
var dt = dataTable.dataTable();
dt.fnReloadAjax(ajaxSource + $(this).val());
}
});
});
[/code]
This discussion has been closed.
Replies
NOTE: _GridIndexPage is a partial view that refers to other partials, such as header/footer, and other controls on the page.
[code]
@Html.Partial("_GridIndexPage")
$(document).ready(function () {
var options = {
primarykey: [{key:"ID"}],
type: "Badge",
columns: [
{ data: "ID", visible: "false" },
{ data: "Name" },
{ data: "Description" },
{ data: "IconUrl" },
{ data: "ActionCount", title: "# Required Actions" },
{ data: "TypeCode", title: "Group Type" },
{ data: "IsActive", title: "Active?" },
{ data: "SortOrder"}],
viewcolumn: true,
editcolumn: true,
url: '@Request.RawUrl',
contexttitle: "Name"
};
buildDataGrid(options);
});
[/code]
NOTE: again, _GridIndexPage is a partial view that refers to other partials, such as header/footer, and other controls on the page.
[code]
@Html.Partial("_GridIndexPage")
$(document).ready(function () {
var options = {
primarykey: [{key:"ID"}],
type: "Rank",
columns: [
{data:"ID",visible: "false"},
{data:"Name"},
{data:"Description"},
{data:"MinPoints"},
{data:"MaxPoints"},
{data:"IconUrl"},
{data:"TypeCode", title: "Group Type"},
{data:"IsActive", title:"Active?"},
{data:"SortOrder"}],
viewcolumn: true,
editcolumn: true,
url: '@Request.RawUrl',
contexttitle: "Name"
};
buildDataGrid(options);
});
[/code]
Notice the "ajaxSource" javascript variable. That is eventually set as the data table's source. It is a single base controller action that delegates method invocation dynamically via reflection to get the data and return it as json to the grid plugin.
[code]
@{
Layout = ""; //this is required here b/c otherwise it will render the default layout file when called by RenderAction
var rawUrl = @Request.RawUrl;
var controllerName = @ViewContext.Controller.ValueProvider.GetValue("controller").RawValue;
}
$(document).ready(function () {
var dataTable = $('#dataTable');
var ajaxSource = "@rawUrl" + "/GetIndex?tenantid=";
var type = "@controllerName";
//load of the data grid based on the drop down
buildDataGrid = function (options) {
if ($(dataTable)) {
var tenantid = '';
if ($('select#TenantID')) {
tenantid = $('select#TenantID').val();
$(dataTable).dataTable({
"oLanguage": { "sZeroRecords": "No " + type + "s to display.", "sProcessing": "...loading..." },
"bProcessing": true,
"sAjaxSource": ajaxSource + tenantid,
"sAjaxDataProp": "", //set up the root json object so the response can be parsed appropriately
"aoColumnDefs": buildGridColumns(options)
});
}
}
}
function buildGridColumns(options) {
var factory = new GridColumnFactory();
return factory.createGridColumnsFor({
type: options.type,
viewcolumn: options.viewcolumn,
editcolumn: options.editcolumn,
deletecolumn: options.deletecolumn,
primarykey: options.primarykey,
columns: options.columns,
imagewidth: 16,
url: options.url,
contexttitle: options.contexttitle
});
}
//change of grid based on change of drop down
$('#TenantID').change(function () { //when the tenant changes,
if (dataTable) { //and a data grid is present,
//rebind the data
var dt = dataTable.dataTable();
dt.fnReloadAjax(ajaxSource + $(this).val());
}
});
$('a[class="action-link delete"]').live('click', function (e) {
e.preventDefault();
var source = $(this).attr('href');
$.ajax({
url: source,
type: 'POST',
success: function (result) {
if (result.saved) {
location.reload();
}
}
});
});
});
[/code]
The buildGridColumns method taps into a simple javascript extension i wrote that builds up the columns dynamically from the supplied options. Below is the code for that extension. Feel free to tweak it to your needs.
[code]
//jquery.dataTables.extensions.columnFactory.js
//concrete types...
function DataTablesColumns(options) {
this.type = options.type;
this.primarykey = options.primarykey; //[{key: "ID"}, {key: "Name"}
this.columns = options.columns // example: [{ data: "ID", visible:false }, { data: "Description", title: "Description"}, { data: "IsActive", title: "Active?"} ];
this.viewcolumn = options.viewcolumn || false; //true / false
this.editcolumn = options.editcolumn || false; //true / false
this.deletecolumn = options.deletecolumn || false; //true / false
this.imagewidth = options.imagewidth;
this.url = options.url;
this.contexttitle = options.contexttitle || "Title goes here";
this.meta = [];
var title = "";
var width = 0;
var j = 0;
var visible = false;
for (var i = j; i < this.columns.length; i++) {
visible = this.columns[i].visible ? false : true;
//no title is provided? just use the data column and separate pascal-casing
title = (!this.columns[i].title) || (this.columns[i].title != '') ? fromPascalCaseToPhrase(this.columns[i].data) : this.columns[i].title;
this.meta.push({ "aTargets": [i], "mDataProp": this.columns[i].data, "sTitle": title, "bVisible": visible });
j++;
}
width = (((this.viewcolumn) ? 1 : 0) + ((this.editcolumn) ? 1 : 0) + ((this.deletecolumn) ? 1 : 0)) * this.imagewidth;
if ((this.viewcolumn) || (this.editcolumn) || (this.deletecolumn))
this.meta.push({ "sWidth": width, "sTitle": "Actions", "aTargets": [j], "bSortable": false, "mDataProp": this.primarykey[0].key, "bUseRendered": false, "fnRender": function (oObj, val) { return makeLinks(oObj, val, options) } });
return this.meta;
}
//column factory
function GridColumnFactory() { }
//default concrete
GridColumnFactory.prototype.concrete = DataTablesColumns;
//method to create grid columns by type
GridColumnFactory.prototype.createGridColumnsFor = function (options) {
this.concrete = DataTablesColumns;
return new this.concrete(options);
}
function fromPascalCaseToPhrase(input) {
if (input == "ID") return input;
return input.replace(/([A-Z])/g, " $1")
}
function makeLinks(oObj, val, options) {
var links = '';
var contexttitle = oObj.aData[options.contexttitle];
if (options.viewcolumn) {
contexttitle = contexttitle == null ? "Viewing " + fromPascalCaseToPhrase(options.type) : "Viewing - \"" + oObj.aData[options.contexttitle] + "\"";
if (options.primarykey.length == 1)
links = "";
else if (options.primarykey.length == 2) {
links = "";
}
}
//reset for next check
contexttitle = oObj.aData[options.contexttitle];
if (options.editcolumn) {
contexttitle = contexttitle == null ? "Editing " + fromPascalCaseToPhrase(options.type) : "Editing - \"" + oObj.aData[options.contexttitle] + "\"";
if (options.primarykey.length == 1)
links += "";
else if(options.primarykey.length == 2)
links += "";
}
if (options.deletecolumn) {
if (options.primarykey.length == 1)
links += "";
else if(options.primarykey.length == 2)
links += "";
}
return links;
}
[/code]
[code]
[HttpGet]
public ContentResult GetIndex(string tenantid)
{
//the tenant has been selected. commit it to the persistence mechanism
CommitTenantToPersistence(tenantid);
dynamic map = InvokeMethodFor(HttpContext.Request.RequestContext.RouteData.Values["controller"].ToString(),
"Index", new object[] { _cryptoProvider, tenantid });
/*NOTE: the code below can be changed to use the mvc json serializer and return a JsonResult after we upgrade to EF 5.0.
currently, the result of the above code returns a list of dynamic proxies that the JsonResult action result can't convert into a
concrete type. I believe EF5 provides a contract resolver to alleviate this situation in EF 5. */
return Content(JsonConvert.SerializeObject(map, Formatting.Indented, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new DynamicProxyContractResolver()
}));
}
///
/// invokes the index public abstract implemented methods and passes parameters to the methods.
///
///
///
///
///
private dynamic InvokeMethodFor(string type, string pageType, object[] methodParameters)
{
dynamic result = null;
Type thisType = this.GetType(); //get the derived type, then invoke it's generic method dynamically via reflection!
string conventionalTargetMethodName = string.Format("Get{0}Model", pageType);
MethodInfo method = thisType.GetMethod(conventionalTargetMethodName);
MethodInfo generic = null;
switch(pageType) //wish i could do this via reflection, but it just gets even more confusing to read
{
case "Index":
generic = method.MakeGenericMethod(GetIndexModelType());
break;
case "Detail":
generic = method.MakeGenericMethod(GetDetailModelType());
break;
case "Edit":
generic = method.MakeGenericMethod(GetEditModelType());
break;
case "Create":
generic = method.MakeGenericMethod(GetCreateModelType());
break;
default:
generic = null;
break;
}
if (generic != null)
{
ParameterInfo[] methodParams = generic.GetParameters();
object classInstance = Activator.CreateInstance(thisType, null);
if (methodParams.Length == 0)
{
result = generic.Invoke(classInstance, null);
}
else
{
result = generic.Invoke(classInstance, methodParameters);
}
}
return result;
}
//abstracts to be implemented by deriving controllers
public abstract Type GetIndexModelType();
public abstract IList GetIndexModel(ICryptoProvider crypto, string tenantId) where T : new();
public abstract Type GetDetailModelType();
public abstract T GetDetailModel(ICryptoProvider crypto, string tenantId, int id, int otherid = 0) where T : new();
public abstract Type GetEditModelType();
public abstract T GetEditModel(ICryptoProvider crypto, string tenantId, int id, int otherid = 0) where T : new();
public abstract Type GetCreateModelType();
public abstract T GetCreateModel(ICryptoProvider crypto, string tenantId, int id, int otherid = 0) where T : new();
[/code]
badge:
[code]
[HttpGet]
public ActionResult Index()
{
return View();
}
public override Type GetIndexModelType()
{
return typeof(Badge);
}
public override IList GetIndexModel(ICryptoProvider crypto, string tenantId)
{
BadgeQuery query = new BadgeQuery(crypto, GetTenant(tenantId));
return query.GetBadges() as IList;
}
[/code]
rank:
[code]
[HttpGet]
public ActionResult Index()
{
return View();
}
public override Type GetIndexModelType()
{
return typeof(Rank);
}
public override IList GetIndexModel(ICryptoProvider crypto, string tenantId)
{
RankQuery query = new RankQuery(crypto, GetTenant(tenantId));
return query.GetRanks() as IList;
}
[/code]