Skip to content

Instantly share code, notes, and snippets.

@janfoeh
Last active December 31, 2015 01:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janfoeh/7914187 to your computer and use it in GitHub Desktop.
Save janfoeh/7914187 to your computer and use it in GitHub Desktop.
Nested viewmodels for Knockout
ko.bindingHandlers.childVm = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var vmName = ko.unwrap(valueAccessor()),
childVm;
childVm = ko.utils.arrayFirst(viewModel._childVms(), function(item) {
return item._viewmodelName() === vmName;
});
// 'with' returns {controlsDescendants: true}, so we have to pass it up - otherwise,
// the descendant bindings will be bound twice ("You cannot apply bindings multiple times to the same element")
return ko.bindingHandlers.with.init(element, function() { return childVm; }, allBindingsAccessor, viewModel, bindingContext);
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var vmName = ko.unwrap(valueAccessor()),
childVm;
childVm = ko.utils.arrayFirst(viewModel._childVms(), function(item) {
return item._viewmodelName() === vmName;
});
ko.bindingHandlers.with.update(element, function() { return childVm; }, allBindingsAccessor, viewModel, bindingContext);
}
};
(function(ko, $) {
"use strict";
ko.bindingHandlers.modal = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = ko.unwrap(valueAccessor()),
childVmName,
parentVm,
childVmSetupContext,
templateToRender,
childVm,
clickHandler;
// we...
// - search for a child viewmodel called 'childVmName'
// - by asking 'parentVm'
// - and initialize the child with an object given us as 'childVmSetupContext'
childVmName = options.viewmodelName;
parentVm = options.parentViewmodel ? options.parentViewmodel : bindingContext.$parent;
childVmSetupContext = options.setupViewmodelWith ? options.setupViewmodelWith : bindingContext.$data;
templateToRender = options.templateName
childVm = parentVm.getVm(childVmName);
// we want our modal to be set up, rendered and displayed when the user clicks
// on the element we are bound to
clickHandler = function clickHandler() {
// set up the child viewmodel with whatever object we have been given,
// if the child wants to be set up
if (typeof childVm.setup === 'function') {
childVm.setup(childVmSetupContext);
}
// initialize our modal
var modal = new MyCoolModalLibrary()
// get a reference to the DOM element our modal library stores the
// content to be displayed in
var modalDomContainer = modal.getContainer();
// tell Knockout to render the template, with our child viewmodel
// as the context, into the modals DOM container
ko.renderTemplate(templateToRender, childVm, {}, modalDomContainer);
modal.display();
};
ko.bindingHandlers.click.init(element, function() { return clickHandler; }, allBindingsAccessor, viewModel, bindingContext);
}
}
};
})(ko, $);
(function(ko, $) {
"use strict";
ko.bindingHandlers.waitForVm = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var vmName = ko.unwrap(valueAccessor()),
subscription,
childVm;
childVm = ko.utils.arrayFirst(viewModel._childVms(), function(item) {
return item._viewmodelName() === vmName;
});
// if the child viewmodel isn't available yet...
if (!childVm) {
// subscribe to the list of child viewmodels
subscription = viewModel._childVms.subscribe(function() {
childVm = ko.utils.arrayFirst(viewModel._childVms(), function(item) {
return item._viewmodelName() === vmName;
});
// and if it has become available now, remove the subscription,
// apply the bindings to our child elements and make ourselves visible
if (childVm) {
subscription.dispose();
ko.applyBindingsToDescendants(bindingContext, element);
ko.bindingHandlers.visible.update(element, function() { return childVm; }, allBindingsAccessor, viewModel, bindingContext);
}
});
}
// hide ourselves and our children if the viewmodel isn't
// immediately available on init().
ko.bindingHandlers.visible.update(element, function() { return childVm; }, allBindingsAccessor, viewModel, bindingContext);
// prevent our descendant elements from being bound if the
// viewmodel isn't immediately available on init(). We will
// take care of that later ourselves through ko.applyBindingsToDescendants
return {controlsDescendantBindings: !childVm};
}
};
})(ko, $);
// Sub-viewmodel handling. Use with borrowed constructor pattern
var Parent = function Parent() {
var that = this;
this._childVms = ko.observableArray();
this.addChildVm = function addChildVm(vm) {
var vmName = vm;
vm = new app.viewmodels[vmName]();
vm.initChildFromParent(that);
that._childVms.push( vm );
return vm;
};
this.getVm = function getVm(name) {
return ko.utils.arrayFirst(that._childVms(), function(item) {
return item._viewmodelName() === name;
});
};
};
app.viewmodels.Parent = Parent;
// Parent-viewmodel handling. Use with borrowed constructor pattern
var Child = function Child() {
var that = this;
this._viewmodelName = function _viewmodelName() {
throw "Viewmodel does not implement _viewmodelName";
};
this._parentVm = null;
this.initChildFromParent = function initChildFromParent(parentVm) {
that._parentVm = parentVm;
if (typeof that.init === 'function') {
that.init();
}
};
};
app.viewmodels.Child = Child;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment