javascript literals from C# - avoiding warnings - javascript

First off, this code does work, but it gives me a low level of annoyance that I'd like to be rid of. I have a .cshtml page that is built using the razor syntax. There is a big, nasty, knockout object living behind the scenes to which many things on the page are bound. Currently, when instantiating that knockout model, I'm doing so by serializing the C# viewmodel object in question within a string literal like below.
<script type="text/javascript">
$(document).ready(function() {
var data = #Html.Raw(Json.Encode(Model));
var contentListingModel = new ContentListingModel(data);
ko.applyBindings(contentListingModel, $('#pageRoot').get(0));
});
</script>
Like I said, the code works. However, on the line with the #Html.Raw call, I get a warning. Visual Studio believes there is a syntax error there (there isn't, after it is rendered). Now, it totally makes sense WHY the thing believes there is a syntax problem there. But I would love to get rid of the warning by coding in a way that doesn't trigger this issue (I realize I could hide the warning or do any number of other things), but I just want it to not show in cases where I'm serializing C# objects into JSON and stuffing them into javascript on the page. Any ideas?
I accept fully that it is pedantic and petty to ask about this one, since the code works perfectly well and is seemingly clean, but it annoys me.

The IDE is smart enough to accept lines which consist purely of Razor, so you can write:
#Html.Raw( "var data= " + Json.Encode(Model) + ";" )
alert(data);
If the (assumed) implicit global bothers you (e.g. it triggers a Resharper warning), split the declaration and assignment.
var data; // javascript
#Html.Raw( "data= " + Json.Encode(Model) + ";" ) // all razor
Which produces at runtime:
var data;
data = {"foo": "bar"};

'You Can Just Serialize Your Model To Json In Your ActionMethod , And Pass It To Your View Through ViewData Or ViewBag And Simply Pass It To ViewModel With Html.Raw , Something Like This :
//In Your ActionMethod
var serializer = new JavaScriptSerializer();
Viewdata["SomeName"] = serializer.Serialize(model);
//In Your cshtml
#{
string data = (string)Viewdata["SomeName"];
}
$(document).ready(function() {
var contentListingModel = new ContentListingModel('#Html.Raw(data)');
ko.applyBindings(contentListingModel, $('#pageRoot').get(0));
});

the javascript debugger ignore the razor code and see this
var data = ;
which is a syntax error, replace this
var data = #Html.Raw(Json.Encode(Model));
with this, and the warning will be gone.
var data #('=') #Html.Raw(Json.Encode(Model));

Related

JSON to JavaScript, SyntaxError: Unexpected token &

