Trouble generating "View" and "Delete" columns in dt1.9

Trouble generating "View" and "Delete" columns in dt1.9

aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
edited September 2012 in DataTables 1.9
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]

Replies

  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    UPDATE: the makeViewLink and makeEditLink functions were not accepting "o" and "val". i updated my code, but those methods are still not being called, and the columns still do not appear.
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    no worries. i figured this out.
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    in the event that anyone is interested in how this works, i'll post several follow-up comments with examples + source code. this is using asp.net mvc, razor syntax. it wouldn't fit all in one comment.
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    Example 1: grid with list of "badges". this is under /Areas/Community/Views/Badge/Index.cshtml.
    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]
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    Example 2: grid with list of "ranks". this is under /Areas/Community/Views/Rank/Index.cshtml.
    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]
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    This partial view defines the data table and it is referenced by "_GridIndexPage", from above examples...

    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]
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    In the above initialization code, you'll notice under "buildDataGrid", it sets the "aoColumnDefs" to the result of the function, buildGridColumns, passing the options (which were created in the views [examples 1/2, above]).

    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]
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    and finally, if you care to know what the "GetIndex" method does within the C# mvc controller, follow this code below. these methods sit inside a "Base" controller, which all other controllers derive from. This base controller defines some abstract methods that must be overridden. These methods are contracts that will be dynamically invoked on the derived controllers to return that derived controller's data in the form of a model.

    [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]
  • aaroncopeaaroncope Posts: 11Questions: 0Answers: 0
    ah yes, here are two examples of the overrides for "badge" & "rank" on those controllers.

    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]
This discussion has been closed.