views:

105

answers:

3

Hey guys. I'm building a web application (using prototype) at the moment that requires the addition of large chunks of HTML into the DOM. Most of these are rows that contain elements with all manner of attributes.

Currently I keep a blank row of html in a variable and

var blankRow = '<tr><td>'
    +'<a href="{LINK}" onclick="someFunc(\'{STRING}\');">{WORD}</a>'
    +'</td></tr>';

function insertRow(o) {
    newRow = blankRow
        .sub('{LINK}',o.link)
        .sub('{STRING}',o.string)
        .sub('{WORD}',o.word);
    $('tbodyElem').insert( newRow );
}

Now that works all well and dandy, but is it the best practice?

I have to update the code in the blankRow when I update code on the page, so the new elements being inserted are the same. It gets sucky when I have like 40 lines of HTML to go in a blankRow and then I have to escape it too.

Is there an easier way? I was thinking of urlencoding and then decoding it before insertion but that would still mean a blankRow and lots of escaping.

What would be mean would be a eof function a la PHP et al.

$blankRow = <<<EOF
text
text
EOF;

That would mean no escaping but it would still need a blankRow.

What do you do in this situation?

SOLVED

Ended up using a DOMBuilder in prototype. No other libraries were needed:

$w('a div p span img table thead td th tr tbody tfoot input').each(function(e) {
        window['$' + e] = function() {
            return new Element(e, arguments[0]);
        }
});

newPart = $div({id: 'partition-4'})
    .insert( $p()
        .insert('<b>Stuff</b>')
    )
    .insert( $p({
        id: 'a-p'})
        .insert('<b>More stuff</b>')
    );

$('parentDiv').insert(newPart);

See my solution here or here

+4  A: 

Hi,

You do it using a good way, and that's pretty fast considering other manners.

An alternate, supposely clean solution is to build in javascript the DOM elements you are to use, by calling several

document.createElement(tagName);

But your code will rapidly grow, in my thought, for nothing.

Another one, my favorite way to achieve DOM creation, is to place inside the HTML body the code you want to copy, give it a className and / or an id like "template" which will additionally ( using css ) hide it, and then process it as needed on event by getting the element back, cloning it, and setting property you want before appending where it belongs

dader51
Yeah I think hiding it in the body sounds like the best idea...
hamstar
+1  A: 

If I understand you well, your question is about initializing large strings the way you'll do that in PHP (or Perl for that matter)? For long (multiline) string I use:

var blankRow = [
     'a line',
     'another line',
     'still more lines',
     'lines lines lines',
     '... etc'
    ].join('')

You can also use a backslash for continuation, but that can get kind of messy:

var blankRow = 'a line\
another line\
still more lines\
lines lines lines\
... etc';

EDIT based on comment: I'm lazy too! So I use this for escaping:

function qs(str){
    str = str || this || '';
    return "'"+str+"'";
}

function qd(str){
    str = str || this || '';
    return '"'+str+'"';

}

String.prototype.qs = qs;
String.prototype.qd = qd;

// applied:
var blankRow = [
         'a line',
         'another '+qd('line'),
         'still more lines',
         'lines '+'lines'.qs()+ ' lines',
         '... etc'
        ].join('');

Problem with being lazy: it's pretty hard work to persist ones lazyness

KooiInc
Ahh thats a smart way to do it. The reason I mentioned the PHP example is that you don't need to escape anything in the middle ;) <-- lazy
hamstar
+4  A: 

is it the best practice?

No. In fact you've got HTML-injection problems leading to bugs, and in the worst case where the injected strings may contain user-submitted content, XSS security holes.

When you put plain text content and attribute values into an HTML string, you must HTML-encode them. In PHP, you have to call htmlspecialchars() on strings going into HTML to do it. In JavaScript, you don't get a built-in HTML-escaping function, so you have to make your own, eg. by using s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;') on the string going into the HTML.

onclick="someFunc(\'{STRING}\');"

That's a whole new level of escaping mess. In a JavaScript string literal inside an event handler attribute, you would have to JS-encode the string (\-escaping ' and \ plus a few Unicode characters for completeness) and then HTML-encode the results. Otherwise the string can break out of its string-literal delimiter and inject arbitrary JS code. Avoid inline event handler attributes in all cases, but especially in templating.

Creating page content with HTML strings sucks. You are very likely to make escaping errors and compromise the security of your application. Use DOM-like methods instead and you don't have to worry about this. You seem to be using jQuery, so try the jQuery 1.4 element creation shortcuts:

$('<tr>').append(
    $('<td>').append(
        $('<a>', {
            href: o.link,
            text: o.word,
            onclick: function() {
                someFunc(o.string);
            }
        })
    )
);

or, keep your blank row actually inside the document as HTML, but then hide it (display: none) or detach it from the document at start time (removeChild or jQuery detach). Then when you want a new row, clone the blank row and make the changes you need:

var blankRow= $('#blankRow').detach();

    ...

var newRow= blankRow.clone();
var link= newRow.find('td>a');
link.attr('href': o.link);
link.text(o.word);
link.click(function() {
    someFunc(o.string);
});

If you must create content from string templates, ensure your templating function HTML-escapes every replacement by default, and attach events by selecting nodes inside the parsed content to call click(function() { ... }) on. Or use event delegation (eg. jQuery live()) to handle events without having to bind to new nodes when added.

bobince
Man... what a mission. Thanks for your help. Really wish I was using jquery instead of prototype now :(
hamstar
@hamstar: Oops! I should have spotted that was a Prototype ID selector `$` not jQuery. D'oh. Prototype has a similar (IMO: in some ways better) shortcut for creating elements, `new Element`. See http://prototypejs.org/2007/5/12/dom-builder
bobince
Sweet! I might have to give that a go :) Cheers
hamstar
Ended up doing it this way, worked awesomely! Took a while to figure it out. If anyone would like to see the code, see http://gist.github.com/402668 or http://pastebin.com/Du5EjjjjThanks a lot for the info bobince!
hamstar
I would strongly avoid setting event handler attributes from strings. I can't remember if Prototype works around the many IE bugs that would normally stop this working, but it's just pretty bad practice in any case; calling `.observe` as you do later on in the code is generally much better. Also `<a href="javascript:void(0)">` is an unpleasantness. Consider a clickable span or a button (styled to not look like a button if that's what you want). See http://stackoverflow.com/questions/1291942/what-does-javascriptvoid0-mean for discussion on this issue.
bobince