View Composition

While playing around with RecoilJS I really grew to love its compose binding. So much so that when I was back working on one of my KnockoutJS-based projects that I just HAD to come up with a new binding. Add to the fact that I’m also using Knockback and wanted to at least make concessions for the memory/release structure that framework provides, and suddenly I had motivation to make a new binding!

Where my view at?

First things first, I made a view binding. I chose to separate the compose & view bindings into 2 separate bindings because I could. It also just felt clumsy passing in an anonymous object sorta like you need to do with knockout’s template binding; I mean, the whole idea behind this was to simplify.

ko.bindingHandlers.view = { init: function (element, valueAccessor) { $(element).data("composeBinding", { view: valueAccessor() }); } }

The view binding is entirely optional. It just lets you smash down a specific view inside of your composition container, if that’s how you roll.

Compose THIS!

So then I needed to handle the actual compose binding:

ko.bindingHandlers.compose = {     after: ["view"],     init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {         var bindingValue = ko.utils.unwrapObservable(valueAccessor()),             hasValue = !!bindingValue; if (hasValue) {             var viewSettings = $(element).data("composeBinding") || {};             //if no view was specified via the view binding, retrieve any available             //view attribute off of the provided value             if (viewSettings.view || bindingValue.view) {                 $(element).data("composeBinding", $.extend(viewSettings, { valid: true }));             } else {                 $(element).data("composeBinding", undefined);                 throw new Error("Invalid compose binding: no view found for composition.");             }         } else {             $(element).empty().data("composeBinding", undefined);             throw new Error("Invalid compose binding: no value provided as data context.");         } return { 'controlsDescendantBindings': true };     },     update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {         var initData = $(element).data("composeBinding"),             valueData = ko.utils.unwrapObservable(valueAccessor());         if (initData.valid) {             var view = ko.utils.unwrapObservable(initData.view);             if (view) {                 view = initData.view;             } else {                 view = ko.utils.unwrapObservable(valueData.view);                 if (view) {                     view = valueData.view;                 }             }             kb.releaseOnNodeRemove(valueAccessor(), element);             ko.bindingHandlers.template.update(element, function () { return { name: view, data: valueAccessor() }; }, allBindingsAccessor, viewModel, bindingContext);         } else {             $(element).empty();         }     } }

So what?

This just enables some extra laziness on my part. I can now set up a view composition with data-bind=”compose: subView” where subView is a property in my current context containing a property named “view”. You can either use a straight-up string or some manner of ko.observable which returns a string, letting you chance which template is composed in place if necessary (think view/edit as one case).

If you get really persnickety you can always data-bind=”compose: subView, view: ‘my-very-specialized-circumstances’” because why not?

Oh yeah

I haven’t actually done any optimization on this thing just yet, or even really used it anywhere super heavy in my own projects. I just thought it was neat and figured I would slap it up here.