External stylesheets for shadow dom in web components - javascript

I'm learning web components with a shadow root and can't seem to find on google if loading external stylesheets is possible with out-of-the-box code? I am NOT using polymer or any other web component library (yet). Code below:
<script src="../../libs/jquery-2.1.1.min.js"></script>
<script>
var hollaProto = Object.create(HTMLElement.prototype);
hollaProto.createdCallback = function () {
var shadow = this.createShadowRoot();
var content = document.querySelector('link[rel=import]').import.querySelector("div");
$("button[data-command=holla]", content).on("click", function () { alert("Holla!"); });
shadow.appendChild(content);
};
var hollaWidget = document.registerElement("holla-back", {
prototype: hollaProto
});
</script>
<div class="holla-back">
<button data-command="holla">Holla!</button>
</div>
If I put my link tag up top, above the first script tag, I style the whole web age, but not the web component.
If I put it under div.holla-back it doesn't style anything.
How do you use external stylesheets with web components?

Link tags are inert in Shadow DOM according to the spec. However, you can use #import, though that has its own performance issues.
The way Polymer works around this is it looks at the link tags and uses xhr to load those styles and apply them.
edit:
The folks working on Shadow DOM are aware of this shortcoming and that it needs to be fixed. Hopefully in the future we can come up with a system that supports external stylesheets.

Shadow DOM doesn't react to link tags. Infact, Chrome 41 throws an error when you use link tags. We have worked around that limitation by inlining CSS classes at the build time using vulcanize. This turned out to be quite handy in separating your CSS and the component definition.

Related

Can I detect if a specific CSS custom variable exists in the loaded styles?

