Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request feature: runtime composition of ko 3.2 component #1463

Merged
merged 4 commits into from
Oct 17, 2014

Conversation

SteveSanderson
Copy link
Contributor

Right now, ko 3.2 component supports composition directly in component's template. This kind of composition only happens in definition time.

Would suggest to support runtime composition, so we can define kind of "container" component, and make tags more expressive.

The usage is something like this:

<ko-toolbar params="..."> <!-- this is a container component -->
  <!-- compose buttons at runtime -->
  <ko-toolbar-button params="..."></ko-toolbar-button>
  <ko-toolbar-button params="..."></ko-toolbar-button>
</ko-toolbar>

FYI, this feature is available on Google Polymer through <content> tag,

@sccooper
Copy link

This is similar to what I was asking for in #1458 but this is a better example of why it's a good idea.

I think it could be easily implemented by allowing the component custom binding to apply its viewModel to its descendants like a normal custom binding when no template is defined by the component.

Of course you could accomplish it with a custom binding for ko-toolbar but it sticks out like a sore thumb that you can't make use of the syntactic sugar of custom elements, especially when it's just a custom binding underneath.

You could also accomplish it by supplying the buttons as a parameter to ko-toolbar, but that lacks flexibility.

There are definite use cases where it feels natural to treat the the inner html of the custom element as the template of the component.

@3cp
Copy link
Author

3cp commented Jul 22, 2014

I have a different view, to take advantage of runtime composition, the logic of container and child should be totally decoupled.

The child could bind to something that the container has no visibility on.

<ko-toolbar-button params="action: something"></ko-toolbar-button>

ko could parse all container and children components independently, and the container only need to provide a placeholder in DOM for ko to inject the children. This also means ko doesn't care whether the children are components or DOM tags or mixed.

Sure, we want communication between child and the container, that can be achieved with special binding context like $parentComponent and $parentComponentContext, they are dependantObservable that will be hooked up at runtime.

<ko-toolbar-button params="action: something,
  enabled: posts().length > 0 && $parentComponent.enabled() ">Clear Posts!</ko-toolbar-button>

@SteveSanderson
Copy link
Contributor

Yep, agreed - there's definitely a good use case here and I'm sure it will fit into the components system. We can try to formalise this into a plan after 3.2.0 ships.

@jonbnewman
Copy link

I could definitely use this...another vote in favor.

@thelinuxlich
Copy link

+1

1 similar comment
@johnderjohn
Copy link

+1

@pbouzakis
Copy link

+1

@vamp
Copy link

vamp commented Aug 8, 2014

+:8ball:

@coffeebohne
Copy link

+1, it will simplify components sharing between apps

@julian-maughan
Copy link

+1

1 similar comment
@johnnyreilly
Copy link

👍

@FeistyMango
Copy link

+1 Also referencing #1493.

Would provide a clean way to programmers to configure presentation logic in a much cleaner and natural way.

@iratherscribble
Copy link

This is very important for me. In my case, I need to be able to dynamically construct the viewmodel and then a template for a viewmodel. This is for creating forms with input fields where those inputs fields can vary based on the data type of the properties for the viewmodels (so I can construct an input field that has a custom binding handler to load a jQuery datepicker, etc.).

There is an assumption that templates and viewmodels are independent and that templates can be loaded before the viewmodel is instantiated. But the templates used do not exist. There is a builder that looks at the viewmodel properties and creates the markup based on conditions defined within the builder of the template.

Referencing #1458 that has more details about the issues and the limitations encountered.

I had a solution working (building a viewmodel and template dynamically) with version 3.1, but the solution was incomplete because I could not re-apply bindings to the container element with an updated viewmodel, or remove the existing binding and apply a new one to the container element.

@mbest mbest added this to the 3.3.0 milestone Aug 26, 2014
@roerdinkholder
Copy link

+1

@nwwells
Copy link

nwwells commented Sep 5, 2014

+1

@mbaranov
Copy link

