views:

83

answers:

3

I run into this problem constantly while developing AJAX applications. Let's say I want users to be able to click on a "flag" icon associated with each comment on my site, which results in an AJAX request being sent to the server, requesting that the comment be flagged. I need to associate a comment id with the comment on the client side so that the AJAX request may communicate to the server which comment to flag.

This page explains a number of ways to annotate HTML in this manner, but none of them are very satisfactory. While I could just use an id or class attribute to associate the comment id with the flag button (e.g. id="comment_1998221"), this fails with more complex data that doesn't fit well into those attributes (e.g. arbitrary strings). Is there a best practice for this sort of thing? Every time I need to do this, I end up with some kludge like using the id attribute, a hidden form field, or worse yet a span set to display:none.

The HTML5 data-* attributes seem like a perfect solution, but I've seen a lot of animosity toward them, which makes me think that people must already have a solution they're happy with. I'd love to know what it is.

+2  A: 

This page explains a number of ways to annotate HTML in this manner, but none of them are very satisfactory.

Still, they are pretty much all you've got. Although that page isn't a terribly good summary, there are errors and it misunderstands what ‘unobtrusive’ JavaScript means.

For example it is in fact perfectly valid to put a script element inside body — just not directly inside a table element. You could put all the script fragments at the bottom of the table, or put each row in its own table, or even, with some limitations if you are intending to mutate the DOM, inside the row in question.

Setting “id="comment-123"” then scanning for all rows with an id starting with ‘comment-’ is indeed good for your particular case. For setting non-identifying extra info attributes, you could use either HTML5 data-attributes or hack it into the classname using eg. “class="comment type-foo data-bar"”. Of course both IDs and classnames have their limits about what characters you can use, but it's possible to encode any string down to valid strings. For example, you could use a custom URL-style encoding to hide non-alphanumeric characters:

<tr class="greeting-Hello_21_20_E2_98_BA">
    ...
</tr>

function getClassAttr(el, name) {
    var prefix= name+'-';
    var classes= el.className.split(' ');
    for (var i= classes.length; i-->0;) {
        if (classes[i].substring(0, prefix.length)==prefix) {
            var value= classes[i].substring(prefix.length);
            return decodeURIComponent(value.split('_').join('%'));
        }
    }
    return null;
}

var greeting= getClassAttr(tr, 'greeting'); // "Hello! ☺"

You can even store complex non-string values in this way, by encoding them JavaScript or JSON strings then retrieving them using exec (or JSON.parse where available).

However, if you are putting anything non-trivial in there it soon gets messy. That's where you may prefer comments. You can fit anything in here except the sequence '--', which is easily escaped if it happens to come up in a string.

<table>
    <tr class="comment">
        <td>...</td>
        <!-- {"id": 123, "user": 456} -->
    </tr>
</table>

function getLastComment(node) {
    var results= [];
    for (var i= node.childNodes.length; i-->0;)
        if (node.childNodes[i]==8)
            return node.childNodes[i];
    return null;
}

var user= getLastComment(tr).user;

The summary warns that this may not be guaranteed to work because XML parsers may discard comments, but then DOM Level 3 LS parsers must keep them by default, and every browser and major XML library so far does.

bobince
A: 

jQuery data API is nice for this.

Suppose you have the following DOM...

<div class="comment">
<a href="#">Flag</a>
Some text
</div>

Then, assuming you are also loading these elements by ajax, you can do

$(".comment").data('someKey', (any javascript value/object));

Then later, upon click handler to the flag, you can do...

$(".flagSelector").click(function(ev) {
    var extraData = $(this).closest(".comment").data("someKey");
    // use extraData along with your request
});

If you are generating the comments on the server side and shipping them with the initial page, you need to figure out how to initialize the data. One way would be to have unique ID-s for the comment and upon pageload, still load the custom data from the server by Ajax.

Jaanus
A: 

Here is how I would do this:

  • When rendering the page server-side, generate the flag link as a normal link, so that it would work fine if you didn't have javascript enabled.

    <a class="flag_link" href="/comment/123/flag/"><img src="flag.gif" /></a>

  • Then, in the javascript, add a click event to do this by ajax instead. I'll use jQuery for my example, but the same thing is not hard to do without it.

<script>

$('a.flag_link').click(function() {
  $.get($(this).attr('href'), function() {
    alert('you flagged this comment');
  });
});

</script>

Of course, you'll do something more user-friendly than an alert to signal success.

Christian Oudard