I know this question has been asked numerous times, but I really donĀ“t get it.
I am creating a site in MVC, and I'm creating a JSON string from my model. I then want to pass it as argument to a JavaScript function that uses it to plot a graph.
Here is were I create the JSON string. This indeed creates a valid JSON string, I checked it at JSONLint.
#{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var weightsAsJsonString = serializer.Serialize(Enumerable.Select(Model, weight =>
new
{
date = weight.Date,
value = weight.Value
}));
}
Further down I create a JavaScript variable from it and pass it into the JavaScript function:
var jsonStringToGraph = #weightsAsJsonString;
google.setOnLoadCallback(drawVisualization(jsonstring));
When I run this, the console prints 'SyntaxError: Unexpected token &' at the line were I declare jsonStringToGraph. I googled around and concluded that I should put ' ' around #weightsAsJsonString, so I do that.
Anyway, in my drawVisualization, I do this:
function drawVisualization(teststring) {
.......
var parsedJson = JSON.parse(teststring);
This gives me SyntaxError: Unexpected token & Index:1
I know that the code at the bottom is what is causing the exception, but I do not understand why. Do anyone understand what I am doing wrong?
Edit: This is the weightsAsJsonString
[{"date":"\/Date(1434492000000)\/","value":100.2},{"date":"\/Date(1434578400000)\/","value":99.2},{"date":"\/Date(1434664800000)\/","value":101.2},{"date":"\/Date(1434751200000)\/","value":98.2},{"date":"\/Date(1434837600000)\/","value":97.2},{"date":"\/Date(1434924000000)\/","value":96.2},{"date":"\/Date(1435010400000)\/","value":95.2},{"date":"\/Date(1435096800000)\/","value":94.2}]
It sounds like your issue is trying to inject content via Razor into JavaScript. By default # will HTML-encode your content, which doesn't work in the context of JavaScript.
#Html.Raw(weightsAsJsonString) will work better, and then your JS will have a JavaScript object, so there's no need for the JSON.parse later on.
When you do var jsonStringToGraph = #weightsAsJsonString; you are actually defining a JSON object and not a JSON string.
Hence when you do JSON.parse(teststring); you are trying to parse an object instead of a string.
Either put apostrophes around the first declaration var jsonStringToGraph = '#weightsAsJsonString'; or simply do not try to parse it again.

MVC - Handling unterminated string literal for new line

In my MVC view am having the below piece of code
<script type="text/javascript">
#foreach (var item in Model.Test)
{
<text> jsFunction(#item.someValue); </text>
}
</script>
Where am calling the Javascript function based on the data i get from model dynamically.
For Example At run time it is rendered like below based on the model data
jsFunction("Works Fine");
jsFunction("works Fine");
Which correctly calls my javascript function
But in case of a new line getting "unterminated string literal" because of the new line
jsFunction("not working
with new line");
What is the best way to handle this Scenario
Thanks
What is the best way to handle this Scenario
Use a JSON serializer which will ensure that the value passed to your javascript function is properly escaped:
<script type="text/javascript">
#foreach (var item in Model.Test)
{
<text>jsFunction(#Html.Raw(Json.Encode(item.someValue)));</text>
}
</script>
or if you update your jsFunction to take an array instead of a single item you could do this:
<script type="text/javascript">
var values = #Html.Raw(Json.Encode(Model.Test));
jsFunction(values);
</script>
and then loop through the normal javascript array that will be passed to your function.
P.S: Don't worry if Razor Intellisense is putting a red squiggle around this code. Visual Studio Intellisense in Razor views doesn't work well. Just ignore the warning and run your application and observe the properly generated code.
Simplest solution is to create a helper or something:
#* declared at top of razor page *#
#helper jsFunction(String str)
{
str = (str ?? String.Empty).Trim().Replace(Environment.NewLine, "\\n");
#:jsFunction("#Html.Raw(str)");
}
#* example usage *#
foreach (var item in Model.Test)
{
#jsFunction(#item.Comment)
}
That would perform sanitization on the string to make it convert \n in to the JavaScript counter-part \\n. You may also include exceptions for quotation marks (turn " into \", etc.)
The better alternative is to use something like JSON.NET though:
<text>jsFunction(#Html.Raw(JsonConvert.Serialize(#item.Comment)))</text>
That would make it completely safe to JavaScript (and handle the exceptions for you).
try this
jsFunction(#Html.Raw(Json.Encode(item.Comment)))
I think this should solve your issue.

passing in a JSON object in HTML DOM

I am experimenting with a charting package called Highcharts (some of you may be familiar with it but regardless the problem is not related to Highcharts per se). What I wanted to do was have my PHP generated HTML embed a JSON object into the DOM which would then be picked up by a static jQuery listening function. Here's what it looks like:
// Static JS file that get's loaded with every page load and
// and listens for a class with ".highchart_config".
// When it finds a config class it then looks in the attribute "data-chart"
// for the JSON configuration object
jQuery.noConflict();
jQuery(function($) {
$(document).ready(function() {
$(".highchart_config").each(function(index) {
var config_obj = $(this).attr('data-chart');
chart = new Highcharts.Chart( config_obj );
});
});
});
And then the HTML is as follows:
<div class="highchart_config" data-chart=' {chart: {"renderTo":"chart2","defaultSeriesType":"column"},title: {"text":"Monkies are Happy Animals"},xAxis:{"categories":["Apples","Oranges","Pears","Grapes","Bananas"],"min":null,"title":""},yAxis: {"min":0,"title":{"text":"Total fruit consumption"}},legend: {"align":"center","x":0,"verticalAlign":"bottom","y":0,"floating":false,"backgroundColor":null,"borderColor":"#CCC","borderWidth":1,"shadow":false,"reversed":true},tooltip: { formatter: function() { return this.series.name + ":" + this.y + " "}},plotOptions: {"column":{"stacking":"normal","dataLabels":{"enabled":false}}},series: [{"name":"Running","data":[5,3,4,7,2]},{"name":"Cycling","data":[2,2,3,2,1]},{"name":"Lifting","data":[3,4,4,2,5]}]}'></div>
Using a debugger I can see this working by placing a breakpoint on the line where Highcharts object instantiation takes place. When the breakpoint is hit I print the value of "chart_obj" which comes out as:
{chart: {"renderTo":"chart2","defaultSeriesType":"column"},title: {"text":"Monkies are Happy Animals"},xAxis:{"categories":["Apples","Oranges","Pears","Grapes","Bananas"],"min":null,"title":""},yAxis: {"min":0,"title":{"text":"Total fruit consumption"}},legend: {"align":"center","x":0,"verticalAlign":"bottom","y":0,"floating":false,"backgroundColor":null,"borderColor":"#CCC","borderWidth":1,"shadow":false,"reversed":true},tooltip: { formatter: function() { return this.series.name + ":" + this.y + " "}},plotOptions: {"column":{"stacking":"normal","dataLabels":{"enabled":false}}},series: [{"name":"Running","data":[5,3,4,7,2]},{"name":"Cycling","data":[2,2,3,2,1]},{"name":"Lifting","data":[3,4,4,2,5]}]}
That looks "right" to me but it doesn't work. Instead the instantiation of the object fails as the config_obj is somehow malformed. To make sure I wasn't making some stupid syntax error I cut and paste the value in config_obj and put it into a static JS file that looks like this:
$(function () {
var chart;
$(document).ready(function() {
chart = new Highcharts.Chart({
chart: {"renderTo":"chart2","defaultSeriesType":"column"},title: {"text":"Monkies are Happy Animals"},xAxis: {"categories":["Apples","Oranges","Pears","Grapes","Bananas"],"min":null,"title":""},yAxis: {"min":0,"title":{"text":"Total fruit consumption"}},legend: {"align":"center","x":0,"verticalAlign":"bottom","y":0,"floating":false,"backgroundColor":null,"borderColor":"#CCC","borderWidth":1,"shadow":false,"reversed":true},tooltip: { formatter: function() { return this.series.name + ":" + this.y + " "}},plotOptions: {"column":{"stacking":"normal","dataLabels":{"enabled":false}}},series: [{"name":"Running","data":[5,3,4,7,2]},{"name":"Cycling","data":[2,2,3,2,1]},{"name":"Lifting","data":[3,4,4,2,5]}]
});
});
});
This "hardcoded" method works and yet the instantiation call should have precisely the same configuration object passed in. I'm at a loss now how to proceed. I have been reading other posts on stackoverflow around this topic but can't find anything to help me with my specific problem. Any and all help is greatly appreciated.
UPDATE:
I have no tried ... to no avail using both data() and attr() methods and in both cases with and without a call to JSON.parse(config_obj). It DOES appear that the problem is related to config_obj being treated as a string so in the debugger I decided to assign a variable "test" to the cut-and-pasted string results of config_obj without the exterior quotation marks. It works fine so it's clearly a well structured JSON string but getting it converted to a string is still eluding me. Below I have an image of my debugging session which shows three things:
First I get an error when using the JSON.parse() function on my config_obj string (that's true regardless if I used data() or attr() to retrieve config_obj from the DOM)
If I instead just cut-and-paste the text into a test variable called "test" it is recognised as a valid JS object
If I use the JSON.stringify() method on the test object it converts back to a string version that is CLOSE to the same as my config_obj variable ... the difference being that the first level attributes in the object have quotation marks around them. This might be a hint at what's going wrong but I still haven't cracked this nut ... any help would be greatly appreciated.
When you get the attributes value - using .attr() - what you're being returned is a string. You'll need to parse that string to turn it into the actual object, so change the following line to:
chart = new Highcharts.Chart( JSON.parse(config_obj) );
It's the JSON.parse() function that's the important part.
Also, as a note, if you're using data-* attributes, it's better to use the .data() function, so you'd change the other line to:
var config_obj = $(this).data('chart');
As you may have seen in my "update" to the question I had found a variation in the string versions of the JSON object between my original object and the one I created by cut-and-pasting this same string into an object and then running JSON.stringify() on it.
This variation -- including double quote markers around object names -- seems to be important for it to work correctly. If you pass it in this way using jQuery's .data() method than it automatically converts it to a JSON object and there's no need to directly call JSON.parse().
I still find it odd that there is a stricter standard to convert a string to an object with JSON's parse() method than there is within JS itself and I'd be interested if anyone has any theories on this. In either event, wanted to thank #Anthony, #DCoder and everyone else who helped.
Here is the working DOM entry:
<div class="highchart_config" data-chart='{"chart":{"renderTo":"chart2","defaultSeriesType":"column"},"title":{"text":"Monkies are Happy Animals"},"xAxis":{"categories":["Apples","Oranges","Pears","Grapes","Bananas"],"min":null,"title":""},"yAxis":{"min":0,"title":{"text":"Total fruit consumption"}},"legend":{"align":"center","x":0,"verticalAlign":"bottom","y":0,"floating":false,"backgroundColor":null,"borderColor":"#CCC","borderWidth":1,"shadow":false,"reversed":true},"tooltip":{},"plotOptions":{"column":{"stacking":"normal","dataLabels":{"enabled":false}}},"series":[{"name":"Running","data":[5,3,4,7,2]},{"name":"Cycling","data":[2,2,3,2,1]},{"name":"Lifting","data":[3,4,4,2,5]}]}'></div>
And the JS that takes this DOM entry as input is:
jQuery.noConflict();
jQuery(function($) {
$(document).ready(function() {
$(".highchart_config").each(function(index) {
var config_obj = $(this).data('chart');
chart = new Highcharts.Chart( config_obj );
});
});
});

MVC 3 Razor View: Generating JavaScript from a boolean model value

I am using ASP.Net MVC 3 Razor view engine.
I have a requirement to generate some JavaScript code in my View based on a value in my View Model. The value I need to use is a boolean, for this example lets call it IsSet.
So what I want to do is create a JavaScript boolean based on this value that I can use in the script later on.
Keep in mind that for all below examples I have this bit of code at the top of my view...
#{ string IsSet = Model.IsSet ? "true" : "false"; }
NOTE: All examples below are JavaScript.
First attempt...
var IsSet = #(IsSet);
... this actually works, the problem is it breaks the auto-formatting (CTRL + E, D) in VS 2010 due to badly formatted JavaScript - as you might expect, and this is not acceptable.
Second attempt...
var IsSet = "#(IsSet)";
...I know, JavaScript is clever, it will auto-parse my string when needed. Ooops, forgot it is a string type and anything other than empty evaluates to true.
Third attempt...
var IsSet = Boolean("#(IsSet)");
....surely this will work... nope, convert non-empty string to true again (bad parser!)
Fourth attempt...
var IsSet = "#(IsSet)" === "true";
Finally something that works, but it doesn't look great to me.
I will use this if need be but ultimately my question is: Is there a better way to handle this kind of situation? Perhaps, the unwanted behaviour in my first attempt is just something that Microsoft may have overlooked?
If anybody has a nice and tidy fifth attempt for me, that would be good.
The important thing for me is that the auto-formatting in VS 2010 does not break
Thanks
I just wrestled with the same issue for about an hour. My final solution was equivalent to the following.
var IsSet = #(Model.IsSet.ToString().ToLower()); // Inside JavaScript block
This requires no additional code.
None of the versions shown so far (both in the question and answers) is something that I would use. Here's how I would do it:
#model MyViewModel
<script type="text/javascript">
var isSet = #Html.Raw(Json.Encode(Model.IsSet));
// TODO: use the isSet javascript variable here as a standard boolean value
</script>
or if you needed other properties of your view model to be manipulated with javascript you could JSON encode the entire model:
#model MyViewModel
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model));
if (model.IsSet) {
alert(model.FooBar);
}
</script>
Version 1 is the only one of those that I'd vote for even if they all worked, because it's the most human-readable. Unfortunately I don't have VS at home so I can't try it out to see what the auto-formatting issue is, but if at all possible I'd want to ignore the issue and go ahead and use that version given that there's nothing actually wrong with the code - it is just VS that is confused. (Are you saying VS is trying to interpret the whole thing as JS and thus finding it invalid?)
But if you want some other options:
Fifth attempt...
#{ string IsSet = Model.IsSet ? "true" : ""; }
var isSet = !!"#(IsSet)";
// OR
var isSet = Boolean("#(IsSet)");
Coerce the string value into a boolean with the old double-not-operator trick - as you already pointed out both "true" and "false" would become true, but this problem goes away if you use "true" and "" (empty string) - so you can use Boolean() as per your "third attempt".
Sixth attempt...
#{ string IsSet = Model.IsSet ? "true" : "false"; }
// helper function at the top of your page:
function bool(strVal) {
return strVal === "true";
}
// variable declaration
var isSet = bool("#(IsSet)");
OK, so you end up with a fairly pointless function at the top of your page, but it keeps the actual variable declarations reasonably tidy and if debugging client side you'll see bool("false") or bool("true").
Seventh attempt...
I don't like the extra step of creating the server-side string IsSet = Model.IsSet ? "true" : "false"; in advance. I don't know Razor syntax, but can you say something along the lines of:
var isSet = !!"#(Model.IsSet ? "true" : "")";
// OR, better:
var isSet = !!"#(rzrBool(Model.IsSet))";
// where rzrBool() is a server-side helper function (that you would create)
// which returns "true" or ""
I would expect all of my "attempts" to work, but again I think your "first attempt" is the best option.
How about:
#Ajax.ToJson(Model.IsSet)
var isSet = /true/i.test('#Model.IsSet');
Single line
Handles the case difference between .Net and JavaScript
Works with auto-formatting (Visual Studio, and Visual Studio with Resharper)
Reasonably idiomatic if you are familiar with JavaScript regexes
Fairly resilient to logic errors; it should do as intended or throw a JavaScript error (or possibly a Razor compilation error).
How about:
#Model.IsSet.ToString().ToLower()
var isSet= #((bool)Model.IsSet?"true":"false");
Here is what I use, inside a javascript block:
var someValue;
#{ var someValue= "someValue= " + Model.SomeValue+ ";"; }
#someValue
I know this is an old question, but none of the answers are particularly elegant.
The simplest solution in these situations is simply to append +0 to your conditional. This implicitly converts the bool to an int, but since the result is 0 or 1 it's immediately converted back again by the if statement. Example:
// The space is optional
if (#IsSet +0) {
// Do stuff
}
Assigning a boolean value to a variable could be achieved as follows:
// Note the double (not triple) equals, which performs type conversion
var isSet = #IsSet+0 == true;
The code works, you get no red squiggly lines, and the Visual Studio formatter is happy.

HTML-escaping the data returned from ajax/json

I'm using ajax to retrieve some data from the backend. I get the result as json.
I then use jquery to add it to the page:
$(...).append('<H3>' + data.title + '</H3>')
And I just realized that the json data is not HTML escaped, which is bad.
What should I do?
HTML escape all the data returned from the backend in the json?
Do all the escaping on the frontend when concatenating strings? (i.e. wrap all external data in an escaping function)
Option 1 means the data in the json is not really "correct", it's useful for HTML, but it does not contain the real data. And worse, it means I can't just use json_encode() - I would first have to walk through the array data and escape everything.
Option 2 seems more complicated, and I'm worried I may miss a spot. On the other hand that's what you do when getting data from SQL and building it in PHP, so I guess I'm used to it.
Please do not suggest:
$(...).append($('<H3></H3>').text(data.title))
That method of writing becomes unwieldy when you have many levels of nested tags. I like to write HTML, not DOM calls.
PS. I know I need a Javascript templating library, but for right now I need to do it with string concatenation.
Here is simple jQuery extension that adds $append() formatting method with html escapement:
(function( $ ){
$.fn.$append = function()
{
var out = arguments[0];
for (var i = 1; i < arguments.length; ++i)
out = out.replace("{" + arg + "}", arguments[arg].toString().htmlEscape());
this.append(out);
};
})( jQuery );
With this method you can write:
$(...).$append('<H3>{1}</H3>', data.title);
And the data will be escaped. Add salt and pepper to the above - to your taste.
Update: You will also need this:
String.prototype.htmlEscape = function() {
return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}
Well, I am sort of working on an open-source project on my weekends.
I think it would fit your demand. Please check it out at http://tog2html.com
For instance, in your case , after getting a json obj (var data). You can do something like :
$(...).append(
Tog('div#main_div').h1('hello, ', data.name).h3().raw(data.title).html()
// raw() is for escaping the data into something valid to be shown in html
)
possible output:
<div id='main_div'><h1>hello, some one</h1><h3>this is my blog title</h3></div>

Categories