Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout binding in MVC4 partial views

In short, what I believe I'm after is a way to provide scope/context to knockout bindings/javascript objects in MVC4 partial views, so that I can reuse the same partial without having them interfere with each other, but still be able to reference parent child viewmodels on the client side.

It's very possible as a knockout noob (and really web dev more broadly) I'm missing a common javascript concept pattern here, but the scenario I'm running into is much like the following. I have a viewmodel on the server, let's say something like:

public class MainModel 
{
    // Other fields 

    SubviewModel Subview { get; set}
}

public class SubviewModel 
{
    // Relevant subview fields
}

And then in the strongly typed (main) partial view I render whatever I need, and pass the subview model into a strongly typed partial (on the server) via @Html.Action. In both cases, I'm setting up the knockout by doing something like this:

var mvcModel = ko.mapping.fromJS(@Html.Raw(JsonConvert.SerializeObject(this.Model)));

ko.applyBindings(mvcModel , document.getElementById("@("divSubview"+Model.Guid)"));

In this case divSubview would be the main wrapper div for the partial and everything generally binds just fine - almost.

The Model.Guid concatenation is there to address the first problem, which is that these partials are generally rendered several on a page for different viewmodels. (Think of it as a list of items, a todo list or whatnot, where each item has subcontent that could change - you get the idea, probably before I attempted to explain further...) So when multiple partials render on the same page, if the div is not uniquely named, if I call applyBindings via document.getElementById, I end up applying the second viewModel to the first rendered div with that name.

The second problem is related, in that when I render the main partial (which there are multiple of for a given page), and then the subview, I often want to refer to either the main viewmodel in the subview or likewise. The workaround I've been using is just setting a named global parameter, and then using that to refer back/forth since I know the script will execute in sequence - but that's quite hacky.

So what's the right solution I'm missing?

I know I could just use Knockout's templates and one larger viewmodel, and ultimately that might be the right solution, but for now there's a great deal going on in the MVC partials (tightly coupled to the server) that I'm not willing to give up. (And secondarily I'm trying to keep the partials as loosely coupled as possible for now - on the client at least - even if I'm taking a bit of a performance hit on the binding/etc.)

What (I think) I'm really after is a way to pass some form of scope to the partials, so that subview partial will have a reference to the main partial viewmodel (and likewise) without having to pollute the global namespace or use unique names. Is there a way for me to say denote a client side JS variable that would have a reference to an object in the child/parent partials? (Or am I missing the point completely and there's a far better way?)

like image 742
Gene Avatar asked Nov 12 '22 19:11

Gene


1 Answers

Long term it really might be best to bite the bullet and use the classic combination of the mapping plugin, a JSON representation of the top-level view model rendered into the page to initialize with, and templates for sub view models. But I understand your pain re existing MVC partial logic, so......

Are you currently rendering a separate script block as part of each sub partial's view to create its Knockout view model and apply its bindings?

Could you not get away with a single binding context and define some kind of module in the page which owns the top-level view model and has its child view models added to it incrementally as the sub partials render, then at the bottom of the page, on DOM ready, apply the bindings?

So, each sub partial's script block would make a call into some function on your module passing in the sub model's raw JSON, which includes its Guid key. Your module then creates a new instance of the sub view model and sets the parent view model as a reference inside it, and pushes it into an observable array of sub view models on the parent view model. Then you have a reference each way.

I'm not sure that you can get around having some kind of page level manager for this structure (if that's what you meant by not polluting the global namespace). In any case, isn't it good to have these partial view models all owned by a single object rather than floating in the global namespace? (if they are)

Hope I understand your context correctly.

UPDATE: Your partials would still be loosely coupled in a sense. The binding expressions in the markup would obviously have to relate to an instance of a sub view model, but that could either be a data context chained down through a foreach on your todo list items or whatever, or explicitly set using the "with" expression, e.g. with someOtherModule.randomStandaloneSubViewModel

like image 68
Tom W Hall Avatar answered Nov 15 '22 09:11

Tom W Hall