How to keep content in the document?

160 views
Skip to first unread message

Justin Fagnani

unread,
Feb 6, 2014, 12:08:58 AM2/6/14
to polymer-dev
I'm still trying to wrap my head around the Zen of custom elements. Right now I'm pondering how to keep content out of the Shadow DOM, in document-oriented apps, like CMS's, blogs, forums, doc viewers, etc. Currently every app I've seen has a single tag in the top document and from there it's shadow all the way down.

The html5rocks.com article on Shadow DOM includes this great paragraph:

One rule of thumb, which is possibly being violated here, is that you shouldn’t put content in Shadow DOM. Content must be in the document to be accessible to screen readers, search engines, extensions and so on. Shadow DOM is for all of the semantically meaningless markup you need to create an attractive, usable widget. But the content should stay in the page.

This makes a lot of sense when you start internalizing the web components way. For a static document that uses custom tags, it's even easy to demo, with something like:

<html><body>
  <blog-post>
    <blog-title>Keep Content Out of Shadow DOM</blog-title>
    <blog-body>It's easy!</blog-body>
    <blog-footer><p>We can put <b>markup</b> here too</p></blog-footer>
  </blog-post>
</html></body>

Sweet. This would presumably be crawlable too, if the custom tag names don't trip up the crawler.

But I'm a little lost on how best to achieve this ideal in a dynamic app, since the markup that uses the custom elements itself needs to be generated, and the primary way we have of doing that is via a component and its template. Doing that would mean that the markup ends up in shadow DOM, which we don't want (assuming light dom option is going away). The other options are imperatively building up the DOM, or using template binding outside of custom elements, which I've heard isn't being encouraged (please correct me if I'm wrong there).

But let's assume that using a template is the way to go. It'd look like this:

<html><body>
  <template id="post" bind>
    <blog-post>
      <blog-title>{{ title }}</blog-title>
      <blog-body>{{ body }}</blog-body>
      <blog-footer><p>We can put <b>markup</b> here too</p></blog-footer>
    </blog-post>
  </template>
</html></body>

Then do a: querySelector('#post').model = post; and voilá!

This has a few interesting implications.

First, it seems like there's two sets of "bindings" for everything: one set of template binding mustaches to generate the document's DOM that uses custom tags, and the other would be mostly a set of content tags with selectors in the custom tags which project the content into the right places in the shadow trees.

It would also seem to imply, though I haven't tried to go down this road so salt grains apply, that custom tags shouldn't really be interfacing with directly an app's data-model, but mainly with their own attributes and content. Bindings would still be common for controlling rendering based on component state. I could maybe see that being a principle, but I haven't heard it before. I'm unsure with this approach how rendering would best be controlled based on the structure of content. Mutation observers that update internal state variables?

Am I totally off in the weeds? I have not seen a Polymer app built this way, but I have not yet seen a Polymer app that adheres to the html5rocks advice either.

Thanks,
  Justin

Dominic Cooney

unread,
Feb 6, 2014, 12:59:17 AM2/6/14
to Justin Fagnani, polymer-dev
This is a great question. My $0.02 inline.

On Thu, Feb 6, 2014 at 2:08 PM, Justin Fagnani <justin...@google.com> wrote:
I'm still trying to wrap my head around the Zen of custom elements. Right now I'm pondering how to keep content out of the Shadow DOM, in document-oriented apps, like CMS's, blogs, forums, doc viewers, etc. Currently every app I've seen has a single tag in the top document and from there it's shadow all the way down.

The html5rocks.com article on Shadow DOM includes this great paragraph:

One rule of thumb, which is possibly being violated here, is that you shouldn’t put content in Shadow DOM. Content must be in the document to be accessible to screen readers, search engines, extensions and so on. Shadow DOM is for all of the semantically meaningless markup you need to create an attractive, usable widget. But the content should stay in the page.

I wrote that, and let me tell you, I was in a different place then. It was a different time. User agents grok this stuff better than I imagined at this time (though it is not perfect.) I don't think "flattened tree" was even a concept back then. Let alone doing a lot of heavy-lifting with non-visual elements.

This makes a lot of sense when you start internalizing the web components way. For a static document that uses custom tags, it's even easy to demo, with something like:

<html><body>
  <blog-post>
    <blog-title>Keep Content Out of Shadow DOM</blog-title>
    <blog-body>It's easy!</blog-body>
    <blog-footer><p>We can put <b>markup</b> here too</p></blog-footer>
  </blog-post>
</html></body>

Sweet. This would presumably be crawlable too, if the custom tag names don't trip up the crawler.

But I'm a little lost on how best to achieve this ideal in a dynamic app,

I doubt this was a solved problem for the pre-Polymer dynamic app, either.
 
since the markup that uses the custom elements itself needs to be generated, and the primary way we have of doing that is via a component and its template. Doing that would mean that the markup ends up in shadow DOM,

Shadow DOM isn't a lock-box, there's also projection with <content>. It might be an interesting exercise to say "what should my markup look like?" and then try to work from there.
 
which we don't want

Lots of chunks of markup belong in Shadow DOM. I think that's OK.

(assuming light dom option is going away).

Light DOM isn't going away. Can you elaborate on what you mean?
 
The other options are imperatively building up the DOM, or using template binding outside of custom elements, which I've heard isn't being encouraged (please correct me if I'm wrong there).

Once you're running script, if your UA supports Shadow DOM you should use Shadow DOM and expect your UA to understand it.

There might be a case for content in the ordinary DOM for compatibility with UAs which don't understand Shadow DOM. But you might not want to rely on them running script, either? I'm not sure.
 
But let's assume that using a template is the way to go. It'd look like this:

<html><body>
  <template id="post" bind>
    <blog-post>
      <blog-title>{{ title }}</blog-title>
      <blog-body>{{ body }}</blog-body>
      <blog-footer><p>We can put <b>markup</b> here too</p></blog-footer>
    </blog-post>
  </template>
</html></body>

Then do a: querySelector('#post').model = post; and voilá!

This has a few interesting implications.

First, it seems like there's two sets of "bindings" for everything: one set of template binding mustaches to generate the document's DOM that uses custom tags, and the other would be mostly a set of content tags with selectors in the custom tags which project the content into the right places in the shadow trees.

I guess for me this boils down to the way I want the author to interact with my component.

If the thing is content--like it might have a button or a bold run in it--then I'd try to make it markup and use <content> to project it.

If the thing isn't content--like a blob of JSON or an array of presidents--then I'd try to use properties for it.

Then there's a way to blat data out as content--template. Because ultimately we want to present stuff.

Going from content to data is weird. I only did that when I was writing an interactive, reflective DOM viewer in Polymer. It's niche.

Polymer elements mean things that were once objects, like XMLHttpRequest, can now  be elements, like <polymer-ajax>. You don't need to use <content> for those because you're not presenting them. But somehow I expect these things to be relatively "static" in arrangement with just their properties changing. The DOM tree is used like XAML to create them so you can wire up their properties.

It would also seem to imply, though I haven't tried to go down this road so salt grains apply, that custom tags shouldn't really be interfacing with directly an app's data-model, but mainly with their own attributes and content.

Yes, this is right. Their own properties/attributes/events, and content for when they are presenting content. If you get hooks too deeply and directly into the app's data model, you'll get a tangle. Divide and conquer, I think.

I imagine Polymer applications are a box. Within the box there's an arrangement of elements (at a coarse-grained level with a tree structure) and databindings (at a finer-grained level with a network structure.) It's sitting there like a machine that when you pour data into it responds in a range of ways, but all of that is carried out by the mechanism. There's very little code in there.

There's a bit of code on the border which might take your data and massage it into something amenable to databinding. And there's legacy code wrapped up in some Polymer elements at the leaf of the tree.

I think it makes sense to use the DOM tree to compose the pieces, a bit like XAML. Shadow DOM can act as a container for that. But I think <content> is really for presentation.
 
Bindings would still be common for controlling rendering based on component state. I could maybe see that being a principle, but I haven't heard it before. I'm unsure with this approach how rendering would best be controlled based on the structure of content. Mutation observers that update internal state variables?

<content>, CSS and databinding all have a role here. I think it makes sense to control rendering based on component state. But I don't see the connection to structure of content outside of unusual ones like the DOM viewer. Can you give me an example?
 
Am I totally off in the weeds? I have not seen a Polymer app built this way, but I have not yet seen a Polymer app that adheres to the html5rocks advice either.

Thanks,
  Justin

Follow Polymer on Google+: plus.google.com/107187849809354688692
---
You received this message because you are subscribed to the Google Groups "Polymer" group.
To unsubscribe from this group and stop receiving emails from it, send an email to polymer-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/polymer-dev/CAEKsHmAiTh3ZNPTEyeOwsTHLG6JpNn8JrZugGri%2BUJkeTSTDhg%40mail.gmail.com.
For more options, visit https://groups.google.com/groups/opt_out.



--

Sébastien Cevey

unread,
Feb 6, 2014, 6:37:56 AM2/6/14
to Dominic Cooney, Justin Fagnani, polymer-dev
On 6 February 2014 05:59, Dominic Cooney <domi...@google.com> wrote:

I was about to post to this ML to ask a very similar question, so pardon me for hijacking this thread:
 
Shadow DOM isn't a lock-box, there's also projection with <content>. It might be an interesting exercise to say "what should my markup look like?" and then try to work from there.

I've just encountered a similar dilemma to Justin while building a monitoring dashboard using Polymer.

Please let me know if any of what I'm trying to do is an anti-pattern or if there is a better alternative.

The idea is to reuse custom elements through composition and a shared interface:

I've got a <my-chart> custom element that renders the data found in the <table> it contains as a visual chart:

<my-chart render="bar">
  <table>...</table>
</my-chart>

Now I'd like to be able to feed data to <my-chart> from various sources, as per the <polymer-ajax> example Dominic mentioned, through composition:

<my-chart render="bar">
  <aws-cloudwatch metric="latency"></aws-cloudwatch>
</my-chart>

<my-chart render="bar">
  <other-monitoring present="throughput"></other-monitoring>
</my-chart>


The problem here is that by default, the <aws-cloudwatch> and <other-monitoring> elements will load data and populate a <table> _in their Shadow DOM_.

There seem to be two obvious solutions (not sure how technically doable they are, I haven't tried yet):

1. Make <my-chart> look at the Shadow DOM of its children, rather than their DOM.
2. Make <aws-cloudwatch> and <other-monitoring> somehow populate their DOM, rather than their Shadow DOM.

Answering the "what should my markup look like?", it feels like 2. is most reasonable (we want the tabular data in the DOM for clients to see, right?). Is it right, or feasible? It doesn't feel like <content> would help, though I might be mistaken.

Alternatively, is this a misuse of Web Components?


I'd love to hear what you guys think and what are the suggested patterns to do this, as it feels like composition of Web Components could be hugely valuable!

Thanks,

--
Sébastien Cevey
Software Developer
Please consider the environment before printing this email.
------------------------------------------------------------------
Visit theguardian.com   

On your mobile, download the Guardian iPhone app theguardian.com/iphone and our iPad edition theguardian.com/iPad   
Save up to 57% by subscribing to the Guardian and Observer - choose the papers you want and get full digital access.
Visit subscribe.theguardian.com

This e-mail and all attachments are confidential and may also
be privileged. If you are not the named recipient, please notify
the sender and delete the e-mail and all attachments immediately.
Do not disclose the contents to another person. You may not use
the information for any purpose, or store, or copy, it in any way.
 
Guardian News & Media Limited is not liable for any computer
viruses or other material transmitted with or as part of this
e-mail. You should employ virus checking software.
 
Guardian News & Media Limited
 
A member of Guardian Media Group plc
Registered Office
PO Box 68164
Kings Place
90 York Way
London
N1P 2AP
 
Registered in England Number 908396

--------------------------------------------------------------------------

Dominic Cooney

unread,
Feb 7, 2014, 3:12:40 AM2/7/14
to Sébastien Cevey, Justin Fagnani, polymer-dev
On Thu, Feb 6, 2014 at 8:37 PM, Sébastien Cevey <seb....@guardian.co.uk> wrote:
On 6 February 2014 05:59, Dominic Cooney <domi...@google.com> wrote:

I was about to post to this ML to ask a very similar question, so pardon me for hijacking this thread:
 
Shadow DOM isn't a lock-box, there's also projection with <content>. It might be an interesting exercise to say "what should my markup look like?" and then try to work from there.

I've just encountered a similar dilemma to Justin while building a monitoring dashboard using Polymer.

Please let me know if any of what I'm trying to do is an anti-pattern or if there is a better alternative.

The idea is to reuse custom elements through composition and a shared interface:

I've got a <my-chart> custom element that renders the data found in the <table> it contains as a visual chart:

<my-chart render="bar">
  <table>...</table>
</my-chart>

Yeah, it's interesting, isn't it? When there are elements like tables we kind of get into a grey area. I think your touchstone should be:

How do I want people to be able to use my element?

If I want people to be able to whack a my-chart around a table and render it, then yes, you should make that work. There's nothing that says this is the ONLY way my-chart can source its data, though...
 
Now I'd like to be able to feed data to <my-chart> from various sources, as per the <polymer-ajax> example Dominic mentioned, through composition:

<my-chart render="bar">
  <aws-cloudwatch metric="latency"></aws-cloudwatch>
</my-chart>

<my-chart render="bar">
  <other-monitoring present="throughput"></other-monitoring>
</my-chart>


The problem here is that by default, the <aws-cloudwatch> and <other-monitoring> elements will load data and populate a <table> _in their Shadow DOM_.

There seem to be two obvious solutions (not sure how technically doable they are, I haven't tried yet):

1. Make <my-chart> look at the Shadow DOM of its children, rather than their DOM.
2. Make <aws-cloudwatch> and <other-monitoring> somehow populate their DOM, rather than their Shadow DOM.

Answering the "what should my markup look like?", it feels like 2. is most reasonable (we want the tabular data in the DOM for clients to see, right?). Is it right, or feasible? It doesn't feel like <content> would help, though I might be mistaken.

Alternatively, is this a misuse of Web Components?


I'd love to hear what you guys think and what are the suggested patterns to do this, as it feels like composition of Web Components could be hugely valuable!

I would definitely be using databinding and not DOM for this kind of composition. Assume you're in the middle of my-app, something like:

<polymer-element name="my-app" attributes="data">
  ...
  .. nonvisual stuff, push data into the model ..
  <aws-cloudwatch metric="latency" sink="{{data.latency}}"></aws-cloudwatch>

  .. visual stuff, pull data out of the model ..
  <my-chart render="bar" source="{{data.latency}}"></my-chart>

It should not feel icky to have aws-cloudwatch not be a child of my-chart. That relationship might be convenient at first, but what if you want to have a chart AND a label that has 90 %-ile latency? Then you're going to want to have two visual widgets slurping off the same data and having the data source be a child of one element but not the other would be weird.

It's also fine to have a complex data model in a given element. What's not OK is if some irrelevant detail of the my-app's data model bleeds into aws-cloudwatch or my-chart.

It's neat if my-chart can also read from tables, and you could make it do that if source is missing or whatever. But I wouldn't make that the primary way to push data into it.

If you end up slurping data from tables a lot, you might like to make a component which does that--wraps a table and brings its data into the databound world.

Dominic Cooney

unread,
Feb 10, 2014, 7:13:17 AM2/10/14
to Sébastien Cevey, Justin Fagnani, polymer-dev
In hindsight, this would be a great question for Stack Overflow.

Dominic
--

Scott Miles

unread,
Feb 10, 2014, 12:52:00 PM2/10/14
to Dominic Cooney, Sébastien Cevey, Justin Fagnani, polymer-dev
Thank you for the thoughtful answer Dominic.

S


Follow Polymer on Google+: plus.google.com/107187849809354688692

---
You received this message because you are subscribed to the Google Groups "Polymer" group.
To unsubscribe from this group and stop receiving emails from it, send an email to polymer-dev...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages