refactor HTML-generating JavaScript - javascript

Unfortunately on my project, we generate a lot of the HTML code in JavaScript like this:
var html = new StringBuffer();
html.append("<td class=\"gr-my-deals\">").append(deal.description).append("</td>");
I have 2 specific complaints about this:
The use of escaped double quotes (\”) within the HTML string. These should be replaced by single quotes (‘) to improve readability.
The use of .append() instead of the JavaScript string concatentation operator “+”
Applying both of these suggestions, produces the following equivalent line of code, which I consider to be much more readable:
var html = "<td class=’gr-my-deals’><a href=’" + deal.url + "’ target=’_blank’>" + deal.description + "</a></td>";
I'm now looking for a way to automatically transform the first line of code into the second. All I've come up with so far is to run the following find and replace over all our Javascript code:
Find: ).append(
Replace: +
This will convert the line of code shown above to:
html.append("<td class=\"gr-my-deals\">" + deal.description + "</td>)";
This should safely remove all but the first 'append()' statement. Unfortunately, I can't think of any safe way to automatically convert the escaped double-quotes to single quotes. Bear in mind that I can't simply do a find/replace because in some cases you actually do need to use escaped double-quotes. Typically, this is when you're generating HTML that includes nested JS, and that JS contains string parameters, e.g.
function makeLink(stringParam) {
var sb = new StringBuffer();
sb.append("<a href=\"JavaScript:myFunc('" + stringParam + "');\">");
}
My questions (finally) are:
Is there a better way to safely replace the calls to 'append()' with '+'
Is there any way to safely replace the escaped double quotes with single quotes, regex?
Cheers,
Don

Consider switching to a JavaScript template processor. They're generally fairly light-weight, and can dramatically improve the clarity of your code... as well as the performance, if you have a lot of re-use and choose one that precompiles templates.

Here is a stringFormat function that helps eliminate concatenation and ugly replacment values.
function stringFormat( str ) {
for( i = 0; i < arguments.length; i++ ) {
var r = new RegExp( '\\{' + ( i ) + '\\}','gm' );
str = str.replace( r, arguments[ i + 1 ] );
}
return str;
}
Use it like this:
var htmlMask = "<td class=’gr-my-deals’><a href=’{0}’ target=’_blank’>{1}</a></td>";
var x = stringFormat( htmlMask, deal.Url, deal.description );

As Shog9 implies, there are several good JavaScript templating engines out there. Here's an example of how you would use mine, jQuery Simple Templates:
var tmpl, vals, html;
tmpl = '<td class="gr-my-deals">';
tmpl += '#{text}';
tmpl += '</td>';
vals = {
href : 'http://example.com/example',
text : 'Click me!'
};
html = $.tmpl(tmpl, vals);

There is a good reason why you should be using the StringBuffer() instead of string concatenation in JavaScript. The StringBuffer() and its append() method use Array and Array's join() to bring the string together. If you have a significant number of partial strings you want to join, this is known to be a faster method of doing it.

Templating? Templating sucks! Here's the way I would write your code:
TD({ "class" : "gr-my-deals" },
A({ href : deal.url,
target : "_blank"},
deal.description ))
I use a 20-line library called DOMination, which I will send to anyone who asks, to support such code.
The advantages are manifold but some of the most obvious are:
legible, intuitive code
easy to learn and to write
compact code (no close-tags, just close-parentheses)
well-understood by JavaScript-aware editors, minifiers, and so on
resolves some browser-specific issues (like the difference between rowSpan and rowspan on IE)
integrates well with CSS
(Your example, BTW, highlights the only disadvantage of DOMination: any HTML attributes that are also JavaScript reserved words, class in this case, have to be quoted, lest bad things happen.)

Related

replacing specific characters in strings

pretty simple:
i have mathematical problems stored in a DB, like 3+6, 5*3, 4-2 etc.
i want the output to show proper mathematical ×s instead of * (and ÷ instead of /).
but they have to be stored in the DB with * and / for obvious reasons (being "normal" characters vs. html entities in DB and especially for mathjs to be able to solve them from string)
so i am looking for a way to change them in the html output.
first i thought about something with css, but that would probably mean i'd have to have a class for them (or is it possible?).
then i thought i could do it with jS/jQuery. but it feels overly complicated at first.
how would you do this?
(server is running node.js + jade. the strings come from the db and are rendered directly on the page, so i need a way to change the symbols "afterwards")
You don't necessarily have to store the characters as HTML entities. So long as you include the appropriate charset meta tag, then you'll be able to use unicode symbols in your page.
<meta charset='utf-8'>
If you can't store them in the database as unicode, then you'll have to programatically fix the strings afterwards.
var replacementSymbols: {
'*': '×',
'/': '÷'
};
function replaceInString(equation) {
var targetSymbols = Object.keys(replacementSymbols);
return targetSymbols.reduce(function(string, symbol) {
var replacement = replacementSymbols[symbol];
return string.replace(symbol, replacement);
}, equation);
}
What you're attempting to do is not really possible with CSS.
You could use JavaScript's string replace method to achieve this instead.
for instance:
var str = "5*6"
var res = str.replace("*", "×");
http://www.w3schools.com/jsref/jsref_replace.asp
When you're writing your html, you can insert a <span class='multiply'></span> for each *
Then you can do:
$("span.multiply").html("×");
And then you you would do the same with division, and so on.
EDIT
You could also try something like this:
var newHtml = $('body').html().replace(/\*/g, "×");
$('body').html(newHtml);
Unless you're using something like Angular and a filter to parse the string, something like this might be the best option. If you're not able to insert classes before the page is rendered, you may need to see if the rendered data is wrapped in something like <pre/> tag so that you can use the appropriate selector:
template
<div class="math-expression">
3 * 4 = 12
</div>
logic
document.querySelectorAll('.math-expression').forEach(
function( elem, i, arr){
s = elem.textContent
elem.textContent = s.replace( /\*/g, 'x' )
}
)
note
I didn't test this as I normally just use Angular filters for these types of text renderings.

ES6 when to use String.raw over default template literal

I'm trying to concatenate a bunch of strings to build a query string in Javascript. Previously I have achieved this through ugly string concatenation:
var queryString = "?action=" + actionValue + "&data=" + dataValue";
But with ES6 I see that there are new methods provided that could help me achieve the same result with much nicer looking code, like string interpolation in C# 6:
string s = $"action={actionValue}&data={dataValue}"
I have tested with both default template literal and String.raw and although the syntax for each is slightly different, they both work. I'm leaning towards using String.raw in my final copy as it doesn't allow for the string to be tagged and thus tinkered with in the future like the default template literal does.
Although it does say in the MDN docs that String.raw basically calls the default template literal method but I like the syntax of String.raw better... I am calling the String.join method inside the curly braces of my string that I am formatting so maybe that is a misuse of String.raw.
Are any ES6 wizards out there able to enlighten me and provide reasons for one over the other?
My code:
var defaultTemplateStringUrl = `#Url.Action("DownloadMultiple")?inIds=${inData.join(',')}&outIds=${outData.join(',')}&truckId=${truckId}`;
var rawStringUrl = String.raw `#Url.Action("DownloadMultiple")?inIds=${inData.join(',')}&outIds=${outData.join(',')}&truckId=${truckId}`;
window.open( /*url goes here*/);
A template literal produces a string. If you use String.raw, you will get the raw form of that string:
`a\tb`; // "a b"
String.raw`a\tb`; // "a\tb"
So you should use String.raw only when you want the raw form.
No, using String.raw makes no sense for you.
You should rather write your own template tag that does the necessary URL encoding and handles arrays in a manner you like.
function myUrl(parts) {
var url = parts[0];
for (var i=1; i<arguments.length; i++) {
var val = arguments[i];
if (Array.isArray(val))
val = val.join(",");
url += encodeURIComponent(val);
url += parts[i];
}
return url;
}
Then you can use
window.open(myUrl`#Url.Action("DownloadMultiple")?inIds=${inData}&outIds=${outData}&truckId=${truckId}`);

how to embed value with an apostrophe in querystring

I have the following javascript:
tr.append("<a href='add_widget.html?id=" + data[i].id + "&pg=" + data[i].page_number + "&dest=" + data[i].dest + "&name=" + data[i].name.replace("'","\\'") + "'</a><button class='btn btn-xs btn-primary'>Edit</button> </td>");
The code in question has to do with the name field.
If I have a name like "John Doe" when I click on the hyperlink created by the above javascript, the new page's querystring has the full name.
However, if I try to pass a name like "John's stuff", the above logic creates a query string variable that looks like this:
&name=John\
How can I change the above code so that the entire string "John's stuff" is passed to the add_widget.html page?
Thanks.
replace("'","%27")
try http://meyerweb.com/eric/tools/dencoder/ it's an online URL encoder/decoder.
When you're trying to "protect" characters, you have to keep in mind what you're protecting them from. In this case, there are two interpreters you have to worry about:
You're building HTML, so you have to worry about the HTML parser;
You're building a URL, so you have to worry about how the browser and the server will parse the URL.
To deal with the first problem, you can replace the quotes with the HTML entity equivalent ('). To deal with the second, you can use encodeURIComponent().
I think you'd want to do the encodeURIComponent() call first, to avoid having the HTML entity notation get messed up. The entity notation will be gone after the HTML parser is finished with the string anyway:
function qEncode(str) {
return encodeURIComponent(str).replace(/'/g, "'");
}
To use that:
tr.append("<a href='add_widget.html?id=" +
qEncode(data[i].id) + "&pg=" +
qEncode(data[i].page_number) + "&dest=" +
qEncode(data[i].dest) + "&name=" +
qEncode(data[i].name) +
"'</a><button class='btn btn-xs btn-primary'>Edit</button> </td>"
);
Note that you could also encode double-quote characters too.
A totally different way of working around this problem would be to build the DOM content with DOM APIs. By doing that, you'd completely avoid the HTML parser, and you'd just need encodeURIComponent().
You need to think, what will be interpreting my code, so what do I need to escape for?
Your code will be interpreted by the HTML Interpreter in the browser
Your code will be interpreted as a URI
This means you need to escape/encode them in reverse order. Luckily JavaScript provides a URI encoder as encodeURIComponent, but it doesn't provide a HTML one (probably as we have DOM Methods) but it isn't too hard to implement for important characters, e.g.
function html_encode(str) {
var re_chars = /[<>'"]/g;
function replacer($0) {
return '&#' + $0.charCodeAt(0) + ';'
}
return str.replace(re_chars, replacer);
}
// example follows
html_encode('<foo bar="baz">'); // "<foo bar="baz">"
So for you,
attrib_value = html_encode(/* ... + */ encodeURIComponent(data[i].name) /* + ... */ );
For completeness,
function html_decode(str) {
var re = /&(?:#\d{1,3}|amp|quot|lt|gt|nbsp);/g, // notice extra entities
d = document.createElement('div');
function replacer($0) {
d.innerHTML = $0;
return d.textContent;
}
return str.replace(re, replacer);
}
// and an example
html_decode('<foo bar="baz">'); // "<foo bar="baz">"
Using escape(data[i].name) instead of data[i].name.replace("'","\\'"), will solve your problem.

escaping a JS reserved word (already double encapsulated)

I have a JS function which is generated by some PHP, the function call is below:
onClick=openPopup('".$row['imgname']."','".$row['adtitle']."','".$row['adviews']."')
Now this works unless the value of $row['adtitle'] contains a JS keyword. The one that brought the bug in my code to my attention was the word 'THIS'. Would there be a way to escape these values, I can't figure it out as I have already used a lot of encapsulation in this call.
Thanks in advance.
EDIT:
openPopup('efc86f7223790e91f423ef1b73278435.jpg','THIS IS A TEST ADVERT 12345678','2')
This call does not work.
openPopup('eada91a6c1197d2f2320e59f45d8ca6b.jpg','is a test','2')
however this one does work..
only thing I could figure was the THIS as when looking at the source, the text following THIS is highlighed differently.
Edit 2 : Here is my function:
function openPopup(imgname,adtitle,adviews) {
document.getElementById('popup').style.display = 'block';
document.getElementById('delimg').src = 'imgstore/' + imgname;
document.getElementById('delAdTitle').innerHTML = adtitle;
document.getElementById('delAdViews').innerHTML = adviews;
document.getElementById('confirm').onclick = function() {
location.href = '?delete=1&id=' + imgname;
}
}
Maybe it’s just a question of proper formatting:
$onclick = 'openPopup('.json_encode($row['imgname']).','.json_encode($row['adtitle']).','.json_encode($row['adviews']).')';
echo 'onClick="'.htmlspecialchars($onclick).'"';
Note that we’re abusing json_encode here to quote the JavaScript string literals. Although we shouldn’t as strictly speaking JSON strings are not a subset of JavaScript strings.

call a javascript function based on a regex match

In the following string, i would like to replace [choice:a3d] with an appropriate drop down menu. I am not sure of how the options need to be formatted just after the colon and before the closing square brace.
string = "operation [number] [choice:a3d] [number]";
I am not really sure where the .replace function comes from but the code I am working with has jquery imported.
string.replace(/(?:\[choice\:)(\w+)(?:\])/g, choice_func);
where:
function choice_func(choice_lists, listname, default_opt)
{
console.log("choice_lists: "+choice_lists); // [choice:a3d]
console.log("listname: "+listname); // a3d
console.log("default_option: "+default_opt); // 67
var list = choice_lists[listname];
return '<span class="string ' + listname + ' autosocket"><select>' +
list.map(function(item)
{
if (item === default_opt){
return '<option selected>' + item + '</option>';
}else{
return '<option>' + item + '</option>';
}
}).join('') +'</select></span>';
}
needless to say the code fails with error "Uncaught TypeError: Cannot call method 'map' of undefined"
also where do parameters to the function come from?
Don't assume that any of this code is correct....
This looks to me like it would be simpler for you to just use whatever code you need to use to compute the replacement string and then just do a replace using the string instead of the regex function. Regex functions are best used when you need to examine the context of the match in order to decide what the replacement is, not when you are just doing a replacement to something that can be computed beforehand. It could be made to work that way - there's just no need for that level of complexity.
When using regex callbacks, the callback gets multiple parameters - the first of which is the match string and there are a number of other parameters which are documented here. Then, you must return a string from that function which is what you want to replace it with. You function is pretending that it has three parameters which it does not and thus it won't work.
I suggest that you compute the replacement string and then just do a normal text replacement on it with no regex callback function.
If you can be clearer about what the initial string is and what you want to replace in it, we could give you some sample code that would do it. As you've shown in your question, your string declaration is not even close to legal javascript and it's unclear to me exactly what you want to replace in that string.
The pseudo code would look like this:
var menuStr = "xxxxxxx";
var replaceStr = choice_func(lists, name, options);
menuStr = menuStr.replace(/regular expression/, replaceStr);

Categories

Resources