After looking into Durandal's implementation, mentioned in #1493, I tried to figure out how the components system can be extended to allow for similar functionality, as sort of an exercise as I'm learning my way around Knockout. I've created a new binding based on the code of the componentBinding which allows defining overridable parts of a component. Here is a quick example: http://jsfiddle.net/mbaranov/6zvjfd2y/. It shows an implementation of a simple expander control with the overridable header and content parts. The composableComponent binding implementation is a word-by-word copy of the component binding with bits of the new code, that I maked with the comments.

When injecting supplied nodes into a component output, it’s useful to
be able to pass the node array directly from the component viewmodel
and bind to those nodes in the view. If we didn’t have this feature,
you’d have to dynamically create a named template or something, which
would be awful.
@SteveSanderson
Copy link
Contributor

OK, implemented. This is a reasonably simple change and hopefully provides a very valuable enhancement.

This implementation is slightly more low-level than @mbaranov's, in that it just supplies the inner nodes to createViewModel and doesn't try to parse out any internal structure from those nodes (like @mbaranov's data-part feature). I think it's better for the KO core implementation to be unopinionated about any internal structure in your templates, so the developer is free to use any technique they like to identify different segments of template (e.g., based on a data-part attribute, or a class name, or anything else).

@rniemeyer
Copy link
Member

I think that it would be nice if the inner nodes were also provided in a context variable ($componentTemplate or nodes or something like that) for easy binding, so in normal cases the view model wouldn't need to reference them.

SteveSanderson added a commit that referenced this pull request Oct 17, 2014
Request feature: runtime composition of ko 3.2 component
@SteveSanderson SteveSanderson merged commit 96aa912 into master Oct 17, 2014
@SteveSanderson SteveSanderson deleted the 1463-templated-components branch October 17, 2014 10:35
@SteveSanderson
Copy link
Contributor

I think that it would be nice if the inner nodes were also provided in a context variable

Good idea! Done. It's called $componentTemplateNodes (a bit verbose, but consistent with the createViewModel parameter templateNodes).

@unsafecode
Copy link

Going further into this discussion, I've reworked a bit the latest available source for the component binding to also support the so called insertion points: imagine we want a bootstrap-panel component that wraps its content into a bootstrap panel. We would like to write our component as

<bootstrap-panel>
    <header>
        My panel
    </header>
    <main>
        <strong>My content</strong>
    </main>
    <footer>
        My footer
    </footer>
</bootstrap-panel>

Then, our template could look like:

<div class="panel panel-default">
    <div class="panel-heading">
        <content select="header" />
    </div>
    <div class="panel-body">
        <content select="main" />
    </div>
    <div class="panel-footer">
       <content select="footer" />
    </div>
</div>

The key feature here is the <content select="..." /> element, which specifies an insertion point by means of a querySelectorAll expression. At runtime, before binding occurs, and when the component template is loaded, I've set a function that processes all insertion points, looking a the newly available originalChildNodes.

The final output, then, will be:

<bootstrap-panel>
    <div class="panel panel-default">
        <div class="panel-heading">
            <header>
                My panel
            </header>
        </div>
        <div class="panel-body">
            <main>
                <strong>My content</strong>
            </main>
        </div>
        <div class="panel-footer">
            <footer>
                My footer
            </footer>
        </div>
    </div>
</bootstrap-panel>

I really hope this approach can prove to be useful. At present time, though, I needed to edit the component binding source, since there's no pluggable way the edit it easliy, so if any of you are interested, I can probably build a PR and make it available.

@xavierzwirtz
Copy link

@unsafecode, that is very similar to what was suggested in #1493 however I like your syntax better.

@austinhyde
Copy link

So, based on this PR & discussion, I mocked up a rudimentary <content> component. You can see it in action here here.

@kaleb
Copy link

kaleb commented Nov 10, 2014

This also takes care of #1493

<content select="[data-parts='foo']">Default content</content>

@mbest
Copy link
Member

mbest commented Nov 10, 2014

@austinhyde That looks pretty nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.