22

Is there a convenient way to quote a large block of HTML that has both single and double quotes in JavaScript?

Is there anything like a HERE-doc <<EOF, a multi-quote character """, or custom delimiters q{}?

Any creative or inventive solutions to this problem?

ajreal
  • 46,720
  • 11
  • 89
  • 119
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • 1
    possible duplicate of [Creating multiline strings in JavaScript](http://stackoverflow.com/questions/805107/creating-multiline-strings-in-javascript) – Adam Katz Mar 14 '15 at 03:01
  • 1
    ECMAscript 6, now the standard, allows use of back-ticks for multi-line strings. An example of this, pretending `\n` is a literal line break, would be `var foo = \`multi \n line \n string\`` – Adam Katz Mar 14 '15 at 03:03

11 Answers11

20

Some people don't like this, so be prepared for scorn and derision, but one trick is to dump your "big block of stuff" into a <script language="text"> block:

<script id='blockOfStuff' language="text">
  Hi this is random stuff
  <h1>Including HTML markup</h1>
  And quotes too, or as one man said, "These are quotes, but
  'these' are quotes too."
</script>

John Resig has used that technique (or that abomination, if you prefer) for examples of his templating mechanism.

You can get at the contents with "innerText" or "innerHTML" as appropriate, or through the services of your favorite framework.

edit — note that via jQuery (contrary to what I said in a comment below) .text() does not work, though I think it should. Use .html() instead.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 2
    ++, I'm amused. I'm not even that offended. Though in this case for SEO reasons I would prefer not to have the block of text in the html (because a dynamic action is brining it into play it isn't by default applicable to the page). – Evan Carroll Jun 01 '10 at 22:14
  • 1
    Unlike Andy's funny answer, does this work cross-browser? Oh, and as I said many times this day, the `language` attribute of ` – Marcel Korpel Jun 01 '10 at 23:09
  • @Marcel well I *think* it does; I haven't done a lot of testing. I kind-of assumed that if Mr. Resig was using it in blog posts etc. that it was probably fairly "safe." I too wonder about "language" esp. w.r.t. future HTML5 support. – Pointy Jun 01 '10 at 23:22
  • @Marcel: I'm glad my answer amused you, I was smiling to myself whilst writing it. I think @Pointy has the only production-viable solution here, and I can't see why it wouldn't work in all browsers it certainly makes sense that it would, even with a totally bogus language or mime-type applied as long as neither were empty or an actual supported scripting language. – Andy E Jun 01 '10 at 23:26
  • @Pointy: not to nit-pick, but "innerText()" and "innerHTML()"? Is there an "in" joke I'm not getting here or did someone promote those properties to functions? :-) – Andy E Jun 01 '10 at 23:28
  • 1
    @Andy E well I think Firefox is pretty loosey-goosey with "innerHTML" and will hand back the contents of a ` – Pointy Jun 02 '10 at 00:17
  • @MatrixFrog you can also do `script type="text/html"`. OTOH trying to set up your text as an external file (`script src='external.txt' type='text/html'`) doesn't seem to work. – LetMyPeopleCode Feb 10 '12 at 17:31
9

Not supported natively.

But since we're talking ways to make it work, here's one that (in my experience) does:

<script type="text/javascript">  

    var name = "Mud";

    var str = "\
        My name is " + name + "\
        not to be confused with Bill\
        or Jack or Pete or Dennis\
        my name is " + name + " and it's always been\
    ";

    alert("Les'n one: " + str);

</script>

Those back-slashes'll do the trick. Just make sure to backslash-escape any double quotes in your string since the whole block is quoted with them.

Note that this doesn't retain newlines, you have to insert them manually as "\n" prior to trailing slash on each line. On the other hand, any whitespace indentation at the beginning of each line will be included in the output.

Really this works best when you have to declare a long multiline string in a script (e.g. XML), not as good when you need to keep that string formatted exactly the way you define it.

Cheers

Madbreaks
  • 19,094
  • 7
  • 58
  • 72
4

JavaScript can't do it but CoffeeScript, which is a thin layer on top of JavaScript, can.

Follow the link and scroll down to "Multiline Strings and Heredocs".

Jakob
  • 24,154
  • 8
  • 46
  • 57
  • > _Link-only answers can become invalid if the linked page changes._ ...which is exactly what's happened. Here's a working url: http://coffeescript.org/#strings – aidan Oct 22 '15 at 00:39
3

I remember seeing a clever solution a while ago that used multi-line comments in a function:

(function () {
   /*
      "This is an example of a multi-line string.  It's really just a mult-line
      comment, and it's wrapped in quote marks.  You might also notice the 
      apostrophe's ;-)"; 
   */
});

Note: that last apostrophe is intentionally incorrect ;-P

The trick is to call the function's toString() method and parse out the multi-line comment using a regular expression. Clever, but much like Pointy's suggestion, a bit of an abomination.

I didn't actually think the question to be looking for a seriously viable method for production uses -- my own fault for jumping to conclusions -- I'm not really sure why you wouldn't just escape the relevant string literal delimiters. As Tim Down pointed out in the comments below, ECMAScript 3rd edition defines toString() for functions as being implementation dependant.

For funsies, I decided to check out browser compatibility and this method is feasible in IE, Opera, Safari and Chrome but not Firefox, which does not include comments in the returned string. http://jsfiddle.net/2yvXG/

Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 1
    Seriously though, this is really brittle: how a function converts itself to a string is unspecified, and what works in current browsers may not work in future browsers. From the ECMAScript 3 spec: *"15.3.4.2 Function.prototype.toString ( ). An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Note in particular that the use and placement of white space, line terminators, and semicolons within the representation string is implementation-dependent."* – Tim Down Jun 01 '10 at 22:24
  • Doesn't work for me Firefox 3.5 `javascript:alert ( (function () { /*foo "" '" bar "' '' */ } ).toString() )` – Evan Carroll Jun 01 '10 at 22:27
  • @Evan: I'll admit, I didn't go as far as testing it in every browser :-) – Andy E Jun 01 '10 at 22:32
  • 3
    @Tim: He asked for creative or inventive solutions, I think this qualifies at least for one of those ;-) In any case, I didn't intend the answer to be a serious suggestion for use in any production environment and probably should have mentioned that somewhere! – Andy E Jun 01 '10 at 22:36
  • @Evan: btw, it works in Opera, Chrome, Safari and IE. I guess that doesn't help you much if you want it to be cross browser, though, sorry. – Andy E Jun 01 '10 at 22:49
  • 1
    @Andy: Sure, it's creative. No criticism from me on that score :) At least you read the question fully, which is more than I managed. – Tim Down Jun 01 '10 at 23:15
3

ECMAscript 6, now the standard, allows use of back-ticks (accent grave) to quote multi-line literal strings. Unfortunately, this is not supported in IE 11, so it should not be used on most websites. (Credit: Adam Katz, above)

Example:

var str=
`Line 1
Line 2`;
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
David Spector
  • 1,520
  • 15
  • 21
  • This is actually the most correct answer to the original question. Checked this as of IE 11.0.45 and it does work on that browser as well. – bearvarine Aug 24 '17 at 16:58
1

HereDoc For JavaScript

FuncToHereDoc ("delemiter", uncalled function with commented HEREDOC)

function FuncToHereDoc(here,str) {
    var reobj = new RegExp("/\\*"+here+"\\n[\\s\\S]*?\\n"+here+"\\*/", "m");
    str = reobj.exec(str).toString();
    str = str.replace(new RegExp("/\\*"+here+"\\n",'m'),'').toString();
    return str.replace(new RegExp("\\n"+here+"\\*/",'m'),'').toString();
}

Usage:

FuncToHereDoc("HERE", MyHereDoc);

function MyHereDoc(){
/*HERE
<p>
This is written ing the HEREDOC, notice the multilines :D.
</p>
<p>
HERE
</p>
<p>
And Here
</p>
HERE*/
}
Zv_oDD
  • 1,838
  • 1
  • 18
  • 26
  • This just makes the assumption that the `func.toString();` returns the source code. [That was identified as being unsafe below](http://stackoverflow.com/a/2953780/124486) – Evan Carroll Oct 11 '12 at 01:45
  • @EvanCarroll assuming vertical order of answers on SO is also unsafe ;) – Madbreaks Feb 25 '15 at 04:46
0

Based on previous answers and different use cases, here is a small example:

https://gist.github.com/lavoiesl/5880516

/*!
 * Extract a function's comment, useful to have multiline string
 * @link https://gist.github.com/lavoiesl/5880516
 *
 * Don't forget to use /*! to avoid the comment being removed in minification 
 */

function extractFuncCommentString(func) {
  var matches = func.toString().match(/function\s*\(\)\s*\{\s*\/\*\!?\s*([\s\S]+?)\s*\*\/\s*\}/);
  if (!matches) return false;

  return matches[1];
}

var myString = extractFuncCommentString(function(){/*!
  <p>
    foo bar
  </p>
*/});
sebastien
  • 418
  • 4
  • 7
  • This just makes the assumption that the func.toString(); returns the source code. [That was identified as being unsafe below](http://stackoverflow.com/a/2953780/124486) – Evan Carroll Jun 27 '13 at 21:30
  • It says that it returns an implementation-dependent representation of the function, a FunctionDefinition, but where the _whitespace_ is not sure. My wrap-up seems to take care of any whitespace issues, even if minimized. – sebastien Jul 03 '13 at 06:05
0

I was interested in this question because I want to use javascript to add a new row to an edit screen (e.g., for multiple phone numbers). (i could use ajax for this but wanted to avoid an extra server request.)

I like Pointy's answer about using the tag to enclose blocks of html you want to use:

<script id='blockOfStuff'>
  Hi this is random stuff
  <h1>Including HTML markup</h1>
</script>

But Firefox and Chrome complained about syntax errors when I tried this. My solution was to change that 'script' tag to a 'div', hide its display from users via css, and move it within the body. e.g.:

<div style="display: none;" id="new_row_innerhtml">
  <td><a href="#" onclick="removeCurrentRow(this); return false;">Remove</a></td>
  <input type="hidden" name="id[]" value="new" />
  <td><input name="name[]" type="text" /></td>
</div>

That removed the syntax errors.

Here's how I used that block:

I had an "Add" link that called the appendRow function:
<a href="#" onclick="appendRow(this); return false;">Add</a>

here's the appendRow function:

function appendRow() {
  var tbl = document.getElementById('my_table');
  var row = tbl.insertRow(-1); // add to end of table
}
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
durian
  • 11
  • I used this solution as a good work around. This has allowed me to put lots of text that would have caused all kinds of JS heartache into my application without any problem. – yougotiger Sep 24 '15 at 00:00
0
// js heredoc - http://stackoverflow.com/a/32915549/466363
// a function with comment with eval-able string, use it just like regular string

function extractFuncCommentString(func,comments) {
  var matches = func.toString().match(/function\s*\(\)\s*\{\s*\/\*\!?\s*([\s\S]+?)\s*\*\/\s*\}/);
  if (!matches) return undefined;
  var str=matches[1];

   // i have made few flavors of comment removal add yours if you need something special, copy replacement lines from examples below, mix them
  if(comments===1 )
  {
   // keep comments, in order to keep comments  you need to convert /**/ to / * * / to be able to put them inside /**/ like /*    / * * /    */
   return (
    str
   .replace(/\/\s\*([\s\S]*?)\*\s\//g,"/*$1*/") //       change   / * text * /  to   /* text */ 
   )
  }
  else if(comments===2)
  {
   // keep comments and replace singleline comment to multiline comment
   return (
    str
   .replace(/\/\s\*([\s\S]*?)\*\s\//g,"/*$1*/") //       change   / * text * /  to   /* text */ 
   .replace(/\/\/(.*)/g,"/*$1*/")          //           change   //abc to  /*abc*/
   )
  }
  else if(comments===3)
  {
   // remove comments
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"") //       match / * abc * /
      .replace(/\/\/(.*)/g,"")             // match //abc
     )
  }
  else if(comments===4)
  {
   // remove comments and trim and replace new lines with escape codes
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"") //       match / * abc * /
      .replace(/\/\/(.*)/g,"")             // match //abc
      .trim() // after removing comments trim and:
      .replace(/\n/g,'\\n').replace(/\r/g,'\\r') // replace new lines with escape codes. allows further eval() of the string, you put in the comment function: a quoted text but with new lines
     )
  }
  else if(comments===5)
  {
   // keep comments comments and replace strings, might not suit when there are spaces or comments before and after quotes 
   // no comments allowed before quotes of the string
   // url html links
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"/*$1*/") //       change   / * text * /  to   /* text */
      .replace(/\/\/(.*)/g,"/*$1*/")          //           change   //abc to  /*abc*/
      .trim() // trim space around quotes to not escape it and:
      .replace(/\n/g,'\\n').replace(/\r/g,'\\r') // replace new lines with escape codes. allows further eval() of the string, you put in the comment function: a quoted text but with new lines
     )
  }
  else if(comments===6)
  { // good for html with links
   // remove comments and trim and replace new lines with escape codes
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"") //       match / * abc * /
      .trim() // after removing comments trim and:
      .replace(/\n/g,'\\n').replace(/\r/g,'\\r') // replace new lines with escape codes. allows further eval() of the string, you put in the comment function: a quoted text but with new lines
     )
  }
  else 
  return str
}

example

var week=true,b=123;
var q = eval(extractFuncCommentString(function(){/*!

// this is a comment     


'select 

/ * this
is a multiline 
comment * /

 a
,b  // this is a comment  
,c
from `table`
where b='+b+' and monthweek="'+(week?'w':'m')+'" 
//+' where  a=124
order by a asc
'
*/},4));

with cache: - make a simple template function, and save the function:

var cache={};
function myfunction(week,a){


    if(!cache.myfunction_sql1) eval('cache.myfunction_sql1=function(week,a){return ('+extractFuncCommentString(function(){/*!
'select 

/ * this
is a multiline 
comment * /

 a
,b  // this is a comment  
,c
from `table`
where b='+b+' and monthweek="'+(week?'w':'m')+'" 
//+' where  a=124
order by a asc

'*/},4)+')}');
    q=cache.myfunction_sql1(week,a);
    console.log(q)
}
myfunction(true,1234)

simple text (not eval'd):

//var cache={};
function myfunction2(week,a){


    if(!cahce.myfunction_sql2) cahce.myfunction_sql2=extractFuncCommentString(function(){/*!

some multiline text
with <html>
and a link://to.url
and a / * comment * / 
*/},6);
    q=cahce.myfunction_sql2;
    console.log(q)
}
Shimon Doodkin
  • 4,310
  • 34
  • 37
0

I actually worked out a kludgy variant similar to user742675, where you put the text in a div, then set its visibility to none, and pull the contents. But just quoting a lot of HTML wasn't enough, so I added a functionality that picked up all the variables you'd declared in your script, so if you had var a = 'Steve', any instance of $a in your heredoc text would be rendered as 'Steve'.

<script type="text/javascript">
// set variables named andy and handy, which we can use as $andy and $handy in our text

var andy = "Fred Flintstone";
var handy = "Steve Austin";

function hereDoc(divid){
var obj = window; // gets an object containing all the variables
var str = document.getElementById(divid).innerHTML; // gets the HTML block
for(var i in obj){

/* the for loop recurses through all the objects in the page - remember strings are objects in Javascript */

if((typeof(obj[i])=="string")||(typeof(obj[i])=="number")){

/* Type of makes sure it only executes replacement for strings and numbers. The function worked without this test in Firefox and Safari, but threw errors on Opera until it was added. */

myregex = new RegExp('\\$'+i,"g");

/* To replace globally, you need to use a regular expression and use the "g" option, but within the string.replace() method, the regular expression is unquoted, so you can't use a variable in it directly. So we create it and assign it to a RegExp object that works in the string.replace() method. */

str = str.replace(myregex, obj[i]);

/* we replace instances of the variable name with a dollar sign before it with the variable's value */
}
}
return str;

/* and when the loop is done, we return the processed text to be used however needed */

}

function gotoit(){

/* fill the "steve" div with the processed contents of the "randy" div. */

document.getElementById("steve").innerHTML = hereDoc("randy");
}

</script>

<a href="javascript:gotoit();">Run the script</a>
<div id="randy" style="display:none;">
The interesting thing about $andy is that he's not nearly as popular with young kids as $handy.<br><br>

What I really find 'interesting' is that this "multiline" thing works as well as $handy's bionic arm. <br><br>
</div>
<div id="steve"></div>
LetMyPeopleCode
  • 1,895
  • 15
  • 20
-1

Here is your answer. In the body of your page make a span or div, it does not matter which, with a unique id and put all the text you want in it with all the quotes you want. Make the style of the span or div "display:none; visibility:hidden;". Then when you want it get the DOM object from the id and retrieve the innerHTML to do with what you will.

  • Op asked for a JS solution, not a DOM solution. What's the point of setting `visibility:hidden;` after you've already set `display:none;`? – Madbreaks Feb 04 '14 at 17:33