initialization modularity for multi-table page

initialization modularity for multi-table page

GregPGregP Posts: 497Questions: 9Answers: 0
edited February 2011 in General
Despite the ... obscure title, a fairly simple problem, really.

I have a page with multiple tables on it. They each need their own initializations because they pull from different server-side resources, have different options enabled, etc. However, SOME of the parameters are shared. Now, if I store each COMPLETE initialization object separately, everything is fine. But I tried to make it more modular and ran into a problem. Like some of my other recent questions, probably more of a general JavaScript question.

So, given three arrays: the shared, the custom, and the full or "spliced" array, here's what the attempt looks like (simplified from actual code).

[code]
var initShared = new Array();
var initCustom = new Array();
var initArray = new Array();

initShared = {
"bAutoWidth": false,
"bJQueryUI": true,
"bProcessing": false
}

initCustom = {
"iDisplayLength" : 25,
"aLengthMenu": [[10, 25, 50, 100, 200, -1], [10, 25, 50, 100, 200, "All"]]
}

initArray = initShared.concat(initCustom);
[/code]

It's telling me that concat is not a valid function of initShared. Which means that for whatever reason it doesn't think that initShared is an array...? In any event, the concat doesn't work. And maybe my way of defining the arrays is misguided to begin with...?

One thing's for sure: in a test defining the arrays as [1,2,3] and [4,5,6] (ie. one-dimensional arrays) the concat function works. So it's mainly just simply that concat() can't do what I want it to do on the objects I am trying to pass to it. That leaves me looking for another option.

Any insight on how to keep the code somewhat modular would be great. Since concat() doesn't work, I suspect push() won't work either (about to go try), so I'll have to think outside the box a bit I believe.

Cheers,
Greg

