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

PROPOSAL: Allow "transparent" rendering of components #4517

Open
bmomberger-bitovi opened this Issue Oct 5, 2018 · 7 comments

Comments

Projects
None yet
3 participants
@bmomberger-bitovi
Copy link
Contributor

bmomberger-bitovi commented Oct 5, 2018

When rendering an element with a callback (e.g. with can-component), the parent element that marks where the rendering is to happen is included in the output. There are cases where this is undesirable. To address this, it should be possible to declare certain components as "transparent", i.e., the component tag itself is not rendered to the document.

This was discussed on a recent live stream (8:17) and a previous live stream.

Proposed example

my-table-row-group.js:

can.Component.extend({
  tag: "my-table-row-group",
  view: stache(`<tr><td>A custom table row</td></tr>
                          <tr><td>{{foo}}</td></tr>`)
  viewModel: { foo: '' },
  transparent: true
});

my-table.stache

<can-import from="./my-table-row-group" />
<table>
  <tr><td>A perfectly normal table row</td></tr>
  <my-table-row-group foo:raw="A supplied table content"/>
</table>

Output of {{> my-table}}

<table>
  <tr><td>A perfectly normal table row</td></tr>
  <tr><td>A custom table row</td></tr>
  <tr><td>A supplied table content</td></tr>
</table>

Prior art:

  • the can-tag property of can-view-import allows a "hand-off tag" -- another element with a registered tag callback -- to be rendered in place of a component while that component is being dynamically loaded. This is done by replacing the <can-import> tag wholesale, and does not lead to memory leaks (the nodelist is properly updated.)

  • Stache Expressions that resolve to component elements and document fragments already can be added in place, replacing the magic tag. This is also true for HTML markup strings when the triple brace syntax ({{{expr}}}) is used. See https://github.com/canjs/can-view-live/blob/master/lib/html.js#L115 for the low-level handling of document fragment data when rendering templates with CanJS.

  • Some previous experiments I did with SVG involved attaching the content of a tag callback as a sibling to the originating tag instead of as descendants. I don't recommend this approach, but it worked in this limited case. https://github.com/airhadoken/react-fractals/blob/master/src/Pythagoras.js#L91

Use cases:

Table row sets

A component that resolves to a series of <tr> tags may render incorrectly if the component tag sits between the parent <table> or <tbody> tags and the child <tr> tags when rendered to the DOM.

Other "interesting" HTML5 content models

The HTML5 specification declares the content models of some tags as collections of other tags, rather than empty, text, or some type class of content. <table> above is one such with <tr>s, <colgroup>s, <thead> and <tbody>, etc., and its inflexibility regarding invalid markup is one of the most egregious, but other content models may lead to undefined behavior.

Examples taken from examining the Content Model properties of tags in https://dev.w3.org/html5/html-author

parent required children notes
<dialog> and <dl> <dt> and <dd>
<ol> and <ul> <li> browser engines seem to tolerate invalid markup well
<ruby> <rt> and <rp> after phrasing content
<figure> <legend> required to be first or last child
<object> <param>
<video> <source>
<fieldset> and <details> <legend> <legend> is required by spec before flow content, but rarely used for fieldset in practice
<select> <option> or <optgroup>
<datalist> and <optgroup> <option>

SVG components

SVGs fail to render content when non-SVG tags are inserted at any level. An SVG component that resolved to, e.g., a <g> and some descendants, should only render its component view to the document and remove its declarative tag.

Concerns

No root element / Avoid memory leaks

The transparent rendering mechanism will render a list of items inline with other content. Therefore, nodelists should be registered and updated accordingly.

ViewModel:

The viewmodel of the component will not be available, since no tag exists for canViewModel('component-tag') calls.

  • Should each immediate element child of the component tag receive the viewmodel if it does not have its own?

Where should the option be set?

Does a component know how it should be used, enough to declare itself transparent? Would it instead be better to declare it at time of usage? E.g.:

<can-import from="./my-table-row-group" />
<table>
  <my-table-row-group foo:raw="bar" opt:transparent="true"/>
</table>

@justinbmeyer justinbmeyer changed the title PROPOSAL: Allow "transparent" rendering of components and custom elements PROPOSAL: Allow "transparent" rendering of components Oct 5, 2018

@matthewp matthewp added the proposal label Oct 5, 2018

@bmomberger-bitovi

This comment has been minimized.

Copy link
Contributor

bmomberger-bitovi commented Oct 8, 2018

Good question raised by @matthewp:

How will connectedCallback work in the transparent component proposal?

Some ideas:

  • Pass null
  • pass a fragment of all child nodes.
  • Pass the first element child
  • Pass the parent element
  • Apply the array of all element children
  • Pass the can-view-nodelist
  • Create the node then immediately remove it from the DOM after connectedCallback is run, and promote its children to the parent.

I don't think there's an optimal answer in this case, but someone probably already knows the least-bad solution

@frank-dspeed

This comment has been minimized.

Copy link
Contributor

frank-dspeed commented Oct 15, 2018

@bmomberger-bitovi when i understand right the solution that i apply to this is i simply don't never ever use can-import :D as we can deprecate that via the ESM Loader so the right way is to return the element in the viewModel of your Component

@frank-dspeed

This comment has been minimized.

Copy link
Contributor

frank-dspeed commented Oct 15, 2018

@bmomberger-bitovi see the canjs routing guide as a example for what i mean by return the element inside the viewmodel

@frank-dspeed

This comment has been minimized.

Copy link
Contributor

frank-dspeed commented Oct 15, 2018

but i think maybe the most best way is really still the programatic way to return the content of the component instance in place via a custom function on the viewmodel

@frank-dspeed

This comment has been minimized.

Copy link
Contributor

frank-dspeed commented Oct 15, 2018

Examples

https://canjs.com/doc/can-stache/expressions/call.html

Return stache (Good Easy way!!!!)

as you can return stache via a function in the view Model do this

Component.extend({
    tag: "my-table",
    view: `
<table>
  <tr><td>A perfectly normal table row</td></tr>
  {{myTableRowGroup(foo="A supplied table content")}}
</table>

    `,
    ViewModel: {
        get myTableRowGroup(data) {
// can be also from file
                    return stache(`<tr><td>A custom table row</td></tr>
                          <tr><td>{{foo}}</td></tr>`)(data)
            }
        }
    }
});

Results in:

<my-table>
<table>
  <tr><td>A perfectly normal table row</td></tr>
   <tr><td>A custom table row</td></tr>
   <tr><td>A supplied table content</td></tr>
</table>
</my-table>

as you can see in this example you would also not need the my-table component as you could have a my-table.stache that you can call with data that you can directly return stache with data inside any viewModel via callexpression

Return a other Component (To Complex!)

not finished

Component.extend({
    tag: "my-table",
    view: `
<can-import from="./my-table-row-group" />
<table>
  <tr><td>A perfectly normal table row</td></tr>
  {{myTableRowGroup({ foo: 'A supplied table content' })}}
</table>

    `,
    ViewModel: {
        myTableRowGroup: {
            default() {

            }
        }
    }
});
@frank-dspeed

This comment has been minimized.

Copy link
Contributor

frank-dspeed commented Oct 15, 2018

@bmomberger-bitovi if the return stache example don't fits in your concept ping me here and i will show a working example of your proposal via return of a complet component instance and if you wish also demonstrate cross live binding

but that would be hugh example so i want to do it on request only i think the return stache() method is exactly what you call return transperent

@frank-dspeed

This comment has been minimized.

Copy link
Contributor

frank-dspeed commented Oct 20, 2018

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