I have built a small library of several HTML web components for internal company use. Some components are mutually dependent on each other, so I also import them mutually. Until recently, I had no serious issues with this approach, but I am now encountering an error message when loading a HTML page that uses such mutually dependent components.
I have isolated the issue in a small example. Please review the following three files.
test-container.js
import { TestItem } from "./test-item";
export class TestContainer extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" }).innerHTML = `
<style>
* {
position: relative;
box-sizing: border-box;
}
:host {
contain: content;
display: block;
}
</style>
<div>
<slot></slot>
</div>
`;
}
connectedCallback() {
if (!this.isConnected) {
return;
}
for (const node of this.childNodes) {
if (node instanceof TestItem) {
//...
}
}
}
}
customElements.define("test-container", TestContainer);
test-item.js
import { TestContainer } from "./test-container";
export class TestItem extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" }).innerHTML = `
<style>
* {
position: relative;
box-sizing: border-box;
}
:host {
contain: content;
display: block;
}
</style>
<div>
<slot></slot>
</div>
`;
}
}
customElements.define("test-item", TestItem);
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test</title>
<script type="module" src="/test-container"></script>
<script type="module" src="/test-item"></script>
<style>
test-container {
width: 600px;
height: 400px;
background: lightblue;
border: 1px solid;
}
test-item {
width: 200px;
height: 200px;
background: lightgreen;
border: 1px solid;
}
</style>
</head>
<body>
<test-container>
<test-item></test-item>
</test-container>
</body>
</html>
This code seems to work fine.
However, if I switch the two <script> tags in the index.html file, the developer tools console shows the following error:
Uncaught ReferenceError: Cannot access 'TestItem' before initialization
at HTMLElement.connectedCallback (test-container:30)
at test-container:37
Since I import several modules in many of my components, I want to sort them alphabetically (for clarity). In my test example it's fine, but in my actual code it isn't...
So basically I want my modules to be completely independent of the order in which they will be imported by other modules. Is there any way to achieve that?
All suggestions are very welcome. However, I am not allowed to install and use any external/3rd party packages. Even the use of jQuery is not allowed. So a solution should consist of only plain vanilla JS, plain CSS, and plain HTML5, and it should at least work correctly in the latest Google Chrome and Mozilla Firefox web browsers.
When you can't control the order in which Elements are loaded,
you have to handle the dependency in your Element
Use: https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined
whenDefined returns a Promise!
So your <test-container> code needs something like:
customElements.whenDefined('test-item')
.then( () => {
//execute when already exist or became available
});
https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined
has a more detailed example waiting for all undefined elements in a page
Dependencies
An Event driven approach might be better to get rid of dependencies.
Make <test-item> dispatch Event X in the connectedCallback
<test-container> listens for Event X and does something with the item
You can then add <another-item> to the mix without having to change <test-container>
Maybe the default slotchange Event can be of help:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/slotchange_event
.
Success met welke aanpak je ook kiest
it may help
<!-- This script will execute after… -->
<script type="module" src="1.mjs"></script>
<!-- …this script… -->
<script src="2.js"></script>
<!-- …but before this script. -->
<script defer src="3.js"></script>
The order should be 2.js, 1.mjs, 3.js.
The way scripts block the HTML parser during fetching is baaaad.
With regular scripts you can use defer to prevent blocking, which also delays script execution until the document has finished parsing, and maintains execution order with other deferred scripts.
Module scripts behave like defer by default – there's no way to make a module script block the HTML parser while it fetches.
Module scripts use the same execution queue as regular scripts using defer.
Source
Related
i'm trying to implement google sign in functionality in my website using handlebars.
i want to include my script in a file and load it when the https://apis.google.com/js/api:client.js? loads because it creates an object gapi which i use in my js.
The problem is that handlebars doesn't give any help to load js files.
i tried using helpers but the problem is that gapi gets undefined in the registered helper as gapi is loaded when client library loads.
i tried doing
<script src="https://apis.google.com/js/api:client.js?onload=after_load"></script>
<script>
function after_load(){
{{helper_name gapi}}
}
</script>
but still the error persists, is there any way to load a js file in hbs? or i just have to put my code in the script tag itself?
To my mind you're confusing handlebar with something else.
Instead of doing such things try to do something like this :
load normally your api in your html.
once loaded you can call your handlebar part (for example jquery has a nice on ready function).
after the handlebar result has been processed inject it in your html.
if you need to launch another script then do it afterwards
Here is one example:
$(document).ready(function () {
var context = { "form" : "<div class='input-container'><div class='label'>User :</div><div class='input'><input type='text' id='username' name='username'></div></div><div class='input-container'><div class='label'>Password :</div><div class='input'><input type='password' id='password' name='password'></div></div>" };
var source = $("#sourceTemplate").html();
var template = Handlebars.compile(source);
var html = template(context);
$("#resultPlaceholder").html(html);
alert("Load is done place your additional scripts calls here");
});
.input-container { display: inline-block; }
.label { float: left; width: 100px;}
.input { float: left; width: 300px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script src="https://apis.google.com/js/api:client.js?onload=after_load"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script id="sourceTemplate" type="text/x-handlebars-template">
<div class="container">
{{{form}}}
</div>
</script>
<br/>
<div id="resultPlaceholder">
</div>
I am trying to get syntax highlighting working but when changing the mode it doesn't work
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/monokai.js"></script>
<script="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/mode-javascript.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js"></script>
<script src="scripts.js"></script>
scripts.js
var html = ace.edit("htmlEditor");
var css = ace.edit("cssEditor");
var js = ace.edit("jsEditor");
html.setTheme("ace/theme/monokai");
css.setTheme("ace/theme/monokai");
js.setTheme("ace/theme/monokai");
var JavaScriptMode = ace.require("ace/mode/javascript").Mode;
js.session.setMode(new JavaScriptMode());
You have a typo in your html <script=" also scripts for theme and modes must be inserted after ace.js
It is better to pass names to ace and let it load modes and themes by itself
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js"></script>
<div id="htmlEditor"><html></div>
<div id="cssEditor">.css { color: red }</div>
<div id="jsEditor">var js</div>
<style>
html, body {
height: 100%;
}
#htmlEditor, #cssEditor, #jsEditor {
height:30%
}
</style>
<script>
var html = ace.edit("htmlEditor");
var css = ace.edit("cssEditor");
var js = ace.edit("jsEditor");
html.setTheme("ace/theme/monokai");
css.setTheme("ace/theme/monokai");
js.setTheme("ace/theme/monokai");
html.session.setMode("ace/mode/html");
css.session.setMode("ace/mode/css");
js.session.setMode("ace/mode/javascript");
</script>
what let me to this question is that I got 404 error when I call setMode
I traversed the code to see whats going on, Ace tries to identify where Ace library and its files are located, and it does that by looking at the script tags within the page, so if it had a lock finding the lib location, it set it as a basePath for it,
but what if Ace is bundled within a one minified main js file main.js it will fail and return 404
to solve this
if (window.ace) {
configureAce();
//....
}
function configureAce() {
// this is important in case of bundling , to have Ace knows where
// its files are located (like themes, workers and modes)
// and get them from that path
window.ace.config.set("basePath", "/resources/js/lib/ace");
}
I have a custom Polymer 2 video-player element and I want the ability to pass in a property to each element instance in order to define which stylesheet to apply to it (different theme names are released each year and each set of styles currently exist on the server in their own file which has a predictable naming format). I saw some examples with inline tags, but that seems to only be an acceptable solution for smaller styles (otherwise it gets out of control for me to jam all my styles in there, which there are a lot of). How can I do the theme-ing properly?
Here's what I got so far in two separate HTML files:
Demo File (demo-page.html)
<link rel="import" href="../my-video-player.html">
<my-video-player data="{...}" theme="2016"></my-video-player>
<my-video-player data="{...}" theme="2017"></my-video-player>
Component File (my-video-player.html)
<dom-module id="my-video-player">
<template>
<link type="css" rel="import" href="css/[[theme]]/styles.css">
</template>
<script>
class MyVideoPlayer extends Polymer.Element {
...
}
</script>
</dom-module>
Note: I tried putting the above link tag one level outside of the template tag as well, but that didn't work.
Right now what happens is only one of the two theme stylesheets get applied to both of my components. What I need instead is for the first component to get the 2016.css stylesheet applied to it, while the second gets the 2017.css stylesheet.
To add external stylesheets, you need to create a custom component that contains all the styles you want. Then, you can link the "style" component into the component(s) using the styles like this:
<link rel="import" href="/shared-styles.html">
<dom-module id="sus-app">
<template>
<style include="shared-styles"></style>
<style>
neon-animated-pages {
height: 500px;
}
neon-animatable {
padding-top: 2em;
padding-left: 10em;
padding-right: 10em;
}
Notice that you can still include styles unique to the component that she shared styles are being used with.
My idea is to have different files (either .css or .html) containing all the rules for a specific theme and load theme dynamically changing the look&feel of the Polymer application at runtime.
I have found pretty useful this method described here by Polymer itself with using custom style at document level (using .html files)
Frequently you want to define custom property values at the document level, to set a theme for an entire application, for example. Because custom properties aren't built into most browsers yet, you need to use a special custom-style tag to define custom properties outside of a Polymer element. Try adding the following code inside the tag of your index.html file
Basically we define an my-theme.html, that defines variables used in the application style, that looks like this:
<style is="custom-style">
/* Define a document-wide default—will not override a :host rule in icon-toggle-demo */
:root {
--icon-toggle-outline-color: red;
}
/* Override the value specified in icon-toggle-demo */
icon-toggle-demo {
--icon-toggle-pressed-color: blue;
}
/* This rule does not work! */
body {
--icon-toggle-color: purple;
}
</style>
And I import it in my index.html file.
And then I found on stackoverflow this method to change the style dynamically.
HTML
<link type="text/css" rel="stylesheet" media="all" href="../green.css" id="theme_css" />
JS
document.getElementById('buttonID').onclick = function () {
document.getElementById('theme_css').href = '../red.css';
};
The problem here is that the file is a rel: stylesheet and changing its href causes the browser to reload the file and apply the changes, wherease in my case changin the href of the html import doesn't leed to the same effect.
Is there a way to get this to work as I want? Is there a better solution to achieve my goal?
Definitely, you have to use custom CSS properties and mixins. If you style your website correctly it will be very easy to swap to different theme.
You can change Custom CSS property value by doing:
this.customStyle['--my-toolbar-color'] = 'blue';
Every css rule that is using var(--my-toolbal-color) will change to color blue, once you call one ore function:
this.updateStyles();
which tells Polymer, hey i just updated styles can you re-render css rules..
Hope this helps and it's the thing you were looking for. Because what you described was too much complicated i think.
Not really happy with it but that work :
<link rel="import" href="my-css.html">
<dom-module id="my-app">
<template>
<style include="my-css"></style>
<button class="btn-primary-dark">Dark button</button>
<button class="btn-primary-light">Light button</button>
<button class="btn-primary-default">Default button</button>
<br><br>
<button on-click="setTheme1">Set theme 1</button>
<button on-click="setTheme2">Set theme 2</button>
</template>
<script>
class MyApp extends Polymer.Element {
static get is() { return 'my-app'; }
// Dynamicaly change vars
setTheme1() {
Polymer.updateStyles({
'--dark-primary-color' : '#689F38',
'--default-primary-color' : '#8BC34A',
'--light-primary-color' : '#DCEDC8',
'--text-primary-color' : '#212121'
});
}
setTheme2() {
Polymer.updateStyles({
'--dark-primary-color' : '#1f8f37',
'--default-primary-color' : '#818bbf',
'--light-primary-color' : '#f0e82f',
'--text-primary-color' : '#333333'
});
}
}
window.customElements.define(MyApp.is, MyApp);
</script>
</dom-module>
my-css.html
<dom-module id="my-css">
<template>
<style>
.btn-primary-dark {
background-color: var(--dark-primary-color);
color: var(--secondary-text-color);
}
.btn-primary-light {
background-color: var(--light-primary-color);
color: var(--secondary-text-color);
}
.btn-primary-default {
background-color: var(--default-primary-color);
color: var(--secondary-text-color);
}
</style>
</template>
</dom-module>
At the moment im building a application which requires a react-native webview because I wanted to build a offline charting application. There are at the moment of writing no librarys used for react-native to create a chart.
In order to create the chart I started to make a WebView because there are already librarys made based on D3.js this webview got a template HTML inside.
So my train of thoughts: Create a webview add all the library's needed for the Graph's and voila a nice graph in React-native.
The idea behind it works, but the Graph is a big blob on a component and trying to get it in another file. So I can include it with 1 line.
//import librarys for the html 'string'.
import d3 from './librarys/d3'
import c3 from './librarys/c3'
import styleSheet from './librarys/c3css'
These library's are concatenated to the Webview.
like this way:
const HTML = `<html>
<head>
<title>Hello Static World</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=320, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
h1 {
padding: 45px;
margin: 0;
text-align: center;
color: #33f;
}
`+ { styleSheet }+ `
</style>`;
`
Result: all html but this part: + { styleSheet }+ `` still remains a [Object object] instead of the resulting string.
Is there anyway to include it properly? Instead of receiving Object object. Parsemethod?
#Dan_abramov: "You can't "concatenate" imports. You need a bundler (like Browserify) that would bundle your JS. Then include it in HTML."
Source: https://twitter.com/dan_abramov/status/737283387128459264