Javascript error when html passed in JSON packet
I am going mad trying to resolve this issue. I have implemented a Coldfusion Ajax-like File Upload that uploads a file using a hidden frame and then grabs the contents returned by the frame. It is based on this article: http://www.bennadel.com/blog/1244-ColdFusion-jQuery-And-AJAX-File-Upload-Demo.htm
So, everything is working great, EXCEPT when I send back HTML in the data. So, if you look in the code below, I have provid开发者_JAVA技巧ed the action page that streams data back to the hidden frame. The first line works, the second line with html does not.
<!--- Create the return HTML. Remember, we are going to be treating the BODY of the returned document as if it were a JSON string. --->
<cfsavecontent variable="strHTML">
<cfset var sResponse = {} />
<!--- THIS WORKS --->
<cfset sResponse = {SUCCESS = true, ERRORS = [], DATA = "Hello World", RETURNID=""} />
<!--- THIS DOES NOT WORK --->
<cfset sResponse = {SUCCESS = true, ERRORS = [], DATA = #HtmlEditFormat('<div>Hello World</div>')#", RETURNID=""} />
<cfoutput>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>#SerializeJSON( sResponse )#</body>
</html>
</cfoutput>
</cfsavecontent>
<!--- Create binary response data. --->
<cfset binResponse = ToBinary( ToBase64( strHTML ) ) />
<!--- Tell the client how much data to expect. --->
<cfheader name="content-length" value="#ArrayLen( binResponse )#" />
<!--- Stream the "plain text" back to the client .--->
<cfcontent type="text/html" variable="#binResponse#" />
The javascript for doing all of this is below. The line that it errors on when HTML is passed back is:
// Assuming that our return data is in JSON format, evaluate the body html to get our return data. var objData = eval( "(" + jBody.html() + ")" );
//ShareForm Photo Upload Form Process (simulated Ajax)
$(document).ready(function() {
// Attach an event to the submit method. Instead of submitting the actual form to the primary page, we are going to be submitting the form to a hidden iFrame that we dynamically create.
$( "#frmShareForm" ).submit(
function( objEvent ){
var jThis = $( this );
var photoUploadAction = $("#photoUploadAction").val();
// Create a unique name for our iFrame. We can do this by using the tick count from the date.
var strName = ("uploader" + (new Date()).getTime());
// Create an iFrame with the given name that does not point to any page - we can use the address "about:blank" to get this to happen.
var jFrame = $( "<iframe name=\"" + strName + "\" src=\"about:blank\" />" );
// We now have an iFrame that is not attached to the document. Before we attach it, let's make sure it will not be seen.
jFrame.css( "display", "none" );
// Since we submitting the form to the iFrame, we will want to be able to get back data from the form submission.
// To do this, we will have to set up an event listener for the LOAD event of the iFrame.
jFrame.load(
function( objEvent ){
// Get a reference to the body tag of the loaded iFrame. We are doing to assume that this element will contain our return data in JSON format.
var objUploadBody = window.frames[ strName ].document.getElementsByTagName( "body" )[ 0 ];
// Get a jQuery object of the body so that we can have better access to it.
var jBody = $( objUploadBody );
// Assuming that our return data is in JSON format, evaluate the body html to get our return data.
var objData = eval( "(" + jBody.html() + ")" );
alert(objData);
// A JSON-format struct is returned that will be used to do callback functionality
SharePhotoAjaxResponseHandler(objData);
// Remove the iFrame from the document. Because FireFox has some issues with "Infinite thinking", let's put a small delay on the frame removal.
setTimeout
(
function()
{
jFrame.remove();
}, 100
);
}
);
// Attach to body.
$( "body:first" ).append( jFrame );
// Now that our iFrame it totally in place, hook up the frame to post to the iFrame.
jThis
.attr( "action", photoUploadAction)
.attr( "method", "post" )
.attr( "enctype", "multipart/form-data" )
.attr( "encoding", "multipart/form-data" )
.attr( "target", strName );
});
});
The javascript error I get is usually something like: "unterminated regular expression literal"
I have also tried removing the SerialiseJson on the CFM page, and the eval in the javascript, which both result in other js errors. I have also tried removing the surrounding html as well as change .html() to .text(), remove the eval() and combinations of all of them. No luck.
Please can someone tell me what the problem is. I know I can't be far off as it works without html.
Thanks
UPDATE: I ran the JSON output that Coldfusion generates through the JSON validator and it failed, due to (from what I can see) the double quotation marks:
{""RETURNID"":"""",""DATA"":""<div>Hello World<\/div>"",""SUCCESS"":true,""ERRORS"":[]}
Now I really don't know why Coldfusion is doing double quotations?? Anyone have any ideas?
The HTML is the response is being interpreted as malformed HTML.
SerializeJSON escapes the "/" in "</div>"
as "<\/div>"
, causing the HTML parser to see malformed HTML.
On the javascript side, the "/div"
looks like a bad regular expression.
For this to work you must force the browser to not interpret the data for you. Send the data as in script block with a bad type (so browser does not run the script).
In Coldfusion:
<!--- Create the return HTML. Remember, we are going to be treating the BODY of the returned document as if it were a JSON string. --->
<cfsavecontent variable="strHTML">
<cfset sResponse = {SUCCESS = true, ERRORS = [], DATA = "<div>Hello World</div>", RETURNID=""} />
<cfoutput>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body><script id="data" type="json">#SerializeJSON( sResponse )#</script>
</html>
</cfoutput>
</cfsavecontent>
<!--- Create binary response data. --->
<cfset binResponse = ToBinary( ToBase64( strHTML ) ) />
<!--- Tell the client how much data to expect. --->
<cfheader name="content-length" value="#ArrayLen( binResponse )#" />
<cfcontent type="text/html" variable="#binResponse#" />
In the broswer the json source is found using
var objUploadBody = window.frames[ strName ].document.getElementById( "data" );
// Get a jQuery object of the body so that we can have better access to it.
var jBody = $( objUploadBody );
// Assuming that our return data is in JSON format, evaluate the body html to get our return data.
var objData = eval( "(" + jBody.html() + ")" );
The javascript error I get is usually something like: "unterminated regular expression literal"
Check your JSON. You can run it though JSONLint to see if it's valid or not. It probably isn't, based on your error message.
You are shoving JSON into the <body>
of an HTML document.
The browser will interpret it as HTML, and perform error correction over it (doing things like convert &
to &
and discarding elements that aren't allowed in places you have put them.
You then pull it out using jQuery's html()
method, which will get the HTML source and not the text you want.
The solution is therefore:
- Encode any entities in the JSON. You need an HTML representation of the JSON string, not the raw string. I've no idea how to do this in CF but there must be an equivalent to
encode_entities
/htmlspecialchars
/ etc - Get the
text()
not thehtml()
(BTW: That is everything you need to do, not a list of alternatives)
The problem is that you're embedding the JSON response from the server inside more html. A JSON response for an AJAX call should comprise of ONLY the JSON text, and nothing else.
Since you're embedding it in HTML, then retrieving it with a DOM call, most likely the browser is totally getting confused by your html within json within html and mangling the DOM.
Try this, instead:
<cfoutput>#SerializeJSON( sResponse )#</cfoutput>
and
var objData = eval(jBody);
though, as other answeres have pointed out, you should not use eval for this. There's far safer ways of getting JSON data parsed back into javascript objects than eval.
精彩评论