Conditional resource loading
This time we want to share with you a piece of code you as a developer might find extremely useful. Especially if you are developing apps for hybrid mobile and web apps.
NOTE: This is part of our new incoming modernization GS-UI library.
Introduction
Have you ever come into a situation to dynamically load HTML page or template where different CSS and JavaScript resources need to be loaded depending on environment as is it browser, mobile, or hybrid app?
Developing a hybrid mobile app which loads a HTML template from a remote server, we want not only to reduce network requests by distributing base libraries, CSS and fonts with our app, but we also want to reduce startup time. Having resources within mobile app helps for sure. The question is how to dynamically load required resources and how to optimize libs to reduce them only to elements required by a specific environment.
There are multiple approaches, howewer, question is how to do it in a clean and neat way.
Let us show you what we mean...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Root Template</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no" name="viewport">
<!-- loader definition; must be loaded first -->
<script src="/modernization/src/head/base.js" type="text/javascript"></script>
<script src="/modernization/src/head/link.js" type="text/javascript"></script>
<script src="/modernization/src/head/script.js" type="text/javascript"></script>
<!-- styling -->
<gs-link env="browser" url="/modernization/assets/css/roboto.css"></gs-link>
<gs-link env="browser" url="/modernization/assets/css/font-awesome.min.css"></gs-link>
<gs-link env="browser" url="/modernization/assets/css/gs-bootstrap.css"></gs-link>
<gs-link env="browser" url="/modernization/assets/css/animate.css"></gs-link>
<!-- scripts -->
<gs-script env="browser" url="/modernization/assets/lib/jquery-3.3.1.slim.min.js"></gs-script>
<gs-script env="browser" url="/modernization/assets/lib/inputmask.min.js"></gs-script>
<gs-script env="browser" url="/modernization/assets/lib/bootstrap.bundle.min.js"></gs-script>
<!-- styling for mobile app -->
<gs-link env="asset" url="file:///android_asset/css/roboto.css"></gs-link>
<gs-link env="asset" url="file:///android_asset/css/font-awesome.min.css"></gs-link>
<gs-link env="asset" url="file:///android_asset/css/gs-bootstrap.css"></gs-link>
<gs-link env="asset" url="file:///android_asset/css/animate.css"></gs-link>
<!-- scripts for mobile app -->
<gs-script target="GreenScreens" url="file:///android_asset/lib/jquery-3.3.1.slim.min.js"></gs-script>
<gs-script env="asset" url="file:///android_asset/lib/inputmask.min.js"></gs-script>
<gs-script env="asset" url="file:///android_asset/lib/bootstrap.bundle.min.js"></gs-script>
<body>
</body>
</html>
As you can see, instead of using <link> and <script> tags to load CSS or JavaScript files, we created our own implementation <gs-link> and <gs-script> tags which will generate <link> and <script> tags based on conditions when to render.
Notice target="GreenScreens". This is our mobile application user agent name set inside WebView used for terminal modernization engine. In other words, we can not only define environment, but custom browser types and load resources accordingly.
NOTE: When WebView is used with URL "file:///android_assets", files will be loaded from application internal resource directory.
The source
Here is full source code of base.js, link.js and source.js files implementing our own <gs-* tags. One can test them with html code sample above.
Code is self-describing and very easy to understand, so we will leave it to you for inspection without further explanations.
/*
* Copyright (C) 2015, 2020 Green Screens Ltd.
*/
/**
* Base head element to load src based on environment
*/
class GSBase extends HTMLElement {
/**
* Get next unique element ID
*/
static nextID() {
GSBase._id++;
return `GSHID_${GSBase._id}`;
}
static hashCode(s) {
let h = 0,
l = s.length,
i = 0;
if (l > 0) {
while (i < l) {
h = (h << 5) - h + s.charCodeAt(i++) | 0;
}
}
return h;
}
/**
* Called every time node is added to parent node
*/
connectedCallback() {
let me = this;
if (me.url && me.isRenderable()) {
let hash = GSBase.hashCode(me.url);
let tmp = document.querySelector(`[data-hash="${hash}"]`);
if (tmp) {
console.log(`URL already exists: ${me.url}`);
me.remove();
return;
}
let el = me.render();
if (el) {
el.setAttribute('data-hash', hash);
if (me.isHead) {
document.head.appendChild(el);
} else {
//me.parentElement.appendChild(el);
me.insertAdjacentElement('afterend', el);
}
if (me.isAuto) {
me._id = GSBase.nextID();
el.id = me._id;
}
}
}
if (!me.isAuto) {
me.remove();
}
}
disconnectedCallback() {
let me = this;
if (me.isAuto) {
let el = document.querySelector(`#${me._id}`);
if (el) {
el.remove();
}
}
}
render() {
}
isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
isAsset() {
return this.isTarget('GreenScreens');
}
isTarget(value = '') {
if (value) {
let regex = new RegExp(`${value}`, 'i');
let sts = navigator.userAgent.match(regex);
if (sts == null) {
return false;
}
}
return true;
}
isRenderable() {
let me = this;
let isMobile = me.isMobile();
let isAsset = me.isAsset();
let target = me.target;
let env = me.env;
if (env === 'asset' && isAsset === false) {
return false;
}
if (env === 'browser' && isAsset === true) {
return false;
}
if (env === 'mobile' && isMobile === false) {
return false;
}
if (env === 'desktop' && isMobile === true) {
return false;
}
if (!me.isTarget(target)) {
return false;
}
return true;
}
get env() {
return this.getAttribute('env') || '';
}
get target() {
return this.getAttribute('target') || '';
}
get url() {
let me = this;
let url = me.getAttribute('url') || '';
if (me.env === 'asset') {
return url + '#mobile';
}
return url;
}
get type() {
let me = this;
let type = me.getAttribute('type') || ''
if (!type) {
if (me.url.indexOf('.js') > 0) {
type = 'text/javascript';
} else if (me.url.indexOf('.css') > 0) {
type = 'text/css';
}
};
return type;
}
get async() {
return this.getAttribute('async') == 'true';
}
get defer() {
return this.getAttribute('defer') == 'true';
}
get isHead() {
return this.getAttribute('head') != 'false';
}
get isAuto() {
return this.getAttribute('auto') == 'true';
}
}
(() => {
Object.defineProperty(GSBase, '_id', {
value: 0,
enumerable: true,
configurable: false,
writable: true
});
Object.seal(GSBase);
})();
/*
* Copyright (C) 2015, 2020 Green Screens Ltd.
*/
/**
* Link element to load css src based on environment
*/
class GSLink extends GSBase {
render() {
let me = this;
let el = document.createElement('link');
el.async = me.async;
el.defer = me.defer;
el.rel = me.rel;
el.type = me.type;
el.href = me.url;
return el;
}
get rel() {
let me = this;
let rel = me.getAttribute('rel') || '';
if (!rel) {
if (me.url.indexOf('.css')) {
rel = 'stylesheet';
}
}
return rel;
}
}
Object.seal(GSLink);
customElements.define('gs-link', GSLink);
/*
* Copyright (C) 2015, 2020 Green Screens Ltd.
*/
/**
* Script element to load src based on environment
*/
class GSScript extends GSBase {
/**
* Called every time node is added to parent node
*/
render() {
let me = this;
let el = document.createElement('script');
el.async = me.async;
el.defer = me.defer;
el.type = me.type;
el.src = me.url;
return el;
}
}
Object.seal(GSScript);
customElements.define('gs-script', GSScript);
Final Word
We hope you will find this very useful in your projects. But also, we wanted to give you insight how our incoming GS-UI modernization lib is built and to get the idea why our approach is different, modern, powerful yet very easy to learn and use.
Note: Library is based on web custom elements technology.