Monday, June 10, 2013

Adhering to the DRY Principal in Javascript/Knockout

One of the things that honestly "hurt my feelings" when developing an HTML5 replacement application awhile back - was that the ViewModel needed to be declared in multiple places. Once in the service and again in the client. Of course, all the demonstrations during tech-week seemed to gloss over this with very simplistic applications. Required reading - Pragmatic Principles
Now, there may be ample reason to want to do this -> what I'm enacting upon in the client may require a subset (possibly?) but I would contend that it should probably be a different service call. Don't transfer bytes that you don't need! So I created a static class helper to be used within a Razor template - and proceeded to forgot all about it. 
A colleague from another department heard about/asked me about it and I rediscovered it. Enjoy. As always YMMV. If you improve on it - share it. I'll laud you copiously.

The helper:
public static class HtmlHelper
    {
        private static string JavaScriptify(string s)
        {
            return string.Join(".", s.Split('.').Select(x => x[0].ToString().ToLower() + x.Substring(1, x.Length - 1)));
        }

        public static IHtmlString KnockoutFrom<T>(this HtmlHelper<T> html, T obj)
        {
            var serializer = new JsonSerializer
                                 {
                                     ContractResolver = new CamelCasePropertyNamesContractResolver()
                                 };

            var sb = new StringBuilder();
            sb.Append("(function() {");

            var json = JObject.FromObject(obj, serializer);

            sb.Append("var vm = ko.mapping.fromJS(" + json + ");");

            var type = obj.GetType();

            var ns = JavaScriptify(type.Namespace);
            sb.Append("namespace('" + ns + "');");
            sb.Append(ns + "." + JavaScriptify(type.Name) + " = vm;");

            sb.Append("})();");

            return new HtmlString(sb.ToString());
        }
    }


The template:
@Html.KnockoutFrom(Model)

No comments: