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

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.

Related

jQuery / Javascript substitution 'Syntax error, unrecognized expression'

I am implementing jQuery chaining - using Mika Tuupola's Chained plugin - in my rails project (using nested form_for partials) and need to dynamically change the chaining attribute:
The code that works without substitution:
$(".employee_title_2").remoteChained({
parents : ".employee_title_1",
url : "titles/employee_title_2",
loading : "Loading...",
clear : true
});
The attributes being substituted are .employee_title_1 and .employee_title_2:
var t2 = new Date().getTime();
var A1 = ".employee_title_1A_" + t2;
var B2 = ".employee_title_2B_" + t2;
In ruby speak, I'm namespacing the variables by adding datetime.
Here's the code I'm using for on-the-fly substitution:
$(`"${B2}"`).remoteChained({
parents : `"${A1}"`,
url : "titles/employee_title_2",
loading : "Loading...",
clear : true
});
Which throws this error:
Uncaught Error: Syntax error, unrecognized expression:
".employee_title_2B_1462463848339"
The issue appears to be the leading '.' How do I escape it, assuming that's the issue? Researching the error message Syntax error, unrecognized expression lead to SO question #14347611 - which suggests "a string is only considered to be HTML if it starts with a less-than ('<) character" Unfortunately, I don't understand how to implement the solution. My javascript skills are weak!
Incidentally, while new Date().getTime(); isn't in date format, it works for my purpose, i.e., it increments as new nested form fields are added to the page
Thanks in advance for your assistance.
$(`"${B2b}"`).remoteChained({
// ^ ^
// These quotes should not be here
As it is evaluated to a string containing something like:
".my_class"
and to tie it together:
$('".my_class"')...
Same goes for the other place you use backtick notation. In your case you could simply use:
$(B2).remoteChained({
parents : A1,
url : "titles/employee_title_2",
loading : "Loading...",
clear : true
});
The back tick (``) syntax is new for Javascript, and provides a templating feature, similar to the way that Ruby provides interpolated strings. For instance, this Javascript code:
var who = "men";
var what = "country";
var famous_quote = `Now is the time for all good ${who} to come to the aid of their #{what}`;
is interpolated in exactly the same way as this Ruby code:
who = "men"
what = "country"
famous_quote = "Now is the time for all good #{who} to come to the aid of their #{what}"
In both cases, the quote ends up reading, "Now is the time for all good men to come to the aid of their country". Similar feature, slightly different syntax.
Moving on to jQuery selectors, you have some flexibility in how you specify them. For instance, this code:
$(".my_class").show();
is functionally equivalent to this code:
var my_class_name = ".my_class";
$(my_class_name).show();
This is a great thing, because that means that you can store the name of jQuery selectors in variables and use them instead of requiring string literals. You can also build them from components, as you will find in this example:
var mine_or_yours = (user_selection == "me") ? "my" : "your";
var my_class_name = "." + mine_or_yours + "_class";
$(my_class_name).show();
This is essentially the behavior that you're trying to get working. Using the two features together (interpolation and dynamic jQuery selectors), you have this:
$(`"${B2}"`).remote_chained(...);
which produces this code through string interpolation:
$("\".employee_title_2B_1462463848339\"").remote_chained(...);
which is not correct. and is actually the cause of the error message from jQuery, because of the embedded double quotes in the value of the string. jQuery is specifically complaining about the extra double quotes surrounding the value that you're passing to the selector.
What you actually want is the equivalent of this:
$(".employee_title_2B_1462463848339").remote_chained(...);
which could either be written this way:
$(`${B2}`).remote_chained(...);
or, much more simply and portably, like so:
$(B2).remote_chained(...);
Try this little sample code to prove the equivalence it to yourself:
if (`${B2}` == B2) {
alert("The world continues to spin on its axis...");
} else if (`"${B2}"` == B2) {
alert("Lucy, you've got some 'splain' to do!");
} else {
alert("Well, back to the drawing board...");
}
So, we've established the equivalency of interpolation to the original strings. We've also established the equivalency of literal jQuery selectors to dynamic selectors. Now, it's time to put the techniques together in the original code context.
Try this instead of the interpolation version:
$(B2).remoteChained({
parents : A1,
url : "titles/employee_title_2",
loading : "Loading...",
clear : true
});
We already know that $(B2) is a perfectly acceptable dynamic jQuery selector, so that works. The value passed to the parents key in the remoteChained hash simply requires a string, and A1 already fits the bill, so there's no need to introduce interpolation in that case, either.
Realistically, nothing about this issue is related to Chained; it just happens to be included in the statement that's failing. So, that means that you can easily isolate the failing code (building and using the jQuery selectors), which makes it far easier to debug.
Note that the Javascript syntax was codified just last year with ECMAScript version 6, so the support for it is still a mixed bag. Check your browser support to make sure that you can use it reliably.

Can I use ui:repeat inside h:outputScript?

Or how would I convert a list of SelectItems to a JavaScript array?
Currently I am trying this:
<h:outputScript>
<!-- Trailing commas valid as per http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.5 -->
var possibleOption = [<ui:repeat value="#{bean.mySelectItems}" var="selectItem">"#{selectItem.value}",</ui:repeat>];
var firstOption = possibleOption[0];
</h:outputScript>
And it works, except that firstOption is undefined although possibleOption gets correctly populated when I check in the console. Maybe a timing problem? Is this even valid JSF, and if so, is there a "blocking" version of ui:repeat or something?
Or which other approach would you recommend?
Aaaah, I got it:
actually I was using:
var chosenOption = '#{empty bean.chosenOption ? possibleOption[0] : bean.chosenOption}';
Which is (now) of course wrong, because I was using possibleOption[0] inside the EL expression headbang
Sorry, one should always post the actual code I guess, not some dumbed down showcase ;)

Get the value of confused Javascript code

I got a piece of code like this:
var password = eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('9 5$=["\\8\\3\\4\\3\\2\\2\\1\\3\\2\\3\\3\\2\\2\\7\\3\\1\\4\\1\\3\\2\\1\\3\\1\\3\\2\\2\\2\\1\\3\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\2\\1\\3\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\2\\1\\3\\1\\3\\2\\2"];6 c(){e["\\f\\g\\d\\a\\b"](5$[0])}',17,17,'|x2b|x5d|x5b|x21|_|function|x29|x28|var|x72|x74|O0|x65|window|x61|x6c'.split('|'),0,{}));
And I unpacked the following code(except 'var password = '):
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('9 5$=["\\8\\3\\4\\3\\2\\2\\1\\3\\2\\3\\3\\2\\2\\7\\3\\1\\4\\1\\3\\2\\1\\3\\1\\3\\2\\2\\2\\1\\3\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\2\\1\\3\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\1\\4\\1\\3\\2\\2\\1\\3\\1\\3\\2\\2"];6 c(){e["\\f\\g\\d\\a\\b"](5$[0])}',17,17,'|x2b|x5d|x5b|x21|_|function|x29|x28|var|x72|x74|O0|x65|window|x61|x6c'.split('|'),0,{}));
Then I got:
var _$ = ["\x28\x5b\x21\x5b\x5d\x5d\x2b\x5b\x5d\x5b\x5b\x5d\x5d\x29\x5b\x2b\x21\x2b\x5b\x5d\x2b\x5b\x2b\x5b\x5d\x5d\x5d\x2b\x5b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x5d\x2b\x5b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x2b\x21\x2b\x5b\x5d\x5d\x2b\x5b\x2b\x5b\x5d\x5d"];
function O0() {
window["\x61\x6c\x65\x72\x74"](_$[0])
}
And after decoding:
var _$ = ["([![]]+[][[]])[+!+[]+[+[]]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]"];
function O0() {
window["alert"](_$[0])
}
Now I wonder how the codes execute and what is the value of password ?
Thanks so much.
The code is obfuscated and intended to permit execution of arbitrary code even if the script text is passed through filters.
The approach is often used for tracking, phishing and other undesirable activities, so I would suggest you don't try running it.
All you need to do is run this code -- not the 'window' stuff, but only the 'decode' part -- and you'll see the solution.
Here are some pointers on decoding:
the outer array brackets are a decoy
an empty array [] evaluates to 0 when used in a calculation such as +[]
!0 = 1
!+0 evaluates to true or 1, !+1 to false or 0 (this surely must be a loophole in Javascript)
.. so !+[] is simply 1.
[1]+[1] is not a valid math sum, so both arrays are converted to strings before being added up.
The above takes care of the numbers. Where does the first character come from? The first part ([![]]+[][[]]) evaluates directly to a string two constants, which add together as a string again, and the array index after it picks up a single character.

