Passing an expression of a model property for viewing

I want to have a way to get an idea, to focus on a specific property of the model from the controller in a general way.

What I still have:


Controller:

// To become an extension/base class method private void FocusOnField<TModel, TProperty>(Expression<Func<TModel, TProperty>> fieldExpression) { ViewData["FieldToFocus"] = fieldExpression; } ... FocusOnField((ConcreteModelClass m) => m.MyProperty); 

View

  public static class ViewPageExtensions { public static MvcHtmlString FocusScript<TModel>(this ViewPage<TModel> viewPage) { if (viewPage.ViewData["FieldToFocus"] != null) { return MvcHtmlString.Create( @"<script type=""text/javascript"" language=""javascript""> $(document).ready(function() { setTimeout(function() { $('#" + viewPage.Html.IdFor((System.Linq.Expressions.Expression<Func<TModel, object>>)viewPage.ViewData["FieldToFocus"]) + @"').focus(); }, 500); }); </script>"); } else { return MvcHtmlString.Empty; } } } 

The problem I am facing right now is that in the FocusScript view FocusScript I don’t know the return type of the property to focus on, and casting to (System.Linq.Expressions.Expression<Func<TModel, object>>) does not executed for any property that does not return an object.

I can’t just add a second general parameter for the property, because I don’t know what type of return property the controller wants to focus on me on.

How can I write a FocusScript extension FocusScript in general so that it can be used with the properties of variables of return types?


Why is there a problem?

I know that I can just pass the identifier of the control that I want to focus on in the controller, and have javascript reading that identifier, find the control and focus on it. However, I do not like to have what belongs in the view (control Id) hardcoded in the controller. I want to tell the method which property I want, and it should know that Id is used in the same way that a view usually gets / creates an Id for a control.

Let's say I have a model:

 class MyModel { public int IntProperty { get; set; } public string StringProperty { get; set; } } 

In different places of the controller I want to focus one other field:

 FocusOnField((MyModel m) => m.IntProperty); ... FocusOnField((MyModel m) => m.StringProperty); 

Now in the first case, the expression is a function that returns an integer, in the second case, it returns a string. As a result, I don’t know how to distinguish my ViewData ["FieldToFocus"] before (pass it to IdFor<>() ), since it depends on the property.

+6
source share
4 answers

It is as simple as using a LambdaExpression expression. No need to go Expression<Func<TModel, TProperty>> .

 public static class HtmlExtensions { public static string IdFor( this HtmlHelper htmlHelper, LambdaExpression expression ) { var id = ExpressionHelper.GetExpressionText(expression); return htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(id); } public static MvcHtmlString FocusScript( this HtmlHelper htmlHelper ) { if (htmlHelper.ViewData["FieldToFocus"] != null) { return MvcHtmlString.Create( @"<script type=""text/javascript""> $(document).ready(function() { setTimeout(function() { $('#" + htmlHelper.IdFor((LambdaExpression)htmlHelper.ViewData["FieldToFocus"]) + @"').focus(); }, 500); }); </script>"); } else { return MvcHtmlString.Empty; } } } 

then in your controller:

 public ActionResult Index() { FocusOnField((MyModel m) => m.IntProperty); return View(new MyModel()); } 

and in your opinion:

 @model MyModel @Html.FocusScript() 

At the same time, I leave without comment the fact that the action of the controller sets the focus.

+2
source

I think I came up with a solution to your problem - it works in my environment, but then I had to guess what your code looks like.

 public static class ViewPageExtensions { public static MvcHtmlString GetIdFor<TViewModel, TProperty>(ViewPage<TViewModel> viewPage, Expression<Func<TViewModel, TProperty>> expression) { return viewPage.Html.IdFor(expression); } public static MvcHtmlString FocusScript<TViewModel>(this ViewPage<TViewModel> viewPage) { if (viewPage.ViewData["FieldToFocus"] == null) return MvcHtmlString.Empty; object expression = viewPage.ViewData["FieldToFocus"]; Type expressionType = expression.GetType(); // expressionType = Expression<Func<TViewModel, TProperty>> Type functionType = expressionType.GetGenericArguments()[0]; // functionType = Func<TViewModel, TProperty> Type[] functionGenericArguments = functionType.GetGenericArguments(); // functionGenericArguments = [TViewModel, TProperty] System.Reflection.MethodInfo method = typeof(ViewPageExtensions).GetMethod("GetIdFor").MakeGenericMethod(functionGenericArguments); // method = GetIdFor<TViewModel, TProperty> MvcHtmlString id = (MvcHtmlString)method.Invoke(null, new[] { viewPage, expression }); // Call GetIdFor<TViewModel, TProperty>(viewPage, expression); return MvcHtmlString.Create( @"<script type=""text/javascript"" language=""javascript""> $(document).ready(function() { setTimeout(function() { $('#" + id + @"').focus(); }, 500); }); </script>"); } } 

There may be a more elegant way to do it, but I think it comes down to trying to pass an object (ie, the return type ViewData["FieldToFocus"] ) into the correct expression tree Expression<Func<TViewModel, TProperty>> but, ve said you don't know what TProperty should have been.

Also, to do this, I had to add another static method to GetIdFor , because at the moment I'm not too sure how to call the extension method. Its just a wrapper for calling the IdFor extension IdFor .

You can make this less verbose (but probably less readable).

 object expression = viewPage.ViewData["FieldToFocus"]; MethodInfo method = typeof(ViewPageExtensions).GetMethod("GetIdFor") .MakeGenericMethod(expression.GetType().GetGenericArguments()[0].GetGenericArguments()); MvcHtmlString id = (MvcHtmlString)method.Invoke(null, new[] { viewPage, expression }); 

After all, will the output of HtmlHelper.IdFor be different from ExpressionHelper.GetExpressionText ? I don’t quite understand IdFor and am wondering if this will always give you a string that matches the name of the property.

 ViewData["FieldToFocus"] = ExpressionHelper.GetExpressionText(fieldExpression); 
+5
source

You can use your metadata from the box to provide you with a property identifier, etc.

And then in your extensions:

  public static MvcHtmlString FocusFieldFor<TModel, TValue>( this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) { var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); var fullPropertyName = html.ViewData.TemplateInfo.GetFullHtmlFieldId(metadata.PropertyName); var jsonData = @"<script type=""text/javascript""> $(document).ready(function() { setTimeout(function() { $('#" + fullPropertyName + @"').focus(); }, 500); }); </script>"; return MvcHtmlString.Create(jsonData); } 
+1
source

Maybe I missed something, but since you are passing the field name to your FocusOnField function, could you also just pass the field name string, which will be the default identifier in the view, and then set the ViewData value? Then your javascript could do something like ...

 <script type="text/javascript"> onload = focusonme; function focusonme() { var element = document.getElementById(@ViewData["FieldToFocus"]); element.focus(); } </script> 

This may not be exactly what you need, but JS will definitely work this way ...

-1
source

Source: https://habr.com/ru/post/920311/


All Articles