Replies

  • allanallan Posts: 63,230Questions: 1Answers: 10,417 Site admin
    Hi Greg,

    You are quite right that concat is not quite the way to go there - its an array method, not an object one - and initShared is an object. What you are looking for here can be done with jQuery's extend method: http://api.jquery.com/jQuery.extend/ . With that you can do something like:

    [code]
    initObject = $.extend( true, {}, initShared, initCustom );
    [/code]
    Allan
  • GregPGregP Posts: 497Questions: 9Answers: 0
    Worked perfectly! I really should have known jQuery would have a class for that.
  • GregPGregP Posts: 497Questions: 9Answers: 0
    Bump from the past. So, the above extending works beautifully, and until today I've been happy to cut and paste the occasional "repetitive" bits of code within each table's initialization object. But with each passing iteration, I get further from the principles of DRY. Problem is, I'm not 100% fluent in JavaScript's capabilities, and I'm not sure if I can do what I want to do next.

    Let me provide some code so that you can see what I'm talking about.

    On the same page, I have 2+ DataTables. Let's say that one of them has this initialization:
    [code]
    alarmsInit = {
    "sAjaxSource": "servlet?type=Alarms",
    "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {

    /* Based on hidden columns, determine if sources and destinations are Server, Client, or unknown */
    /* and then apply a relevant class. */
    if (aData[2] == "Server") {
    $('td:eq(0)', nRow).addClass('server')
    } else if (aData[2] == "Client") {
    $('td:eq(0)', nRow).addClass('client')
    } else {
    $('td:eq(0)', nRow).addClass('unknown')
    }
    return nRow;
    }
    [/code]

    And the other has this:

    [code]
    nodesInit = {
    "sAjaxSource": "servlet?type=Nodes",
    "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {

    /* Based on hidden columns, determine if sources and destinations are Server, Client, or unknown */
    /* and then apply a relevant class. */
    if (aData[3] == "Server") {
    $('td:eq(1)', nRow).addClass('server')
    } else if (aData[3] == "Client") {
    $('td:eq(1)', nRow).addClass('client')
    } else {
    $('td:eq(1)', nRow).addClass('unknown')
    }
    return nRow;
    }
    [/code]

    You can see the problem. I'm doing essentially the same work, with the only difference being the column of the returned data and the visible column (via td) to which a class is applied.

    I don't know how I can break out the callback function into something more modular. If the function was not based on dynamic information, I could just declare it as a variable. But since those indexes are different each time, I need to be able to call it as a function, appClassAdder(aDataIndex,tdIndex).

    Is this even possible, or am I doomed to copy/paste each time I want to do the same thing?

    Apologies for what is essentially a JavaScript question rather than a DataTables question!
  • allanallan Posts: 63,230Questions: 1Answers: 10,417 Site admin
    jQuery's extend function is superb for this kind of thing. Something like:

    [code]
    var globalConfig = { ... whatever ... };
    var table1config = $.extend( true, globalConfig, { ... table 1 config ... } );
    var table2config = $.extend( true, globalConfig, { ... table 2 config ... } );
    [/code]
    The next major version of DataTables will provide the ability to override the defaults (implemented in a similar manner) which hopefully will help this kind of thing.

    Allan
  • GregPGregP Posts: 497Questions: 9Answers: 0
    Thanks for the reply, Allan!

    I should probably just implement this to see what happens before commenting back, but I'm a fool, so here goes:

    My understanding from earlier in the thread is that this will work great for relatively static configurations. But I'm missing in the above code how I'm able to pass dynamic information. It's the content of the {table 1 config} and {table 2 config} that I need to modularize. I've already implemented an extended config object (the alarmsInit and nodesInit are the equivalent of table1config and table2config)... the part I'm trying to break out now is dynamically applying the class based on cell contents. This part is repeated in each extended configuration object:

    [code]
    /* Based on hidden columns, determine if sources and destinations are Server, Client, or unknown */
    /* and then apply a relevant class. */
    if (aData[dataColumn] == "Server") {
    $('td:eq(visibleColumn)', nRow).addClass('server')
    } else if (aData[dataColumn] == "Client") {
    $('td:eq(visibleColumn)', nRow).addClass('client')
    } else {
    $('td:eq(visibleColumn)', nRow).addClass('unknown')
    }
    [/code]

    I could copy/paste into each of the callbacks with a set number instead of dynamic variables (dataColumn, visibleColumn) but it would be ideal to be able to just do something like:

    [code]
    nodesInit = {
    "sAjaxSource": "servlet?type=Nodes",
    "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
    renderIcons(3,1);
    return nRow;
    }
    [/code]

    But "renderIcons()" would have to have access to the aData object and possibly nRow... and I don't think that's possible in terms of scope and in the context of the callback.
  • GregPGregP Posts: 497Questions: 9Answers: 0
    edited March 2011
    Bah, I had an idea, and then I remembered I needed access to nRow as well. Foiled by scope again! My mortal enemy!
  • allanallan Posts: 63,230Questions: 1Answers: 10,417 Site admin
    Hi Greg,

    If each part of the default initialisation object needs to be overridden then certainly there is scope for this getting a little messy (and confusing :-) ). At that point there really isn't any point in having defaults - although aoColumnDefs with a target class applied to columns might be able to help in some cases - perhaps not this one.

    You say:

    > But "renderIcons()" would have to have access to the aData object and possibly nRow... and I don't think that's possible in terms of scope and in the context of the callback.

    Fair enough - but if renderIcons needs aData and nRow, why not just pass them into the function and have it deal with them as parameters?

    Allan
  • GregPGregP Posts: 497Questions: 9Answers: 0
    I will think on this! As the project gets broader and more modular in scope, I feel I'm going to have to do a heavy piece of refactoring anyhow. There are several times I need to take information based on hidden columns and do something to a visible cell based on that information. I could feasibly create a catch-all function for rendering that would take the 'type' of render, the aData, the nRow, the hidden column value, and the visible column value. Could take it even further and pass a 2D array containing information for all renders on any given row.

    I'm seeing some possibilities!

    Do you anticipate much of a performance hit passing around the aData and nRow like that?
  • allanallan Posts: 63,230Questions: 1Answers: 10,417 Site admin
    Yup sounds like a fair approach. I wouldn't have thought the performance impact would be that large - although obviously with the more rows you have the more time it will take since they are finite operations. The more you touch the DOM the slower it is - the more you can do in Javascript without touching the DOM the faster it will run :-)

    Allan
This discussion has been closed.