I am creating some custom web components. One of the components is a light/dark theme switcher. However, this relies on a specific stylesheet being loaded.
I would like to output a warning if the correct stylesheet has not been loaded.
But, users have several different ways of loading stylesheets (e.g. link tag, #import statement, etc) and so it is not guaranteed that they will have loaded the correct sheet.
The stylesheet that is needed has some pretty specific custom variables and named styles in it. So I would like to know if there is any way from JavaScript (inside my web component), to check whether one of those variables or style names exists in the stylesheets loaded to the current page.
Many thanks to RedRex for giving me the pointer. Custom variables are accessible but not from the document.style. You have to use getComputedStyle.
So, in my web component Class, I have a connectedCallback function that runs whenever an instance of the component is added to the page.
connectedCallback() {
// Is the correct stylesheet loaded?
if ( !getComputedStyle(this).getPropertyValue('--uib-css').includes('uib-brand') )
console.warn('[uib-theme-changer] WARNING: It appears that you are not using uibuilder\'s uib-brand.css stylesheet. This component may not work as expected.')
} // ---- end of connectedCallback ---- //
In the CSS file, I have
:root, :root.light {
color-scheme: light dark;
/* Create a checkable var - helps web components know if this stylesheet is loaded.
* NOTE: no space between : and text! */
--uib-css:uib-brand;
}
Perhaps worth noting that web components are designed to limit the interaction between the light-DOM CSS and the shadowDom CSS. However, custom variables are designed to flow into your component.
 
Footnote: For the curious, I am writing some custom web components that will play nice with Node-RED and node-red-contrib-uibuilder.

Conditionally Rendered Web Components and Stylesheet Links

So I'm working on a micro-frontend architecture POC right now, using Web Components to wrap around code from any other framework out there. The goal is to have pieces of the UI be individually deployable to different hosts and simply pulled into the "parent" app (ie, the one that the user navigates to).
I've got most of my architecture working, but right now I'm trying to integrate the Shadow DOM into my work. My current design is to load both the JS and CSS through global static link/script tags, as shown below. Without the Shadow DOM, this works perfectly.
<html>
<head>
<link rel="stylesheet" href="http://micro-fe.com/file.css" />
</head>
<body>
<web-component></web-component>
<script src="http://micro-fe.com/file.js"></script>
</body>
</html>
Once I mount the content inside my Web Component using the Shadow DOM, however, this breaks down. The stylesheet I am loading in the page header is no longer able to touch the content within the Web Component. That is my ultimate goal for using the Shadow DOM, but that means I need a different way of loading my CSS. The goal is to load it from an external stylesheet like it is now, and not have it inlined in a tag. Something like this:
// Code is inside Web Component. "this" is HTMLElement
const shadowRoot = this.attachShadow({ mode: 'open' });
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'http://micro-fe.com/file.css';
shadowRoot.appendChild(link);
I haven't tested that yet, but I've read that tags are supposed to work from within the Shadow DOM. This, in theory, will get me my scoped styles in the Web Component.
My final challenge, and the reason I am posting this question, has to do with conditional rendering. My won't always be on the page. Other logic around it will determine when I want that content rendered. So my concern is that I don't want the CSS file to have to be re-loaded each time the component gets rendered.
I'm considering browser caching as a possible solution, but in general I'm wondering if there are any tips that can be provided. I know this is a bit complicated and non-standard, but my ultimate goal is to solve all of these problems and publish a library that does it all out of the box to make it easier for others.
I haven't tested that yet, but I've read that tags are supposed to work from within the Shadow DOM. This, in theory, will get me my scoped styles in the Web Component.
Yes, you can use <link> inside Shadow DOMs.
So my concern is that I don't want the CSS file to have to be re-loaded each time the component gets rendered. I'm considering browser caching as a possible solution, but in general I'm wondering if there are any tips that can be provided.
Yes, thanks to browser caching the CSS file will only be downloaded the first time it is needed.

Override or remove the appTheme CSS injected into a Buildfire App

So I faced the challenge of created a large custom app through the Buildfire platform. The client had a lot of custom element styles that were being overridden by the appTheme settings in the dashboard, and overriding these styles in traditional CSS fashion was growing to be a monumental task.
I wrote this small function to remove the custom styles injected into the application and so far all of the client's custom styling is showing correctly.
I figured I would share this with the community since this has been an issue without resolution for our team.
If you wish to disable the appTheme CSS in your plugin, you can simply use a meta tag in the widget's HTML, like so:
<meta name="buildfire" content="disableTheme">
This is covered the SDK wiki under the meta tag section.
This code is inside my index.html file for the AngularJS Application.
<body ng-controller="mainController" onload="removeCustomCSS()">
<script>
function removeCustomCSS() {
let links = document.querySelectorAll('[href*=appTheme]');
links[0].remove();
}
</script>

Building a Windows 8 Metro app with jQuery

I am just starting out with Windows 8 development using HTML/JS. I've spent the last few months immersed in jQuery development for apps targeting vehicle head-units and televisions.
Jumping into this, I thought the transition would be simple. I have the design and structure of my site all figured out for the most part and was hoping to follow some of the practices I had been using for my previous work.
That is, I want to essentially create a single page app. The main default.html file will house the top navigation/title and one other div. The other div will be used to load in all the other pages, all separate HTML files within the project.
All of the global functions and major functionality will reside in a javascript file, application.js. Then any page-specific javascript will reside at the top of each HTML file.
I'm quickly realizing that this is a problem. Using jQuery.load() to load in my pages causes security errors in my app.
JavaScript runtime error: Unable to add dynamic content. A script attempted to inject dynamic content, or elements previously modified dynamically, that might be unsafe. For example, using the innerHTML property to add script or malformed HTML will generate this exception. Use the toStaticHTML method to filter dynamic content, or explicitly create elements and attributes with a method such as createElement.
I was really hoping to avoid having to learn a bunch of Microsoft-specific stuff. I think it's great that they've provided a lot of tools and what not, and maybe I just haven't used them enough, but everything just feels too rigid for me and for what I'm trying to do or can already be accomplished with jQuery. I'm one who likes to know EXACTLY what is happening and have full control over it.
Also looking through the templates and sample projects, I really don't like all the repeated code. For instance, every single HTML file declaring all the same references. I want to write my references and sections like my title bar just once, and not have to copy/paste that code all over my project.
Is there a way to do things the way I was hoping, and create a single page app? Do they have their own substitute for jQuery's .load()?
Any help pointing me in the right direction would be much appreciated!
EDIT 8/14/2012:
I have tried using the fix from this question:
Using jQuery with Windows 8 Metro JavaScript App causes security error
This gets rid of the security warning and I can load in HTML using jQuery.load(). However, looking at DOM explorer, my HTML is being stripped of my scripts.
I have also tried wrapping my .load() call inside of MSApp.execUnsafeLocalFunction(), but yet again my file still gets stripped of all scripts. What gives?
I fixed by simply changing the line of jQuery that was causing the error.
jQuery-1.8.0, line 5566:
append: function () {
return this.domManip(arguments, true, function (elem) {
if (this.nodeType === 1 || this.nodeType === 11) {
self.appendChild(elem); // problem line
}
});
},
Changed to:
append: function () {
return this.domManip(arguments, true, function (elem) {
if (this.nodeType === 1 || this.nodeType === 11) {
var self = this;
MSApp.execUnsafeLocalFunction(function () {
self.appendChild(elem);
});
}
});
},
There is a "formal" way to do what you are seeking.
WinJS.Navigation is provided to support "single page apps". For example, the default.html would contain a markup that would represent where the dynamically loaded page content would go:
<div id="contenthost"
data-win-control="Application.PageControlNavigator"
data-win-options="{home: '/pages/home/home.html'}">
</div>
In the example above, the actual content page loaded is at /pages/home/home.html
In event handlers, you can simply do the following to load or navigate to another page:
WinJS.Navigation.nav("/pages/other/page.html");
True, it is not jQuery, but it works great :)
Depending on your app, if you are not intending to access any WinRT components, you can navigate your page to ms-appx-web which will change the security policy around the page, but you can't specify this from start up. You would have to do a navigate, and leverage that new securyt context.
The other option you have it to wrap the calls to JQuery with msWWA.execUnsafeLocalFunction function, which will enable all that unsafe code be pushed into the DOM

