5

I was thinking it would be very useful to have an extended version of the EditorFor HTML helper which automatically writes out value data bindings for Knockout JS.

This would be for where the client side view model and the server side view model are the same - I've already autogenerated the client side view model by using ko mapping and getting the viewmodel via AJAX.

Has anyone else attempted something like this, or are there any projects which include something similar to what I'm thinking here?

The advantage of this would be that when refactoring there would be no danger of the data bound values being missed.

Chris Nevill
  • 5,922
  • 11
  • 44
  • 79
  • 1
    Do you know about about http://knockoutmvc.com/? Though I've heard bad things about it http://stackoverflow.com/questions/11618042/is-there-a-reason-i-would-use-knockout-mvc-instead-of-knockout-js. However you could probably steal some extensions from it :) – Snæbjørn Mar 23 '13 at 23:14
  • I started a similar project for this back in MVC3, but when we heard about the Single Page App template with Knockout integration coming in MVC4 we stopped working on it. Unfortunately, this didn't materialize and we never got back to the project. Now, I believe that feature is returning (or has returned) in SP2. – Joel Cochran Mar 24 '13 at 12:37

2 Answers2

3

We have done something along these lines, its far from perfect, and we have much more in our custom extensions, but I extracted the essence.

using System.Web.Mvc.Html;

namespace System.Web.Mvc
{
    public static class EditorForExtensions
    {
        public static MvcHtmlString TextBoxForViewModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

            var htmlAttributes = HtmlAttributesForKnockout(metadata);

            return htmlHelper.TextBoxFor(expression, htmlAttributes);
        }

       private static Dictionary<string, object> HtmlAttributesForKnockout(ModelMetadata metadata)
        {
            var htmlAttributes = new Dictionary<string, object>();

            var knockoutParameter = String.Format("value: {0}", metadata.PropertyName);

            htmlAttributes.Add("data-bind", knockoutParameter);

            return htmlAttributes;
        }
    }
}

This can then be used like:

@Html.TextBoxForViewModel(m => m.Name)
Chad Edrupt
  • 1,564
  • 11
  • 17
1

Wanted to add the htmlAttributes overload to Chad's answer above for anyone looking to drop it in and have it work. All the other helpers can be built from these examples pretty easily. (Thanks Chad, your extension helped ease my transition into using knockout!)

using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc.Html;

namespace System.Web.Mvc {
    public static class KnockoutExtensions {
        public static MvcHtmlString KnockoutTextBoxFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression ) {
            var metadata = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData );
            var htmlAttributes = HtmlAttributesForKnockout( metadata );
            return htmlHelper.TextBoxFor( expression, htmlAttributes );
        }

        public static MvcHtmlString KnockoutTextBoxFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object attributes ) {
            // convert passed anonymous object (attributes) into IDictionary<string,object> to pass into attribute parser
            var attrs = HtmlHelper.AnonymousObjectToHtmlAttributes( attributes ) as IDictionary<string, object>;
            var metadata = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData );

            var htmlAttributes = HtmlAttributesForKnockout( metadata, attrs );

            return htmlHelper.TextBoxFor( expression, htmlAttributes );
        }

        private static Dictionary<string, object> HtmlAttributesForKnockout( ModelMetadata metadata, IEnumerable<KeyValuePair<string, object>> attributes = null ) {
            var htmlAttributes = new Dictionary<string, object>();

            var knockoutParameter = String.Format( "value: {0}", metadata.PropertyName );
            htmlAttributes.Add( "data-bind", knockoutParameter );

            if ( attributes != null ) foreach ( var attr in attributes ) htmlAttributes.Add( attr.Key, attr.Value );

            return htmlAttributes;
        }
    }
}
Mike Devenney
  • 1,758
  • 1
  • 23
  • 42