canjs / can-component

Custom elements and widgets
https://canjs.com/doc/can-component.html
MIT License
8 stars 8 forks source link

Add context to `<can-slot>` #105

Open Sinjhin opened 7 years ago

Sinjhin commented 7 years ago

It would be good to add context to <can-slot>s.

This could look like <can-slot name='subject' context='this' /> or <can-slot name='subject' {context}='this' /> or <can-slot name='subject' context='doSomething(a, b, c)' />

Using the curly brackets for binding is up for discussion.

Things left to do:

justinbmeyer commented 7 years ago

Reasons to use plain attribute name (context='this', context='doSomething(a,b,c):

Reasons to use curly attribute name ({context}='this', {context}='doSomething(a,b,c):

matthewp commented 7 years ago

I don't think it would be a good idea to add context to a can-slot. This takes power away from the developer that is implementing the slot to define their own context and gives it to component that is slotting it. If the component needs to have control over a subtemplate then passing the template through normal methods (like the component's own attributes) is more appropriate.

justinbmeyer commented 7 years ago

@matthewp I think you might have it backwards. The developer implementing the slot has control over the context the can-template element provides.

matthewp commented 7 years ago

I can't find any code that references can-slot or can-template in can-component. Where can I learn more about this feature so that i can understand it better?

Sinjhin commented 7 years ago

@matthewp look at https://github.com/canjs/can-stache/pull/209 and https://github.com/canjs/can-component/pull/103

justinbmeyer commented 7 years ago

There are docs in progress here: https://github.com/canjs/can-component/tree/4-can-slot/docs

It's a pretty common ask to want some of the data on the view-model available to the "user dom" (this is why can-component had leakScope: true by default at first).

Say I had an autocomplete that looked like:

VM = DefineMap.extend({
  query: "string",
  get resultsPromise(){
    return Search.getList({query: this.query});
  }
})

Component.extend({
  ViewModel: VM,
  view: `<input .../> 
    <ul>{{#each resultsPromise.value}}<li>...{{/}}</ul>`
})

If we want to let people customize the search results, we need to be able to pass the search results back to them. context (or as I show {this}) would allow us to pass the search results like:

<auto-complete>
  <can-template name="results">
    {{#if this.isPending}} Loading ... {{/if}}
    {{#each this}}<div>....{/}}
  </can-template>
</auto-complete>

Implemented as follows (with default search results):

Component.extend({
  tag: "auto-complete",
  ViewModel: VM,
  view: `<input .../> 
    <can-slot {this}="resultsPromise" name="results">
      <ul>{{#each resultsPromise.value}}<li>...{{/}}</ul>
    </can-slot>`
})
matthewp commented 7 years ago

Ok, I think I understand the feature now and rescind my previous objection. I think this is blurring the lines between component API and stache a bit much though.

justinbmeyer commented 7 years ago

@matthewp why do you think that? There is a bluring as can-stache needs to be aware of <can-template> in a similar way as can-stache is aware of custom elements. I think it's ok for can-stache to provide some low level custom-element fundamentals similar to how the DOM might provide <slot>s.

Another alternative, which might have been what you were thinking is for <can-template> to be able to "steal" values from the viewModel:

<auto-complete>
  <can-template name="results" {^search-results-promise}="*searchResultsPromise">
    {{#if *searchResultsPromise.isPending}} Loading ... {{/if}}
    {{#each *searchResultsPromise}}<div>....{/}}
  </can-template>
</auto-complete>

I don't like this as much. I look at <can-template> as mostly a way to pass a renderer to a can-component. I look at {this} or {context} as a way to specify an argument that gets passed to that argument.

Effectively:

var component = function(renderer) {
  renderer(CONTEXT);
}

component( myRenderer )
matthewp commented 7 years ago

I don't think this is low-level though. It's providing an alternative syntax for executing a function. You can already execute a template in stache like so:

light

<my-element {childtmpl}="someTemplate"/>

my-element template

{{childtmpl(someContext)}}

I don't have a problem with can-component reaching in to its light-dom and building up this .templates thing, I just don't see why there needs to be a second way within the language to execute a renderer function. I can see the advantage of the (arguably) nicer syntax but that can be done in userspace through custom elements.

justinbmeyer commented 7 years ago

@matthewp how could be done in userspace? <content> is basically the same thing (implemented almost exactly the same) and can't be done in userspace.

matthewp commented 7 years ago

To summarize my opinion: I think it's a good idea to separate slotting and hydrating. Otherwise, how could I do something like pass a can-template to another element?

matthewp commented 7 years ago

@matthewp how could be done in userspace? is basically the same thing (implemented almost exactly the same) and can't be done in userspace.

So, assuming that this.templates within the component refers to the light dom can-templates:

Component.extend({
  tag: "my-element",
  events: {
    inserted: function(){
      this.viewModel.templates = this.templates;
    }
  }
})

(maybe there should be an option for can-component to do this for you?)

{{templates.sometmpl(someContext)}}

If that works, and it should, then building higher level constructs like a custom element that takes a context and a template and executes that template should be trivial.

justinbmeyer commented 7 years ago

@matthewp yeah, I've thought about this, but it's so heavily "view" related, that I avoided it (which might require people creating a templates property on their VM).

Perhaps for can-element we can create a view or templates property automatically on the element?