I built an application using Polymer and its working as intended. I'd like to style it with a totally custom look and feel.
Is it possible to disable default styling of Polymer elements via a flag or some roundabout way, or will I have to manually override everything I want to change?
To override an element's styles from the outside, you can use ::shadow and /deep/:
http://www.polymer-project.org/articles/styling-elements.html#style-fromoutside
Those pierce through the Shadow DOM boundaries and allow you to target nodes internal to the element. Unfortunately, this means you need to explicitly write rules that target these nodes. This is sort of the deal with components...an author defines the look and feel, but you're welcome to override it as consumer/developer.
It's also worth noting that the visual elements use the non-visual core-*/polymer-* elements to get their job done. If you need a completely different UI, I'd create an element that reuses those core elements.
Related
I am a web developer and recently started working with Ionic 4 which is based on Web Component. I was trying to edit the CSS of the components but I was not able to edit the same and later on figured out that it was because of Web-Components which do have #shadow-root.
My Question is, Is there any way to edit the CSS and JS of a Web Component.
If not, why is it there?
The drawbacks of the same according to me are:
- Not able to apply the custom CSS into the child components of the Component.
- Plugins like Stylus will be useless as the CSS won't be applied and we won't be able to get the dark mode.
The Component Author decides the level of styling that can be applied.
no #shadow-root
All global CSS applied
all children are part of the main document DOM
ShadowDOM created with this.attachShadow({mode:"open"})
No global CSS applied
CSS Properties (if used by the Component author) are applied
You can access the shadowRoot and overwrite everything inside
(this is like buying an IKEA table and putting a saw in it)
shadowDOM created with this.attachShadow({mode:"closed"})
No global CSS applied
CSS Properties (if used by the Component author) are applied
You can not access the shadowRoot
Documentation
source: https://developers.google.com/web/fundamentals/web-components/shadowdom
TL;DR
Shadow DOM removes the brittleness of building web apps. The
brittleness comes from the global nature of HTML, CSS, and JS. Over
the years we've invented an exorbitant number of tools to circumvent
the issues. For example, when you use a new HTML id/class, there's no
telling if it will conflict with an existing name used by the page.
Subtle bugs creep up, CSS specificity becomes a huge issue (!important
all the things!), style selectors grow out of control, and performance
can suffer. The list goes on.
Shadow DOM fixes CSS and DOM. It introduces scoped styles to the web
platform. Without tools or naming conventions, you can bundle CSS with
markup, hide implementation details,
and author self-contained components in vanilla JavaScript.
Read all about styling components:
https://developers.google.com/web/fundamentals/web-components/shadowdom#styling
(not a standard yet) https://developers.google.com/web/updates/2019/02/constructable-stylesheets
I'm learning web components. When designing a custom element, I have to decide what is going to be hidden in the the shadow DOM. The remainder will then be exposed in the light DOM.
As far as I understand, the APIs allow two extreme use cases with different tradeoffs:
hide almost nothing in the shadow DOM, most of the element's content is in the light DOM and in the element's attributes:
this allows an HTML author to provide anything for the component to display without writing JS;
this is close to the status quo regarding searchability and accessibility
but there is little reward for the work involved; I add complexity with components but they don't encapsulate anything (everything is exposed).
hide almost everything in the shadow DOM, the element's innerHTML is empty:
this requires the element to be instantiated from JS;
this locks usage a lot more because instantiating from JS is more strict (type-wise) than using HTML slots and attributes;
this may be less searchable and accessible (I'm not sure whether this is the case);
I currently lean toward hiding everything in the shadow DOM for the following reasons:
I intend to instantiate everything from JS. I'm not going to author pages in HTML manually. It would be more work to code both an HTML API and a JS API.
It's less cognitive work to hide everything. I don't need to find a right balance about which information is visible in the light DOM.
It's closer to most JS frameworks I'm familiar with.
Am I missing something?
Edit
Thank you, I am answered that it depends on the use case which partially answers my question. But I'm still missing an answer regarding the case I'm in: I'd rather not support slots for some of my components.
I'll add an example for each extreme of the spectrum:
Light-DOM-heavy component: the component user has to insert elements into slots
<template id=light-email-view>
<div>
<div><slot name=from></slot></div>
<ul><slot name=to></slot></ul>
<h1><slot name=subject></slot></h1>
<div><slot name=content></slot></div>
<ul><slot name=attachements></slot></ul>
<div class=zero-attachment-fallback>no attachments</div>
</div>
</template>
Shadow-DOM-heavy component: the component user has to use the JS API
<template id=shadow-email-view>
<div></div>
</template>
<script>
...
let view = document.createElement('shadow-email-view');
// this method renders the email in the shadow DOM entirely
view.renderFromOject(email);
container.appendChild(view);
</script>
In the first example, the component author has more work to do since they need to "parse" the DOM: they have to count attachments to toggle the fallback; basically, any transformation of input that isn't the browser copying an element from the light DOM into the matching shadow DOM slot. Then they need to listen for attribute changes and whatnot. The component user also has more work, they have to insert the right elements into the right slots, some of them non-trivial (the email content may have to be linkified).
In the second example, the component author doesn't need to implement support for instantiating from HTML with slots. But the component user has to instantiate from JS. All the rendering is done in the .renderFromObject method by the component author. Some additional methods provide hooks to update the view if needed.
One may advocate for a middle ground by having the component offer both slots and JS helpers to fill those. But I don't see the point if the component isn't to be used by HTML authors and that's still more work.
So, is putting everything with the shadow DOM viable or should I provide slots because not doing so isn't standard compliant and my code is going to break on some user agent expecting them (ignoring older UAs that are not at all aware of custom elements)?
#supersharp has nailed it.
One thing I see with Web Components is that people tend to have their component do way too much instead of breaking into smaller components.
Let's consider some native elements:
<form> there is no shadow DOM and the only thing it does is read values out of its children form elements to be able to do an HTTP GET, POST, etc.
<video> 100% shadowDOM and the only thing it uses the app supplied children for is to define what video will be playing. The user can not adjust any CSS for the shadow children of the <video> tag. Nor should they be allowed to. The only thing the <video> tag allows is the ability to hide or show those shadow children. The <audio> tag does the same thing.
<h1> to <h6> No shadow. All this does is set a default font-size and display the children.
The <img> tag uses shadow children to display the image and the Alt-Text.
Like #supersharp has said the use of shadowDOM is based on the element. I would go further to say that shadowDOM should be a well thought out choice. I will add that you need to remember that these are supposed to be components and not apps.
Yes, you can encapsulate your entire app into one component, but the browsers didn't attempt to do that with Native components. The more specialized you can make your components to more reusable they become.
Avoid adding anything into your Web Components that is not vanilla JS, in other words, do not add any framework code into your components unless you never want to share them with someone that does not use that framework. The components I write are 100% Vanilla JS and no CSS frameworks. And they are used in Angular, React and vue with no changes to the code.
But chose the use of shadowDOM for each component written. And, if you must work in a browser that does not natively support Web Components that you may not want to use shadowDOM at all.
One last thing. If you write a component that does not use shadowDOM but it has CSS then you have to be careful where you place the CSS since your component might be placed into someone else's shadowDOM. If your CSS was placed in the <head> tag then it will fail inside the other shadowDOM. I use this code to prevent that problem:
function setCss(el, styleEl) {
let comp = (styleEl instanceof DocumentFragment ? styleEl.querySelector('style') : styleEl).getAttribute('component');
if (!comp) {
throw new Error('Your `<style>` tag must set the attribute `component` to the component name. (Like: `<style component="my-element">`)');
}
let doc = document.head; // If shadow DOM isn't supported place the CSS in `<head>`
// istanbul ignore else
if (el.getRootNode) {
doc = el.getRootNode();
// istanbul ignore else
if (doc === document) {
doc = document.head;
}
}
// istanbul ignore else
if (!doc.querySelector(`style[component="${comp}"]`)) {
doc.appendChild(styleEl.cloneNode(true));
}
}
export default setCss;
The choice is 100% dependent on the use case.
Also:
if you want the user to be able to format your custom element with global CSS style attributes, you may opt for the normal, light DOM.
you're right: in the Shadow DOM, "this may be less searchable": the document.querySelector() method won't inspect the Shadow DOM content.
as a consequence, some third-pary JS library may fail to integrate easily with Shadow DOM
if you intend to use a Custom Element polyfill for legacy browsers, you may avoid Shadow DOM because some of its features cannot be really polyfilled.
in many cases, the answer is to provide a mix of Light DOM and Shadow DOM. As suggested by #JaredSmith:
Shadow DOM for the Web Component author,
Light DOM for the Web Compoent user, intergrated in the Shadow DOM with <slot>.
As a conclusion, you should consider the context in which your Web Component will be used to decide whether Shadow DOM is required or not.
Answer to the Edit
Considering your use case, I would create a custom element and:
let the user populate the light DOM with atomic value(s): type element <div class="mail-to"> or custom sub-components <mail-to> as suggested by #Intervalia,
use a Shadow DOM to mask the light DOM,
use Javascript: this.querySelectorAll('.mail-to') or this.querySelectorAll('mail-to') instead of <slot> to extract data from the light DOM and copy (or move) them to the Shadow DOM.
This way users won't have to learn the <slot> working, and the developer will be able to format the web component rendering with more freedom.
<email-view>
<mail-to>guillaume#stackoverflow.com</mail-to>
<mail-to>joe#google.fr</mail-to>
<mail-from>supersharp#cyber-nation.fr</mail-from>
<mail-body>hello world!</mail-body>
<email-view>
Alright. Setting aside for a moment that I think this is a bad questionable idea, here's some code that should do what you want (I didn't run it, but it should work):
class FooElement extends HTMLElement {
constructor () {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(document.importNode(template.content, true));
}
_xformObject (object) {
// turn the obj into DOM nodes
}
renderFromObject (object) {
// you may need to do something fancier than appendChild,
// you can always query the shadowRoot and insert it at
// a specific point in shadow DOM
this.shadowRoot.appendChild(this._xformObject(object));
}
}
You'll have to register the custom element of course.
Now sometimes you really can't get away from doing something like this. But it should be the absolute last resort. See below:
Why I think this is a bad questionable idea, and how to make it better:
One of the main draws to web components is that it enables declarative HTML markup rather than procedural JS DOM manipulations. While providing an API like what you're talking about is certainly a big step up from e.g. creating a table by creating a table node, creating a row node, creating some tds, appending them to the row, then appending that to the table, I (and I think most) developers are of the idea that if your custom element requires direct JavaScript manipulation by the user, then it's not really an HTML element: it's a JavaScript interface.
Let me qualify that a little. When I say "requires" JavaScript I mean there's no way to drop it on the page with some appropriate attributes and wind up with the thing you want. When I say "direct" I mean by calling methods of the object representation of the element directly rather than say toggling an element attribute. To put my point in code:
// good
myCustomElement.setAttribute("toggled-on", true);
// this isn't *bad*, but don't *force* people to do this
myCustomElement.toggleState();
You may want to still provide the second as part of your public API as a convenience to your users, but requiring it seems beyond the pale. Now one issue is that you obviously can't easily pass complex data structures to an HTML attribute (Polymer has helpers for this if you're using Polymer).
But if that's the case, rather than have that be part of the element API, I'd provide a standalone function that returns the appropriate DOM structure rather than baking that in to an element. You could even make it a class method of your custom element class if that's how you roll.
Consider the case where you have a List element that renders an arbitrary number of Item elements. I think it's great to provide a convenience method that takes an array and updates the (light) DOM. But users should be able to append them directly as well.
Your use case may require hacking around the problem. Sometimes you really do legit need to use an antipattern. But consider carefully whether that's the case in your element.
This is purely dependent on your case. But as a general rule, if you find yourself buried in a hell of nested shadow roots, then you may consider going easy on using shadow doms.
Like the follow example illustrates:
<my-outer-element>
shadowRoot
<slot1> ---Reveal dom
<my-inner-element>
shadowRoot
....
I'm seeing weird behaviour when using containerless functionality for aurelia components. We're creating custom elements for in SVG container, which required us to use containerless tag to strip the custom element tags before adding it to the DOM, as only SVGElements tags are allowed within a SVG container.
We're using Aurelia release version 1.0.0 and build our SPA with webpack.
Here you can find a gistrun example which displays the 2 implementation of containerless usage.
https://gist.run/?id=58ba6282ad54c1263eec3a141fe42183
In this example i've created 2 viewmodels and bind this to our custom elements. Using as-element="compose" to tell aurelia that i've created the viewmodel and that Aurelia shouldnt create a VM. The difference between these to custom elements are the containerless tag:
CustomElement doesnt have the #containerless tag in the Viewmodel but have 'containerless' in the HTML view.
withattr component does'nt have 'containerless' in the HTML view, but it does have the #containerless tag in the Viewmodel, as described in the Aurelia HUB.
I expect in both situations that I would see a blue rectangle. customelement tags are stripped by Aurelia because of the containerless tag. however the #containerless tag doesnt seems to work, as in implementation 2.
Question:
Any clue why these implementation have different outputs?
Which is the correct one? I would expect 2, as stated in the Aurelia Docs that the #containerless tag should be placed on the viewmodel.
Any help would be appreciated :)
The #containerless decorator works directly on the element you place it on.
What's happening is that the decorator is applied to your withattr element, but as-element="compose" turns it into a compose element under the hood. This compose element then does not have the #containerless tag applied to it.
Likewise, with your customelement you are in fact not applying #containerless to customelement, but to the compose that it is turned into.
Remove the as-element="compose" part and simply declare your <withattr/> element naked in the markup, and containerless will work because the actual element will still be withattr.
Note that it's not recommended to use #containerless with as-element unless there is no other way to accomplish something, as is the case with using custom elements inside table elements.
Why not simply have a compose inside your custom element, and bind the path to the view through a bindable property on the custom element?
EDIT
Sorry, I kind of overlooked the fact that you wanted to specify your own ViewModel instance.
This requirement limits you to using the compose element because that's the only way Aurelia supports providing your own ViewModel instance.
It's also certain that you need #containerless. And you need that #containerless to be on the compose element.
Conclusion, your first solution seems perfectly fine from a technical perspective.
As a matter of personal preference, I would do this:
<compose containerless view.bind="'./customelement.html'" view-model.bind="customElementViewModel"/>
Instead of this:
<customelement containerless as-element="compose" view-model.bind="customElementViewModel"/>
To be a little more flexible with dynamic views, make it clearer that we're using compose, and not having to <require> the view. But that really boils down to preference and other requirements.
We are developing a bookmarklet, and when the bookmarklet loads on different websites, eg: cnn.com, bbc.co.uk, yahoo.com it displays in various styles and we have struggle to reset these styles.
The bookmarklet content is in the current page DOM and not in an iframe (because we need cookies and access to DOM).
We tried using CSS reset, but that resets only some basic stuff, like margins. And pages where for example there is a custom form, or rounded table rectangles it inherits and it should not.
Is there a way that we can completely isolate this DIV area in the current page to look only as we want?
How about trying to replace your div with some obscure element that is unlikely to be on their pages.
eg. b or em or i or maybe even one of the newer html5 elements if you're not fussed about browser support.
And styling them to display: block to function like a div which is a block element.
Your resultant HTML is not going to be valid or pretty, but it's a bookmark so, meh.
Short of that, a really good reset is what you'll need.
Or you'll just have to live with slight differences in your styling.
We end up using https://github.com/premasagar/cleanslate
CleanSlate is an extreme CSS reset stylesheet. It is used to reset the styling of an HTML element and all its children, back to default CSS values. It is composed exclusively of !important rules, which override all other types of rules.
Well, you can use either the unique id and adding !important to each property afterwards - for resetting the generated element in the DOM - or you could use the new scoped attribute in "HTML5".
But that may result in problems with all explicit "inherit" valued styles on that element or the parents. For example, relative font sizes will result in problems, too.
Therefore is the experimental scoped attribute on the style section, but last time I tried it only Chrome/Chromium supported it, Firefox may have landed support for it recently, too - because there was a huge discussion on the mailing list.
http://updates.html5rocks.com/2012/03/A-New-Experimental-Feature-style-scoped
Edit:
Another solution could be to use a custom element that is not in the DOM by default.
Something like document.createElement("thisisfrommyapp");
You can style them like other elements, but have to apply display:block or whatever behaviour want for them.
Also, IE allows using them, but you actually need to insert them into Tridents' parser before. If you want to use them in HTML, you have to do the createElement() before the DOM is parsed (so it's most likely inside the head of your document).
<html>
<head><script>document.createElement('customelement');</script></head>
<body><customelement>is stylable in IE8, too</customelement></body>
</html>
You have to do the createElement stuff for Trident only, because otherwise you will result in weird parsing behaviours due to their display:inline-block defaulted model :)
If you are using XHTML on the website for whatever stupid reasons (there are no valid reasons to use XHTML over HTML, due to parsers stripping out XML tags anyways), you should use a custom namespace for it.
~Cheers
Follow this 2 steps to sandbox a container.
<div class="namespace-box">
<h1 class="namespace-title">Title</h1>
<p class="namespace-text">Text</p>
</div>
Unset all properties of the container's namespace, all: unset; is just a placeholder:
[class*="namespace-"],
[class*="namespace-"]:after,
[class*="namespace-"]:before,
[class*="namespace-"]:hover:after,
[class*="namespace-"]:hover:before {
all: unset;
// properties to be unset
}
Use a Grunt or Gulp task to add the attribute selector to your original CSS. This increases the cascade and prevents overrides by the unset hack:
[class*="namespace-"].namespace-box,
[class*="namespace-"].namespace-title,
[class*="namespace-"].namespace-text {
// original properties
}
You can automate the specification with the postcss-increase-specificity task.
Enjoy your bulletproofed container.
As I've gotten deeper into using jQuery with various sites I've worked on, I've found that I can get lost on whether a class attribute value is appended to an element in the DOM in order to attach an actual CSS style, or to bind an event to it. As such, I've started leaning towards using the rel attribute on anchor tags to denote if something is going to be bound to an event, keeping the class attribute specifically for stylization. (I've not delved into this deep enough to determine if there are any drawbacks or fundamental flaws with this approach, however, and am open to comments & criticisms on it.)
It got me to thinking that others must have similar things they do to help keep their code organized, and I'm interested in learning about some new ideas that might be floating around out there.
Usually this is not much of an issue for me, even in medium sized projects.
I usually assign classes for styling, and I often end up using same selectors in JS code.
Semantically speaking, the rel attribute is not an appropriate way to store data. As it should point out the relation of a link to the target.
HTML5 makes things more flexible with data- custom attributes.
You use the class attribute when you have multiple HTML elements that have shared presentation or shared behavior.
So if you have several buttons for which you want to use the same event handler, then you give those buttons a class and then use JavaScript to select those elements (by class) in order to set the handler on them (you use a JavaScript library which has a selector engine). For example, in jQuery:
$(".addButton").click(function() {
// the event handler
});
Classes are used both for CSS styling and JavaScript manipulation.