import { DynamicCSSInclude } from "./DynamicCSSInclude.js"; import { DynamicJSInclude } from "./DynamicJSInclude.js"; /** * @class DynamicDepsLoader * * @description * Responsibilities: * > looks at the DOM and find the component's dependencies * > loads the dependencies and caches them in the LocalStorage * * * HOW IT WORKS * ------------ * * The dependecies are inserted into the component's markup * as data- attribute * * single JS dependency * -------------------- * * * * double (and nested) JS dependencies * ----------------------------------- * in this case the second js is loaded and launched after the * execution of the first one * * * * deferred JS dependencies * ------------------------ * * * they work the same as the other dependencies but they are * loaded just after the first scroll. * * * single CSS dependency * --------------------- * * * multiple CSS dependencies: in this case add more than one dependency * in priority order. ex: * * * * NOTE: we could add other feature like cache expiration times * * @param {dictionary} args { jsdir, cssdir } */ export class DynamicDepsLoader { constructor( args = { // set defaults jsdir : '/js/components/', cssdir : '/css/components/' }) { this.base_url = `${window.location.protocol}//${window.location.host}`; /// write the properties this.js_dir = args.jsdir; this.css_dir = `${this.base_url}${args.cssdir}`; this.js_dep_selector = '[data-js-dependency]'; this.def_js_dep_selector = '[data-js-deferred-dependency]'; this.css_dep_selector = '[data-css-dependency]'; /// feature detection IntersectionObserver this.fallback = ( typeof window.IntersectionObserver == 'undefined' ); } scanDependencies() { // load dependecies if(this.fallback) this.FallbackLoadDeps(); else this.getDeps(); } scanJS() { return this.scan( this.js_dep_selector, 'jsDependency', this.js_dir ); }; scanCSS() { return this.scan( this.css_dep_selector, 'cssDependency', this.css_dir ); } /** * Scan the markup to find js deferred deps */ scanDeferredJS() { return this.scan( this.def_js_dep_selector, 'jsDeferredDependency', this.js_dir ); } /** * Takes the dependencies * and istance them all */ getDeps() { // scan components and find dependencies this.js_deps = this.scanJS(); this.def_deps = this.scanDeferredJS(); this.css_deps = this.scanCSS(); // defer dependencies loading after the first scroll this.firstScrollTrigger( () => { new DynamicJSInclude( this.def_deps ); } ); // appends js and styles return { jsinc : new DynamicJSInclude( this.js_deps ), cssinc : new DynamicCSSInclude( this.css_deps) }; } /** * Scan the markup to find deps */ scan( selector, datafield, prefix) { // find the elements with dependencies let elements = [].map.call(document.querySelectorAll( selector ), i => i ); // extract the dependencies return elements.reduce( ( dependencies, element ) => { let deps = element.dataset[ datafield ].split(',') /// remove empty spaces .map( dep => dep.trim() ) /// remove empty deps .filter( dep => dep ) /// add prefix .map( dep => prefix + dep ) /// remove already taken deps .filter( dep => !dependencies.flat().includes( dep ) ); // remove dependency attribute delete element.dataset[ datafield ]; if( deps.length ) dependencies.push( deps ); return dependencies; }, [] ); } /** * set the trigger to * sense the first scroll and executes the callback */ firstScrollTrigger( callback ) { /// create the trigger element var trigger = document.createElement('span'); trigger.style.position = 'absolute'; trigger.style.display = 'block'; trigger.style.top = ( window.innerHeight + 10 ) + 'px'; trigger.style.left = '0'; trigger.style.width = '1px'; trigger.style.height = '1px'; trigger.style.backgroundColor = 'transparent'; /// put the trigger on the page document.body.style.position = 'relative'; document.body.appendChild(trigger); /// setup the observer var observer = new IntersectionObserver( function (entries, observer) { entries.forEach( function( entry ) { if( entry.isIntersecting ) { /// call the callback callback(); /// stop observing observer.unobserve(trigger); } }.bind(this)); }.bind(this)); /// start observing observer.observe(trigger); } }