2

I have a custom element that has two categories of styles, static and dynamic. The static styles exist in the module <dom-module><style>...</style></dom-module> but the dynamic styles need to live in their own stylesheets and then get loaded if necessary.

The custom element will have a few styles with the syntax color: var(--dynamic-element-color) then after some ajax stuff determines a client I will grab that client's stylesheet and include it in some fashion to update the mixin styles...maybe via @import url(client-1.html) combined with Polymer.updateStyles() if necessary.

I attempted this via 2 methods:

1)Within the custom element's method - some form of this worked in Chrome but no where else:

ready: function() {
    this.determineClientStyle('test');
},
determineClientStyle: function(client) {
    var link = document.createElement('link');
    link.rel = 'import';
    link.href = '/elements/'+client+'/linus.html';
    var beforeNode = Polymer.dom(this.root).childNodes[0];
    Polymer.dom(this.root).insertBefore(link, beforeNode);
}

2)Within index.html - worked in Chrome and FF but not Safari or my phone's "internet" browser:

window.addEventListener('WebComponentsReady', function() {
    function applyClientTheme(client) {
        var link = document.createElement('link');
        link.rel = 'import';
        link.href = client+'.html';
        Polymer.dom(document.head).appendChild(link);
    }
    applyClientTheme('theme');
});

Here is an example project on github.

My real project has numerous custom elements all of which will have styles with var(--some-style) and I hope to load a single import that fulfills them all on a per client basis.

I have yet to be successful with what @Abhinav mentions below...either that or I am interpreting it incorrectly or just doing it wrong.

What is the best way to go about doing this?

tehaaron
  • 2,250
  • 10
  • 31
  • 54

1 Answers1

0

I see two problems here :

1. Style leaking on the page

You don't want your style to leak on to the page but most probably you are not using shadow dom, because starting from Polymer 1.0 shady dom is the default which doesn't provide style encapsulation.
You can force polymer to use shadow dom where it is supported by tweaking global polymer settings.

<script>
    window.Polymer = window.Polymer || {};
    window.Polymer.dom = 'shadow';
</script>

2. Using external stylesheet inside shadow dom

You can use css @imports inside shadow dom to load external stylesheet. When components are available on the page, along with client identifier, you can inject these style tags inside your local dom.
If you are using html imports to bring in your components on page, you can include a small script in your html which will contain code to inject style tags inside templates. something like this perhaps:

//If you are using html imports to bring in your components on page.
//doc will contain html which is being imported.
var doc = document.currentScript.ownerDocument;

// Fetch template from doc
var myTemplate = doc.querySelector('template#myTemplate');
var cssURL = '/elements/'+client+'/dynamic_style_1.css';
// construct a style tag here with css @imports which contain above css url.
// Since you have access to template in myTemplate
// inject this style tag inside it.
// Since it is a css @import, Remember to inject it at top(using insertBefore)

I have written few descriptive answers here and here on the topic.
Hope it helps.


Edit

document.currentScript.ownerDocument returns document of which script is currently being executed.

Now, there are 2 places where your component might exist.

  1. Host(Current) document
    In this case, document.currentScript.ownerDocument will return the current document from which you can fetch the template. But you don't really need this here because you can rather do a document.querySelector directly and then append a style tag to it or do whatever you want.

  2. Imported document
    Now this is a bummer since your template isn't present on the current page so document.querySelector can't fetch you that.
    So, there are two ways you can append a style tag to it now, which will differ based on where you want to do this operation.

a. Ready method: you want to do in ready method when your client identifier is present.

Very well, you don't need document.currentScript.ownerDocument here either because you can just query for your link tag and fetch contents from it using import property.
Something like this:

<link id="myImport" rel="import" href="my-components.html">

//This is what goes inside ready method
var linkTag = document.querySelector('#myImport');
var importContent = linkTag.import;
var myTemplate = importContents.querySelector('#myTemplate');
//Yayy!! You have your template. Now go ahead and inject a style tag into it.

b. Imported HTML: Nope! I want to do it even before that. Actually I want to do it when my contents are being imported on the page.

Okies. Only option you have here is... wait for it... Yup, you guessed it. document.currentScript.ownerDocument.
Quoting this nice tutorial on HTML imports.

Imports are not in the main document. They're satellite to it. However, your import can still act on the main page even though the main document reigns supreme. An import can access its own DOM and/or the DOM of the page that's importing it.

The script inside the import references the imported document (document.currentScript.ownerDocument)

I hope your use case lies in one of the three possibilities mentioned above.
At the end, your shadow root should contain a style tag with a css @import which should point to correct location of your css file.
If that is true, style will load on the page and will be applied only inside your shadow root.

Community
  • 1
  • 1
Abhinav
  • 1,346
  • 12
  • 25
  • I do not follow your descriptive answers. Can you provide some concrete code on how you would inject addition stylesheets into your custom elements? – tehaaron Oct 18 '15 at 02:08
  • I have updated my answer with code snippet. [Here](http://stackoverflow.com/a/32956340/4788782) is one more answer which provides more details if needed. – Abhinav Oct 18 '15 at 07:58
  • Can you look at my updated code snippet in the question and tell me if this is the right place and way to go about it? This ready method belongs to the custom element that will consume the style. Additionally, I can see that `style` is getting added as a child node of `myTemplate` but I am not positive that, that is what I want. – tehaaron Oct 19 '15 at 21:13
  • I also noticed that document.currentScript.ownerDocument doesnt work if the custom element code is in a separate file and being imported. – tehaaron Oct 20 '15 at 20:14
  • Your last edit was something which I was suggesting. Did it work? I have updated my answer with your query regarding `document.currentScript.ownerDocument` – Abhinav Oct 20 '15 at 21:15
  • Both of the attempts I made above worked in Chrome but so far nothing is working in Safari on OSX or iOS. I will experiment with your latest suggestions tonight/tomorrow. I really appreciate your input! – tehaaron Oct 20 '15 at 22:36
  • One other question, do these solutions rely on setting `window.Polymer.dom = 'shadow'`? Because that is something I did not do as I opted to stick with the default shady dom behavior. – tehaaron Oct 20 '15 at 22:44
  • Oh yes. That is the crucial bit because otherwise you are using shady dom, which doesn't provide style encapsulation. In other words, your style will leak on to the page. – Abhinav Oct 21 '15 at 04:32
  • For fixing issues in browsers which don't support HTML imports(or, webcomponents in general), read [this](http://webcomponents.org/polyfills/html-imports/). To summarize, in browsers other than chrome you'll have to use `document._currentScript.ownerDocument` (note the underscore). – Abhinav Oct 21 '15 at 10:00