Remove all CSS rules

Is there a way to wipe out all CSS rules once style sheets have already been loaded?
I have to use a proprietary JavaScript library (ESRI's ArcGIS Server API) which is built on top of Dojo. I make extensive use of Dojo's widgets and would like to use Dojo's claro theme but unfortunately the ESRI library mungs up the CSS by loading in off-site CSS files (and probably CSS rules hard-coded in the JS). This ends up mangling the Claro theme.
So many Dojo widget CSS classes get rewritten and new rules get created that just wiping out all CSS and reloading the standard Dojo stylesheets seems easier/safer.
Something like the following would be nice:
* {none}
but I figure I'll have to end up using either Dojo or jQuery to accomplish this.
check out this bookmarklet called RefreshCSS by Paul Irish:
javascript:(function(){var h,a,f;a=document.getElementsByTagName('link');for(h=0;h<a.length;h++){f=a[h];if(f.rel.toLowerCase().match(/stylesheet/)&&f.href){var g=f.href.replace(/(&|%5C?)forceReload=\d+/,'');f.href=g+(g.match(/\?/)?'&':'?')+'forceReload='+(new Date().valueOf())}}})()
It refreshes the CSS stylesheets on a page, without refreshing the page itself.
I think you could do some alterations to it and get it to do what you want?
Another approach using jQuery that might work is to run this once the page has loaded:
$('head link, head style').remove();
Nope. Sadly, such a thing does not exist.
The answers to these related questions give pretty much the rundown on what is possible in terms of workarounds.
Is there a way to “sandbox” an html block away from its page's CSS without using iframes?
Reset CSS for a certain area of the page?
prevent meyer reset css to mess with dynamic content
How to reset css in middle of html document ?
There is always document.head.innerHTML = ""; But it really cleans house so you have to store away any scripts,metatags, titles or whatever you want to save and add them again.

Categories