I have an HtmlHelper
extension method that takes javascript callback functions as parameters.. for example:
@Html.SomethingCool("containerName", "jsCallbackFunction")
<script type="javascript">
function jsCallbackFunction(e, i) {
alert(e.target.name + ' / ' + i);
}
</script>
As you can see, the javascript callback function name is passed to the HtmlHelper
extension method. This causes the developer to have to refer back to the documentation to figure out what parameters the jsCallbackFunction
function needs.
I would much rather prefer something like this:
@Html.SomethingCool("containerName", New SomethingCoolCallbackDelegate(Address Of jsCallbackFunction))
<OutputAsJavascript>
Private Sub jsCallbackFunction(e, i)
' SOMETHING goes here. some kind of html dom calls or ???
End Sub
The SomethingCoolCallbackDelegate
would provide the code contract for the target function.
Then the compiler would compile the jsCallbackFunction as javascript on the MVC page.
Is there anything like this built into .NET 4 / ASP.NET MVC 4 / Razor 2 ? Or any other technology that can achieve something similar?
Examples are in VB, but solutions in C# are quite acceptable as well.
@gideon: notice that jsCallbackFunction
takes two parameters e
, and i
. However, the HtmlHelper extension method simply asks for a string (the name of the javascript callback function) and does not indicate what parameters this function might take. The problem I am trying to solve is two-fold.
First, the missing parameter hints. A .NET delegate type passed in place of the "javascript callback name" string would accomplish this. I am open to other solutions to accomplish this. I am aware of XML comments. They are not really a solution.
Second, trying to keep the page programmer working in a single language. Switching between javascript and VB (or js and C#) requires (for me at least) an expensive context switch. My brain doesn't make the transition quickly. Keeping me working in VB or C# is more productive and cost effective. So being able to write a function in a .NET language and have it compiled to javascript, in the context of an ASP.NET MVC/razor view, is what I am after here.
@TyreeJackson: SomethingCool
is an HtmlHelper
extension method that I would write that outputs html and javascript. Part of the javascript output needs to call into a user(programmer)-supplied function to make some decisions. Think of it similar to the success or failure function you supply to an ajax call.
While I can't give you a full transpiler/compiler option since that would be an enormous amount of work, I can suggest the following to assist with the intellisense support and emitting of the functions and calls.
Here is the infrastructure code. You would need to complete the getArgumentLiteral and getConstantFromArgument functions to handle other cases you come up with, but this is a decent starting point.
public abstract class JavascriptFunction<TFunction, TDelegate> where TFunction : JavascriptFunction<TFunction, TDelegate>, new()
{
private static TFunction instance = new TFunction();
private static string name = typeof(TFunction).Name;
private string functionBody;
protected JavascriptFunction(string functionBody) { this.functionBody = functionBody; }
public static string Call(Expression<Action<TDelegate>> func)
{
return instance.EmitFunctionCall(func);
}
public static string EmitFunction()
{
return "function " + name + "(" + extractParameterNames() + ")\r\n{\r\n " + instance.functionBody.Replace("\n", "\n ") + "\r\n}\r\n";
}
private string EmitFunctionCall(Expression<Action<TDelegate>> func)
{
return name + "(" + this.extractArgumentValues(((InvocationExpression) func.Body).Arguments) + ");";
}
private string extractArgumentValues(System.Collections.ObjectModel.ReadOnlyCollection<Expression> arguments)
{
System.Text.StringBuilder returnString = new System.Text.StringBuilder();
string commaOrBlank = "";
foreach(var argument in arguments)
{
returnString.Append(commaOrBlank + this.getArgumentLiteral(argument));
commaOrBlank = ", ";
}
return returnString.ToString();
}
private string getArgumentLiteral(Expression argument)
{
if (argument.NodeType == ExpressionType.Constant) return this.getConstantFromArgument((ConstantExpression) argument);
else return argument.ToString();
}
private string getConstantFromArgument(ConstantExpression constantExpression)
{
if (constantExpression.Type == typeof(String)) return "'" + constantExpression.Value.ToString().Replace("'", "\\'") + "'";
if (constantExpression.Type == typeof(Boolean)) return constantExpression.Value.ToString().ToLower();
return constantExpression.Value.ToString();
}
private static string extractParameterNames()
{
System.Text.StringBuilder returnString = new System.Text.StringBuilder();
string commaOrBlank = "";
MethodInfo method = typeof(TDelegate).GetMethod("Invoke");
foreach (ParameterInfo param in method.GetParameters())
{
returnString.Append(commaOrBlank + param.Name);
commaOrBlank = ", ";
}
return returnString.ToString();
}
}
public abstract class CoreJSFunction<TFunction, TDelegate> : JavascriptFunction<TFunction, TDelegate>
where TFunction : CoreJSFunction<TFunction, TDelegate>, new()
{
protected CoreJSFunction() : base(null) {}
}
Here is an example of a standard function support wrapper:
public class alert : CoreJSFunction<alert, alert.signature>
{
public delegate void signature(string message);
}
Here are a couple of example Javascript function support wrappers:
public class hello : JavascriptFunction<hello, hello.signature>
{
public delegate void signature(string world, bool goodByeToo);
public hello() : base(@"return 'Hello ' + world + (goodByeToo ? '. And good bye too!' : ''") {}
}
public class bye : JavascriptFunction<bye, bye.signature>
{
public delegate void signature(string friends, bool bestOfLuck);
public bye() : base(@"return 'Bye ' + friends + (bestOfLuck ? '. And best of luck!' : ''") {}
}
And here is a console app demonstrating its use:
public class TestJavascriptFunctions
{
static void Main()
{
// TODO: Get javascript functions to emit to the client side somehow instead of writing them to the console
Console.WriteLine(hello.EmitFunction() + bye.EmitFunction());
// TODO: output calls to javascript function to the client side somehow instead of writing them to the console
Console.WriteLine(hello.Call(func=>func("Earth", false)));
Console.WriteLine(bye.Call(func=>func("Jane and John", true)));
Console.WriteLine(alert.Call(func=>func("Hello World!")));
Console.ReadKey();
}
}
And here is the output from the console app:
function hello(world, goodByeToo)
{
return 'Hello ' + world + (goodByeToo ? '. And good bye too!' : ''
}
function bye(friends, bestOfLuck)
{
return 'Bye ' + friends + (bestOfLuck ? '. And best of luck!' : ''
}
hello('Earth', false);
bye('Jane and John', true);
alert('Hello World!');
UPDATE:
You may also want to check out the JSIL. I'm not affiliated with the project and cannot speak to it's stability, accuracy nor efficacy, but it sounds interesting, and may be able to help you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With