javascript literals from C# - avoiding warnings

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));

Server side variables in javascript

I suppose this isn't a huge deal, since there are other way around this issue, but I'm really curious as to the answer, since I thought this was possible to do.
I have a public property that returns a boolean in my code behind. I'd like to access this server variable in my javascript validation function, but so far, not quite getting it.
Public Property editMode() As Boolean
Get
If Cache("editMode") IsNot Nothing Then
Return (DirectCast(Cache("editMode"), Boolean))
Else
Return False
End If
End Get
Set(ByVal value As Boolean)
Cache("editMode") = value
End Set
End Property
function validateEdit()
{
alert("editMode value is " + '<%#editMode()%>');
if ('<%#editMode()%>'.toString() == "True")
{
alert("You are currently in edit mode. Please save or cancel changes.");
return false;
}
return true;
}
I've tried a bunch of variations on this, but it's always False. In the current code the alert returns "editMode value is False"
When I use:
if ('<%#editMode()%>') ...
Then it's still always False, but it goes into the if condition, so the behaviour is as if it were always true.
One other thing to mention is that most javascript/server tag stuff I find says to use <%=editMode %>, but I can't do this because every time I use the = instead of the # I get an exception:
"The Controls collection cannot be
modified because the control contains
code blocks (i.e. <% ... %>)."
So I solved this by using # and saying
Page.Header.DataBind()
Page.Form.DataBind()
In the page load event handler.
Any ideas? Thank you in advance. :)
(Also, I usually use C#, so I might have unknowingly done something goofy in the VB part, so feel free to point that out too)
First, try changing to this:
<%=editMode()%>
Not sure if that's it, but it can't hurt. Second, are you in edit mode when you first load the page? That code is going to run server side and return the result to the user.
On the user's page, they will see:
function validateEdit()
{
alert("editMode value is " + 'False');
if ('False'.toString() == "True")
{
alert("You are currently in edit mode. Please save or cancel changes.");
return false;
}
return true;
}
Again, not sure if that is it, but it is important to understand that javascript is not making any calls to the server.
This helped me fix the error.
"The Controls collection cannot be modified because the control contains code blocks"
Moving the javascript function out of the head and into the body fixes the problem. Seems to be a few things that could cause this issue, but in my case, the most likely culprit is the AjaxToolKit.
One more thing.
You do realize you are converting a string to another string with
'<%#editMode()%>'.toString()
Right?
I think what you want is this
if ('<% =editMode.toString() %>'= 'True')...
or Better yet
if (<% =editMode.toString().ToLower() %>)...

Categories