0

I was looking for some time for a solution to the following problem: I have an object in a javascript function that defines a table of data. I want to pass a row of this table to a function defined using the onclick handler of a table cell (a td element) so that it can open a popup with extra information for that element.

I found quite a few solutions to this, but mostly involving strings prepared from php and then passed to javascript. My case involves no php at the level where the above functionality is required.

After some experimentation I came up with the following.

Passing an object to an inline HTML function requires 2x stringify. The first will create the json string, and the second will escape all double quotes and also add double quotes at the beginning and end. The receiving function then only needs to JSON.parse() the json string passed this way.

// JSON format for json_txt is:
//  { "column 1" : ["c1 data 1", "c1 data 2", ...],
//    "column 2" : ["c2 data 1", "c2 data 2", ...],
//    ...
//  }

function createTable(json_txt, el_id)
{
    var el = document.getElementById(el_id);
    var o = JSON.parse(json_txt);
    var s = '<table>';
    var num = numTableRows(o);  // just returns number of rows
    for (var r=0; r<num; r++)
    {
        s += '<tr class="tbl_data">';
        for (var col in o)
        {
            if (col === 'a special column name here')
            {
                var js = {};
                for (var c in o) js[c] = o[c][r];
                s += "<td onclick='onTrackShowInfo(this," + JSON.stringify(JSON.stringify(js))+" )'>";
            }
            else
                s += '<td>';

            s += o[col][r];
            s += '</td>';
        }
        s += '</tr>';
    }
    s += '</table>';
    el.innerHTML = s; 
}

....

function onTrackShowInfo(el, json_row)
{
    var o = JSON.parse(json_row);
    // ok, we have the row from element 'el' at this point
}

One reason I am posting it here, is to help others who may also be looking for something similar, and also to ask if anyone sees any issues with this, or has come up with a better solution to this specific problem.

EDIT: Added a very simplified version of the createTable() function to show a bit more of the context where this code runs.

6
  • 2
    It'd be much, much easier to do this if you didn't use onclick to assign your event handlers, but instead used modern DOM APIs. You really only need to store the index on the table row anyway. Commented Dec 21, 2014 at 17:40
  • 2
    Expanding on what @Pointy said. You should be using addEventListener to handle the click event, this would solve your issue. Commented Dec 21, 2014 at 17:41
  • I agree with @Pointy Now you need to escape the JSON twice to not break the HTML. Personally a horrible solution. Save the JSON data in a side array and refer to it by index. Also using event handlers will be better. Commented Dec 21, 2014 at 17:43
  • all this json stringify is way over complicating a fairly simple situation Commented Dec 21, 2014 at 17:43
  • 2
    you can create the table html string the way you are doing and use event delegation for event listeners Commented Dec 21, 2014 at 17:45

2 Answers 2

2

As @charlietfl suggests, you should get out of the habit of using JavaScript in your attributes, and use event delegation for this problem:

function createTable(json_txt, el_id) {
    var el = document.getElementById(el_id);
    var o = JSON.parse(json_txt);
    var s = '<table>';
    var num = numTableRows(o);  // just returns number of rows
    var tableData = [];

    for (var r=0; r < num; r++) {
        var rowData = {};

        s += '<tr class="tbl_data">';
        for (var col in o) {
            var curVal = o[col][r];
            rowData[col] = curVal;

            s += "<td data-row='" + r + "'>" + curVal + "</td>";
        }
        s += '</tr>';

        tableData.push(rowData);
    }
    s += '</table>';
    el.innerHTML = s; 

    el.addEventListener("click", function(e) {
        var targ = e.target,
            rowNum,
            data;

        if(targ && targ.nodeName == "TD") {
            rowNum = targ.getAttribute("data-row");
            data = tableData[rowNum];
            if (data) {
                onTrackShowVal(this, data);
            }
        }
    });
}

(Just as a side not, if you are using jQuery, that last part could be done like this, and the "td" string could be altered to give a more specific selector if needed):

$(el).on("click", "td", function () {
    var rowNum = $(this).data("row"),
        data = tableData[rowNum];

    if (data) {
        onTrackShowInfo(this, data);
    }
});
Sign up to request clarification or add additional context in comments.

5 Comments

The element is a div that is passed as a parameter to the function that now creates the innerHTML for it. Also, 'js' is rebuilt for every row of the table right before the onclick handler is specified. The whole table object is also passed as a parameter too in json format. However, your answer is advanced and gives me food for thought.
@DNT Please see my edit above. I haven't included the part to make the table object accessible (I'm not quite sure what you mean by that), but that should be easy enough to incorporate. If you show us the code where you are including the whole table in the JSON, I can show you how to include it here.
I edited the original code by adding a very simplified version of the 'createTable' function that uses this code. As you see one of the goals is not use globals to keep any of the tables or rows since this code will need to run in the browser for some time with different or updated data obtained from periodic ajax calls.
@DNT Please see my edit. I do strongly advise you to use something other than string concatenation. If any of the data values in o had something like </table> in it, it would break your whole page, not to mention the XSS vulnerabilities you are inviting.
Thanks, using closure to get at the table is a good solution. Much appreciated.
0

Look at this edit:

var js = {};
for (var c in o) js[c] = o[c][r];
s += "<td onclick='onTrackShowInfo(this, "+c+" )'>"; 

...

function onTrackShowInfo(el, json_row_index)
{
    var o = js[json_row_index];
    // ok, we have the row from element 'el' at this point
}

4 Comments

I'm afraid that 'js is not a global variable. It is created within a function that iterates over the whole table object, and creates the HTML, so 'js' would not be visible from the handler.
Why not create a global var?
The function that creates the table may be called to create various similar tables in other areas of the page, so encapsulation of this functionality is essential.
I would still create a master object containing the all the objects and simply refer to their indexes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.