/* /common/helpers/loader.js started 11/06/2023 from play_finder_load.js D.8 started 29/08/2022 from load_tester.js started 15/08/2022 to see about controlling the loading of specific js versions and play_travel_load.js A.5 started 27/09/2022 F.1 12/06/2023 loads js & css, but not necessarily in the correct order; based on _loadScript; preserved by FH as loader (2023_06_13 11_47_37 UTC).js which also includes other approaches F.2 15/06/2023 new approach based on cScriptLoader; seems to keep loading order properly I.2 26/07/2023 q_log; load_sqnc; load_next I.15 30/04/2024 wad but needs further consideration I.16 10/06/2024 resolve load_next() issue exposed by move to vemk V.1 12/06/2024 migrate to vemk; disable stat calls; change load_sqnc(); abandon load_next V.11 15/10/2024 _whover correction for www; site deinitions V.11 15/01/2025 use cache flag in pull(0 to speed up loading V.11 27/02/2025 adjust _whover for vmuk.pull.js; prep for JSmin; V.11 09/03/2025 rename vemap version to loader.2.0.js because breaking changes; */ //## purpose // latest attempt at generic dynamic loader for js scripts and css // loads & stats scripts & css without delay // order and triggering are determined externally; play_travel_pull.js is a template // puts filename and date into standard metadata div // self contained- does not require any helpers // sends filename and path to server; files are optionally pulled and installed; all files are statted by spi.js // // ## method // should be loaded directly in the html in (so from *.rb not loader.erb) // intended to be called from eg pull.js which will organise which files are wanted // can also be called on the fly for individual files // // load_some(sender, stem, path, leadr, names, tailr, report, install = true) // load_file(some_sender, some_path, some_file, report, install = true) // some_sender used by app.js for reporting eg 'campFinder_load_html' or 'play_rail' // some_path path on VE; relative to www eg 'finder/play_travel/require/' or 'common/helpers' // some_file file name inc ext; eg 'play_travel_init.js' or 'play_travel_style.css' // report true: confirm loading & stat to console; false: silent; however this is used by the logr system for finer control; see below // install true: load the script & stat; false: stat only; stat gets & formats file date & passes it to standard md div //## method: // sender, eg 'railFinder_html'; used by app.js for console reporting // path, eg 'common/tempers/' path on VE; relative to www // leadr, eg 'railFinder' or 'play_rail'; can be set globally but overridden for individual files // names, eg ['init', 'show', 'maap', 'call']; an array; individual file would be eg ['call'] // tailr, eg '.js' or 'min.js'; can be set globally but overridden for individual files // report, true log & stat to console; false silent to console; // report is set in pull, mostly by verbose but can be overridden // report cannot be seen by _loadScript, so set true/false in logr_lscr() // install true load script into ; false stat only; // stats itself; //## logr loader logging // // verbose is set in pull.js for its project // most calls to loader.js follow verbose, but it can be overridden for any file // report is set per file by poull.js and used in loader.js functions // logr is used in loader.js to implement this with optional override // set individually, per comment // comments outside this system are obviously exempt // // ## method // copy a standard function & rename; edit msg // use standard blocks (see below) as required // // ## usage // logr_name (view, some_msg, some_thing = dummy, some_flag) // view is local to each fnction; used to control individual // 0 off // 1 on when report true // override 2 on when report false // cancel override // logr_cncl = 2 override makes comments // logr_cncl = 3 override comments are disabled // some_msg text to be displayed; before comma // some_thing array, object etc to sniff // use eg fn = some_json not fn = "some_json" // some_flag report; set by pull; // // // ## standard blocks; edit name before use // //#report // fn = '' // logr_msg = 'some_fn:: fish ' + fish ; logr_name(1, logr_msg, fn, report) // fn = fish //## an object, array etc // logr_msg = "'some_fn:: fish '" ; logr_name(2, logr_msg, fn, report) // // ### scopes // Anything in root of loader.js is supra-global and will be defined for all projects // Anything in root of ~pull.js is global to that project & sub-projects eg osgb // Anything in root of ~init.js is global to that project // Anything in root of ~file.js is local to file.js // // ; console.log('loader.js ' + ' F.2' + ' started @ ' + new Date().toString().slice(16,24) ) //#### see scopes (above) before adding functions or variables to this file //## metadata first var MD = {}; //## MUST be called before anything else; used by meta.js MD.loader = []; ; // console.log('loader:: MD ', MD) MD.loaded = ['NB loaded scripts can report "cache" or "servr" but this is overidden by dev tools "disable cache"']; ; // console.log('loader:: MD ', MD) MD.timing = {}; MD.timing.start = new Date().valueOf(); //## cannot use _ts; not built yet MD.tmp = {}; MD.tmp.lap = 0; // post_stat identifier MD.helpers = {}; MD.helpers.count = {}; MD.browser = navigator.userAgent; //## this is, or can be, replaced downstream by get_browser() hc = MD.helpers.count; hc.whover = 0; hc._whover = 0; hc._padlog = 0; q_log_col = '#eedfa5'; //## default //## W3 colours UK Light straw //## for proj_helpers finish = false; alive = ''; awake = ''; //## logr see above logr_cncl = 3; //cancel override; no logr reporting //# MD.pull.cache is new at V.11 if (typeof(MD.pull) == 'undefined') { MD.pull = {}; MD.pull.cache = false; } else { if (typeof(MD.pull.cache) == 'undefined') { MD.pull.cache = false; }; } //## fallback for report; goky but it's coming up as 'undefined' at I.2 which breaks reporting // try {if (report === undefined) {report = true}} catch (err) { report = true} //#### identity # see identity explanation in pull.js; # set ver first: be careful!!; //#### block, function and call must run first //############################ whover V.11 27/02/2024 V.11 01/10/2024 I.2 27/07/2023 function _whover (ohw = 'who?', ver = 'ver?', vrl = _site) { ; // console.log(who + ':: _whover ' + vrl, document.currentScript.src) ; // console.log("\n") ; console.log('loader:: _whover who ', who) function _padlog(some_exp, some_size, some_just='R') { ; // console.log('padlog') hc._padlog += 1; x = some_exp.toString().length; y = some_size; fill = ''; for (var k = x; k < y; k++) { fill += ' '} ; // say('pad:: fill ', fill) if (some_just == 'R') { return fill + some_exp; } else { return some_exp + fill; }; } //## _padlog hc._whover += 1; explain = false; if ( ohw == 'who?') { explain = true }; if ( ver == 'ver?') { explain = true }; if ( vrl == undefined) { explain = true }; if (explain) { ;console.log( '### common identity system:: who and ver must be set locally; _site must be set by pull.js or locally'); }; //# idky correction because sometimes it's https://www.VE & sometimes https://VE dcs = document.currentScript.src.replace('www.', '') .replace(vrl.replace('www.', ''), ''); // who is picked up from indx when _whover is called from vmuk if (typeof(who) == 'undefined') {} else { if (ohw == who) {} else { dcs = dcs.replace(who, ohw); } ; // console.log("dcs", dcs) ; console.log("ohw", ohw); console.log("who", who) }; if (dcs.includes('?')) { dcs = dcs.split('?')[0] }; ; // console.log("dcs", dcs) ; console.log("who", who) if (dcs.includes('.pull.js')) { ver = ver.replace('(pull.js) ', '') }; msg = _padlog(ohw + ':: ', 10, 'L') + _padlog(dcs, 60, 'R') + ' ' + ver + ' started @ ' + new Date().toString().slice(16,24); q_log(msg) ; // console.log('loader:: _whover msg', msg) return msg; } //## _whover _site = 'https://www.vemap.co.uk/'; //## must be set // _site = 'https://www.videoengineer.co.uk/'; //## must be set this_scr = 'loader'; //## in helpers use this_scr to avoid downstream inheritance of who, ver this_ver = 'V.11 27/02/2025'; //## in helpers keep up to date manually _whover(this_scr, this_ver) //## keyword FUNCTION api calls at the top, then local then generic //## helper FUNCTIONS ** these are global but renamed & intended for local use only //############################ ts 03/07/2023 function _ts() { //## console.log(ts()) // 1688417076128 return new Date().valueOf(); } //############################ _isEmpty() F.2 04/07/2023 1.3 14/05/2021 function _isEmpty (value) { ; // console.log('loader:: _isEmpty ') return ( (value == null) || // null or undefined (value.hasOwnProperty('length') && value.length === 0) || // has length and it's zero (value.constructor === Object && Object.keys(value).length === 0) // is an Object and has no keys ); } // ## _isEmpty //############################ _getName F.2 04/07/2023 function _getName(some_url) { ; // console.log(' _loadScript:: _getName ') //## extract bare filename from url fish = some_url.replace(w_site, '') ; // console.log('yxfish ' + fish) ; //console.log('yxfish C ' + fish.replace(".js?v=%3C?=hash_file(%27md5%27,%20$file);?%3E", '') ) return fish.replace(".js?v=%3C?=hash_file(%27md5%27,%20$file);?%3E", ''); } //## _getName //## logr FUNCTIONS //############################ q_log Q.1 01/01/2024 I.2 28/07/2023 function q_log(someword, something) { ; // console.log('loader:: q_log someword', someword) // function q_log(someword, something, some_colour) { ; // console.log(' _loadScript:: _getName ') //## colour console.log //# renamed Q.1 to avoid conflict with q_log() in helpers.core some_colour = q_log_col ; // console.log('q_log:: some_colour', some_colour) a_msg = "%c" + someword ; // console.log('loader:: q_log:: a_msg', a_msg) // c_msg = 'color: ' + '#FFCC00' c_msg = 'color: ' + some_colour; e_msg = 'color: ' + '#FF6699'; x_msg = ''; if (someword.includes('err')) { x_msg = e_msg} else { x_msg = c_msg}; if (_isEmpty(something)) { ; // console.log('q_log:: something is empty') return console.log(a_msg, x_msg); } else { ; // console.log('q_log:: something not empty') return console.log(a_msg, x_msg, something); }; } //## q_log //########################### logr_stat I.2 26/07/2023 F.2 04/07/2023 function logr_stat (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_stat:: ' + some_number + ' some_thing ' + some_thing + ' some_flag ' + some_flag + ' some_name ' + some_name + ' logr_msg ' + some_msg) /*## loader logging uses logr_stat ## method logr_stat (view, some_msg, some_thing = dummy, some_flag) view is local to this fnction; used to control individual 0 off 1 on when report true 2 on when report false some_msg text to be displayed; before comma some_thing array, object etc to sniff use eg fn = some_json not fn = "some_json" some_flag report; set by pull etc; varies with every file; don't use verbose ## standard blocks; edit name_msg before use //#report fn = '' name_msg = 'some_sender ' + some_sender ; name_msg(1, logr_msg, fn, report) fn = payload //## an object name_msg = "payload" ; name_msg(2, logr_msg, fn, report) */ msg = " post_stat:: " + "[" + MD.tmp.lap + "] " + some_msg ; // console.log('logr_stat:: MD.tmp.lap', MD.tmp.lap) // msg = " post_stat:: " + some_msg ; // console.log('logr_stat:: MD.tmp.lap', MD.tmp.lap) if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // if (some_number > 0) { console.log(msg,) } // } else { // if (some_number > 0) { console.log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_stat //########################### logr_some I.2 26/07/2023 F.2 04/07/2023 function logr_some (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_some:: ' + some_number + ' some_flag ' + some_flag + ' logr_msg ' + some_msg) /*## loader logging ## method copy this function & rename; edit msg logr_name (view, some_msg, some_thing = dummy, some_flag) view is local to this fnction; used to control individual 0 off 1 on when report true 2 on when report false some_msg text to be displayed; before comma some_thing array, object etc to sniff use eg fn = some_json not fn = "some_json" some_flag report; set by pull etc; varies with every file; don't use verbose ## standard blocks; edit name before use //#report fn = '' logr_msg = 'some_sender ' + some_sender ; logr_name(1, logr_msg, fn, _reprt) fn = payload //## an object logr_msg = "payload" ; logr_name(2, logr_msg, fn, _reprt) */ msg = " load_some:: " + some_msg; if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // if (some_number > 0) { console.log(msg,) } // } else { // if (some_number > 0) { console.log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_some //########################### logr_more I.2 26/07/2023 F.2 04/07/2023 function logr_more (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_more:: ' + some_number + ' some_flag ' + some_flag + ' logr_msg ' + some_msg) msg = " load_more:: " + some_msg; if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // if (some_number > 0) { console.log(msg,) } // } else { // if (some_number > 0) { console.log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_more //########################### logr_ordr I.2 26/07/2023 F.2 04/07/2023 function logr_ordr (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_ordr:: ' + some_number + ' some_flag ' + some_flag + ' logr_msg ' + some_msg) msg = " logr_ordr:: " + some_msg; if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // if (some_number > 0) { console.log(msg,) } // } else { // if (some_number > 0) { console.log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_ordr //########################### logr_next I.2 26/07/2023 F.2 04/07/2023 function logr_next (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_ordr:: ' + some_number + ' some_flag ' + some_flag + ' logr_msg ' + some_msg) msg = " logr_next:: " + some_msg; if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // // if (some_number > 0) { console.log(msg,) } // if (some_number > 0) { q_log(msg) } // } else { // // if (some_number > 0) { console.log(msg, some_thing) } // if (some_number > 0) { q_log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_next //########################### logr_scrl I.2 26/07/2023 F.2 04/07/2023 function logr_scrl (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_scrl:: ' + some_number + ' some_flag ' + some_flag + ' logr_msg ' + some_msg) /*## loader logging ## method copy this function & rename; edit msg logr_name (view, some_msg, some_thing = dummy, some_flag) view is local to this fnction; used to control individual 0 off 1 on when _reprt true 2 on when _reprt false some_msg text to be displayed; before comma some_thing array, object etc to sniff use eg fn = some_json not fn = "some_json" some_flag _reprt; set by pull etc; varies with every file; don't use verbose ## standard blocks; edit name before use //#report fn = '' logr_msg = 'some_sender ' + some_sender ; logr_name(1, logr_msg, fn, _reprt) fn = payload //## an object logr_msg = "payload" ; logr_name(2, logr_msg, fn, _reprt) */ // log_col = log_col msg = "ScriptLoader:: " + some_msg; // msg = " x load_more:: " + some_msg if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // if (some_number > 0) { console.log(msg,) } // } else { // if (some_number > 0) { console.log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_scrl //########################### logr_lscr I.2 26/07/2023 F.2 04/07/2023 function logr_lscr (some_number, some_msg, some_thing = dummy, some_flag, some_name) { ; // console.log(' logr_lscr:: ' + some_number + ' some_flag ' + some_flag + ' logr_msg ' + some_msg) //## this one is different: cannot pick up _reprt in _loadScript or here so set manually for now // this overrides the setting in the loadCsript definition some_flag = false; // some_flag = true msg = " _loadScript:: " + some_msg; if (some_flag || some_number == logr_cncl) { // if (_isEmpty(some_thing )) { // if (some_number > 0) { console.log(msg,) } // } else { // if (some_number > 0) { console.log(msg, some_thing) } // } q_log(msg, some_thing); }; } //## logr_lscr //## loader FUNCTIONS ** //#fun######################## cScriptLoader V.11 15/01/2025 Z.1 12/06/2024 F.2 04/07/2023 var cScriptLoader = (function () { ; // console.log('cScriptLoader') //## from https://stackoverflow.com/questions/14521108/dynamically-load-js-inside-js // works well, although won't load standalone .css // apart from #report & some diagnostic logging there's little stylistic change, so lack of braces make it hard to read or follow //## cacheing by design always caches; good for dev but not wanted for .min.js; use dev flag or get status from MD and make alternative this.withNoCache // would this work transparently instead of current ugly arrangement? // withNoCache filename A https://videoengineer.co.uk/playmaps/play.journey.js loader.js:375:81 // withNoCache filename B https://videoengineer.co.uk/playmaps/play.journey.js?no_cache=1688312508129 //## V.11 cacheing // use cache flag in MD.loader; set in pull() < NB loader not loaded> // reports in MD.loaded as "load" no cache flag // "cache" loaded without cacheing *BUT* dev tools "disable cache" will override this // "servr" pulled from server tag = 'load'; function cScriptLoader(files, _reprt) { ; // console.log(who + ':: cScriptLoader', files) var _this = this; ft = {}; // file timing this.withNoCache = function (filename) { ; // console.log('withNoCache filename A ' + filename) ; // console.log('MD.loader.cache' ,MD.loader.cache) fnm = filename.replace(w_site, ''); // ScriptLoader filename fnm_a = fnm; // post_stat filename MD.loaded[fnm] = {}; // if (MD.loader.cache) { if (MD.pull.cache) { tag = 'cache'; } else { if (filename.indexOf("?") === -1) { filename += "?no_cache=" + new Date().getTime(); } else { filename += "&no_cache=" + new Date().getTime(); } ; // console.log('withNoCache filename B ' + filename) tag = 'servr'; } ; // console.log('withNoCache filename C ' + filename) return filename; }; //# CSS this.loadStyle = function (filename) { ; // console.log(who + ':: loadStyle filename ' + filename) link = document.createElement("link"); //## HTMLLinkElement link.rel = "stylesheet"; link.type = "text/css"; link.href = _this.withNoCache(filename); ; // console.log('Loading style ' + filename); link.onload = function () { ; // console.log(who + ':: Loaded style "' + filename + '".'); //#report ft.load = _ts() - ft.strt; // ft.cScriptLoader = _ts() - ft.strt dur = _ts() - MD.timing.start; fn = ''; logr_msg = dur + ' Loaded style "' + filename + ' in ' + ft.load ; logr_scrl(2, logr_msg, fn, _reprt); // // MD.loaded[fnm].load = ft.load // // MD.loaded[fnm].cScriptLoader = ft.load // MD.loaded[fnm]['loadStyle~' + tag] = ft.load // // MD.loaded[fnm].name = fnm // // this_fl = _this.m_js_files[i].replace(w_site, '') ; // console.log(who + ':: this_fl', this_fl) this_fl = filename.replace(w_site, '').replace('../..', '') ; // console.log(who + ':: this_fl', this_fl) MD.loaded[this_fl] = {}; MD.loaded[this_fl][tag] = dur ; // logr_scrl(2, logr_msg, fn, _reprt) }; link.onerror = function () { ; console.error("ScriptLoader:: " + 'Error loading style "' + filename + '".'); }; _this.m_head.appendChild(link); }; //# JS this._loadScript = function (i) { ; // console.log(who + ':: _loadScript', i) ft.strt = _ts(); script = document.createElement('script'); script.src = _this.withNoCache(_this.m_js_files[i]); ; // console.log(who + ':: _loadScript files ', script.src) script.type = 'text/javascript'; var loadNextScript = function () { ; // console.log(who + ':: loadNextScript ' + (i+1) + ' "' + _this.m_js_files[i+1] + '".'); if (i + 1 < _this.m_js_files.length) { _this._loadScript(i + 1); }; }; script.onload = function () { ; // console.log(who + ':: _loadScript Loaded script ' + i + ' "' + _this.m_js_files[i] + '".'); ; // _this.log('Loaded script "' + _this.m_js_files[i] + '".'); loadNextScript(); //#report ft.load = _ts() - ft.strt; dur = _ts() - MD.timing.start ; // console.log(who + ':: _loadScript Loaded script ' + i + ' "' + _this.m_js_files[i] + '" dur', dur); fn = ''; logr_msg = dur + ' Loaded script "' + _this.m_js_files[i] + ' in ' + ft.load; this_fl = _this.m_js_files[i].replace(w_site, '').replace('../..', '') ; // console.log(who + ':: this_fl', this_fl) MD.loaded[this_fl] = {}; MD.loaded[this_fl][tag] = dur ; // logr_scrl(2, logr_msg, fn, _reprt) // MD.loaded[fnm]['_loadScript~' + tag] = dur ; logr_scrl(2, logr_msg, fn, _reprt) }; script.onerror = function (){ ; console.log(who + ':: cScriptLoader \t *catch*', 'Error loading script "' + _this.m_js_files[i] + '".'); ; // console.error("ScriptLoader:: " + 'Error loading script "' + _this.m_js_files[i] + '".'); loadNextScript(); } ; // _this.log('Loading script "' + _this.m_js_files[i] + '".'); _this.m_head.appendChild(script); }; this.loadFiles = function () { ; // console.log(who + ':: loadFiles ', _this.m_css_files) ; // this.log(this.m_css_files); // this.log(this.m_js_files); for (var i = 0; i < _this.m_css_files.length; ++i) _this.loadStyle(_this.m_css_files[i]); _this._loadScript(0); }; this.m_js_files = []; this.m_css_files = []; this.m_head = document.getElementsByTagName("head")[0]; //## this.m_head = document.head; // IE9+ only function endsWith(str, suffix) { if (str === null || suffix === null) { return false; }; return str.indexOf(suffix, str.length - suffix.length) !== -1; }; for (var i = 0; i < files.length; ++i) { if (endsWith(files[i], ".css")) { this.m_css_files.push(files[i]); } else if (endsWith(files[i], ".js")) { this.m_js_files.push(files[i]); } else { ; console.error("ScriptLoader:: " + 'Error unknown filetype "' + files[i] + '".'); }; }; ; // console.log('this 3', this) }; return cScriptLoader; })() //#fun######################## _loadScript F.2 04/07/2023 1.3 15/05/2021 _loadScript = (source, beforeEl, _reprt = false, async = true, defer = true) => { // const _loadScript = (source, beforeEl, _reprt = false, async = true, defer = true) => { //### copied & renamed from play_helpers 01/07/2023; not used by loader but needed for php and belongs here /*//## working method; avoids reloading; can be nested, see base_json_div.erb::do_marker() let loadedm1 = 'no' function do_marker() { ; // say('base_json_div.erb:: loadedm1 ', loadedm1) if (loadedm1 == 'yes') {clickMarker(); return;} var scriptUrl = "./local_markers.js?v=" ; // log('loading: ' + scriptUrl) _loadScript(scriptUrl).then(() => { clickMarker() loadedm1 = 'yes' ; log('script loaded: ' + scriptUrl); }, () => { ; log('FAILED to load script ' + scriptUrl); }); } //## use relative path addressing */ ft = {} ; // file timing ft.strt = _ts() ; // console.log('_loadScript source', source) fnm_a = ''; if (source.includes('./') ) { fnm_a = source.replace('./', MD.pull.foldr); } else { fnm_a = source; }; //#report fn = ''; logr_msg = "source" ; logr_lscr(1, logr_msg, fn, _reprt); // logr_msg = "yxstat B " + 'source ' + source ; logr_lscr(2, logr_msg, fn, _reprt) return new Promise((resolve, reject) => { let script = document.createElement('script'); const prior = beforeEl || document.getElementsByTagName('script')[0] ; // say('Promise prior ', prior) script.async = async; script.defer = defer; //#report fn = ''; logr_msg = "yxstat D " + ' source ' + source + ' fnm_a ' + fnm_a + ' prior ' + _getName(prior.src) ; logr_lscr(1, logr_msg, fn, _reprt); function onloadHander(_, isAbort) { ; // console.log(' _loadScript:: Loaded script ' + source + ' ' + fnm) //#report ft.load = _ts() - ft.strt; dur = _ts() - MD.timing.start; fn = ''; logr_msg = dur + ' Loaded script ' + source + ' in ' + ft.load ; logr_lscr(2, logr_msg, fn, _reprt); file = source.replace('./', '').replace("?v=", '') ; // console.log('file ', file) ; // console.log('source ', source); path = MD.pull.foldr ; // console.log('path ', path) if (file.includes('/')) {} else { file = path + file ; }; MD.loaded[file] = {}; MD.loaded[file].name = file; MD.loaded[file]._loadScript = ft.load; //## prep for stat fobj = { fpath: path, fname: file, }; post_stat(sender, fobj, _reprt); //## reject if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) { script.onload = null; script.onreadystatechange = null; script = undefined; if (isAbort) { reject(); } else { resolve(); }; }; }; script.onload = onloadHander; script.onreadystatechange = onloadHander; script.src = source; prior.parentNode.insertBefore(script, prior); }); // ## Promise /* Returns: true: undefined, null, "", [], {} false: true, false, 1, 0, -1, "foo", [1, 2, 3], { foo: 1 } from: https://stackoverflow.com/questions/5515310/is-there-a-standard-function-to-check-for-null-undefined-or-blank-variables-in */ /* ### _loadScript from ## from https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript/28002292#28002292 ## usage const scriptUrl = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit'; // example // const scriptUrl = "some_url"; _loadScript(scriptUrl).then(() => { console.log('script loaded'); }, () => { console.log('fail to load script'); }); ## all about promises https://web.dev/promises/ ## anothern working option is cScriptLoader at https://stackoverflow.com/questions/14521108/dynamically-load-js-inside-js */ } //## _loadScript //## main FUNCTIONS ** //########################### post_stat F.2 04/07/2023 F.1 11/06/2023 A.5 08/05/2023 // function post_stat(some_sender, this_json, _reprt) { ; // console.log('post_stat:: _reprt ' + _reprt + ' ' + some_sender + ' ', some_json ) function post_stat(some_sender, some_json, _reprt) { ; // console.log('post_stat:: _reprt ' + _reprt + ' ' + some_sender + ' ', some_json ) /* sends POST request to scr_stat.js which returns file name & last modification date; then calls call_jss to load the script */ // ' load_some:: //## loader logging uses logr_stat // _reprt = true // some_json = {} ; console.log('post_stat:: this_json', this_json) // if (this_json.fpath == '' && this_json.fname.includes('/') ) { ; console.log('post_stat:: fpath is empty') // // idx = this_json.fname.lastIndexOf('/') // // some_json.fpath = this_json.fname.slice(0, idx+1) // // some_json.fname = this_json.fname.slice(idx+1) // // } else { // // some_json = this_json // } // some_json = this_json ; ; // console.log('post_stat:: some_json', some_json) //# at V.1 10/06/2024 stst disabled because not really used; working with vemk before disabled return; MD.tmp.lap += 1; MD.tmp.lap['sender'] = some_sender; MD.tmp.lap['json'] = some_json; MD.tmp.lap['report'] = _reprt; payload = JSON.stringify(some_json); //#report fn = some_json; logr_msg = 'some_sender: ' + some_sender ; logr_stat(0, logr_msg, fn, _reprt); fn = payload; logr_msg = "payload" ; logr_stat(1, logr_msg, fn, _reprt); //## xhr xhr = new XMLHttpRequest(); xhr.open("POST", n_site, true) ; // console.log('n_site', n_site) xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Authorization', 'Bearer 123456'); xhr.setRequestHeader('sender', some_sender) ; // console.log(' loader.js:: post_stat: xhr ' , xhr ) xhr.onreadystatechange = function() { ; // console.log(' loader.js:: post_stat: this.readyState ' + this.readyState) ; if (this.readyState == 4 && this.responseText.length > 1) { ; // console.log(' loader.js:: this ', this) camel = JSON.parse(this.responseText)[0] ; // console.log(' loader.js:: post_stat: camel ', camel ) flnm = camel['fname'].split('&&') ; // console.log(' loader.js:: post_stat: flnm ', flnm ) fldt = camel['fdate'].split(':'); hump = fldt[0].replace(' ', ' ') + ':' + fldt[1] + ' ' + flnm[0] ; // console.log(' loader.js:: hump ', hump) //#report fn = hump; // logr_msg = 'xstat [' + fnm_a + ']' ; logr_stat(2, logr_msg, fn, _reprt) fn = ''; logr_msg = fldt[0] + ':' + fldt[1] + ' ' + flnm[0] ; // logr_stat(1, logr_msg, fn, _reprt) fnm = some_json.fpath + some_json.fname ; //console.log('fnm ', fnm) if (MD.loaded[fnm]) { if (MD.loaded[fnm].name) {} else { MD.loaded[fnm].name = fnm; }; } else { // MD.loaded[fnm] = {} MD.loaded[fnm] = {}; // MD.loaded[fnm]['name'] = fnm }; MD.loader.push(hump); fnm_a = ''; //## i_load //## needs revising for _loadScript; use MD i_load.innerHTML += '
' + hump; i_load.innerHTML = i_load.innerHTML.split('
') .sort() .reverse() .join('
') ; // console.log('i_load.innerHTML ', i_load.innerHTML) }; //## readyState }; //## onreadystatechange ; // console.log('post_stat:: payload', payload) xhr.send(payload); } //## post_stat //############################ load_some V.11 15/10/2024 I.15 12/05/2024temp F.2 04/07/2023 F.2 15/06/2023 function load_some(sender, path, leadr, names, tailr, _reprt, install = true) { ; // console.log('load_some ' + ' ' + path + ' ' + leadr + ' _reprt ' + _reprt, names) //# V.11 15/10/2024 now globally defined donstream if (typeof(w_site) == 'undefined') { // w_site = 'https://www.videoengineer.co.uk/'; // w_site = 'https://www.vemap.co.uk/'; w_site = 'https://www.vemap.co.uk'; }; if (typeof(n_site) == 'undefined') { n_site = 'https://node.videoengineer.co.uk/stat'; }; fobj = {} ; // console.log(' load_some:: ' + sender + ' _reprt ', _reprt) // report = false //# I.15 12/05/2024temp ident = leadr.toLowerCase().replace('.', ''); localStorage.setItem( 'load_' + ident + '_optns', JSON.stringify([sender, path, leadr, names, tailr, _reprt, install])); //#report fn = ''; logr_msg = 'sender ' + sender + ' path ' + path + ' leadr ' + leadr + ' names ' + names + ' tailr ' + tailr + ' _reprt ' + _reprt + ' install ' + install; ; logr_some(1, logr_msg, fn, _reprt); files = []; for (var i = 0; i < names.length; i++) { ; // console.log(i + ' ', jss[i]) //## prep for cScriptLoader if (install) { files[i] = w_site + path + leadr + names[i] + tailr ; // console.log(i + ' files[i] ', files[i]) }; //## prep for stat fobj = { fpath: path, fname: leadr + names[i] + tailr, }; post_stat(sender, fobj, _reprt); }; ; // console.log('load_some files ', files ) if (files.length > 0) { // var ScriptLoader = new cScriptLoader(files, verbose); var ScriptLoader = new cScriptLoader(files, _reprt); ScriptLoader.loadFiles(); }; } //## load_some //############################ load_more I.14 23/03/2024 I.2 25/07/2023 F.2 01/07/2023 function load_more(some_thing) { //## convenience to load more scripts or css // intended for post-load requests //## method // some_thing Array ['init', 'call'] will collect, stat & install pathr+leadr+'init'+tailr etc with the same defaults as the main batch listed // do not use railFinder_fish.js, use fish // some_thing String "dir/path/file.js' is a single, fully pathed file; can be used for .js if main load is .min.js //## // loads and reads some_thing each file but needs a delay or promise before opening go(). // simplest is to add a block like this to end of file (or maybe last file) // //## self running for use by load_more() // go(); //## see // load_more(['natural_cntrl']) // load_more('playmaps/play.natural_cntrl.js') // //## full #report if true // if (verbose) { ; console.log(' load_more:: ' + some_thing) // } //#report try { //## changed at I.14 23/03/2024 //# cause is murky & effect is tba //# change report to _reprt to try to avoid conflict downstream if (typeof(_reprt) == 'undefined') { // _reprt = true ; console.log(' load_more:: setting _reprt ', _reprt) _reprt = verbose ; // console.log(' load_more:: setting _reprt ', _reprt) } } catch (err) { ; console.log('load_more:: *catch*', err.message) // _reprt = true }; fn = ''; logr_msg = some_thing ; logr_more(2, logr_msg, fn, _reprt); switch (true) { //## https://stackoverflow.com/questions/43423458/use-string-includes-in-switch-javascript-case; /i means case insensitive // case (isArray(some_thing)): case (some_thing.constructor === Array): // path = pathr path = MD.pull.foldr; // load_some(sender, path, leadr, some_thing , tailr, verbose, true) ; // console.log(sender + ' ' + path + ' ' + leadr) load_some(sender, path, leadr, some_thing , tailr, _reprt, true); ; // console.log(sender + ' ' + path + ' ' + leadr) break; // case (isString(some_thing)): case (some_thing.constructor === String): // load_some(sender, '' , '' , [some_thing], '' , verbose, true) ; // console.log(sender + ' ' + path + ' ' + leadr) load_some(sender, '' , '' , [some_thing], '' , _reprt, true); ; // console.log(sender + ' ' + path + ' ' + leadr) break; default: ; console.log(' load_more:: input not recognised ' + some_thing) }; } //## load_more //## sequential FUNCTIONS ** //# load_ordr appeared to work with labyrinths; might just be size based luck; kept jic //# load sqnc calls cScriptLoader //############################ load_sqnc Z.1 12/06/2024 I.15 12/05/2024temp I.2 25/07/2023 function load_sqnc(sender, path, leadr, arr, tailr, _reprt, install) { ; // console.log(' load_sqnc:: [' + arr.length + '] sender ' + sender + ' path ' + path + ' leadr ' + leadr + ' arr[ ' + arr.length + '] tailr ' + tailr + ' _reprt ' + _reprt + ' install ' + install, arr ) //## load in sequence // expects array; does not check //#report fn = '' ; // console.log('load_sqnc:: path ' + path + ' leadr ' + leadr + ' tailr ' + tailr) logr_msg = ''; logr_msg += 'load_sqnc arr[' + arr.length + ']'; logr_msg += ' indx 0'; logr_msg += ' this file '; // logr_msg += path; // logr_msg += leadr; logr_msg += arr[0]; ; // console.log(logr_msg) // logr_msg += tailr logr_msg += ' next_indx 1'; ; // logr_next(2, logr_msg, fn, _reprt) //# V.1 version mixd = [] ; // console.log(who + ':: load_sqnc arr ', arr) for (var i = 0; i < arr.length; ++i) { ; // console.log(who + ':: load_sqnc ' + i , arr[i]); console.log(who + ':: load_sqnc ' + path + leadr + arr[i] + tailr) mixd.push(path + leadr + arr[i] + tailr) ; // console.log(who + ':: load_sqnc mixd ', mixd) }; ScriptLoader = new cScriptLoader(mixd, _reprt); ScriptLoader.loadFiles(); //# I.15 version; doesn't sequence properly // load_some(sender, path, leadr, [arr[0]] , tailr, _reprt, install) // localStorage.setItem( 'load_' + ident + '_array', JSON.stringify(arr)) ; // console.log(who + ' leadr ' + leadr) // localStorage.setItem( 'load_' + ident + '_optns', JSON.stringify([sender, path, leadr, arr, tailr, _reprt, install])) // ident = leadr.toLowerCase().replace('.', '') ; // put('calling load_next', ident) // load_next('1') } //## load_sqnc // //############################ _halt I.2 26/07/2023 working for 2 // function _halt() { ; // console.log(' load_next:: ' , next_indx ); // console.log('load_one:: ' , next_indx ) // console.log('_halt') // } //############################ load_ordrload_ordr I.4 18/08/2023 I.2 25/07/2023 F.2 01/07/2023 function load_ordr(ordr_1, ordr_2, this_sender = sender) { ; // console.log(' load_ordr:: [' + ordr_1 + ', ' + ordr_2 + ']' ) //#### calls load_more for ordr_1; waits till loaded; then calls for ordr_2 // optional this_sender added @ 1.4 for loading from deep nested scripts // this is brilliant; it just waits for something to happen before erroring; //so success is not convinced that is wad but seems to work //## at I.2 it didn't work until a load_more was added to errorHandler; dky, wip //## problem // between the ScriptLoader:: #report and "success" is about 400ms; needs to be reduced //## method //## see // https://stackoverflow.com/questions/38213668/promise-retry-design-patterns // https://mtsknn.fi/blog/js-retry-on-fail/ // ordr_1 = 'playmaps/require/play.wwll.js' // ordr_2 = 'playmaps/play.labyrinths.js' // // //## full #report if true // // if (verbose) { ; console.log(' load_ordr:: ') ; // [' + //#report // // } ; // console.log(' load_ordr:: _reprt', _reprt) ; // console.log(' load_ordr:: verbose', verbose) //## report //# force logging if report is non-boolean; // try {if (report === undefined) {report = true}} catch (err) { report = true} fn = ''; logr_msg = ordr_1 + ' then ' + ordr_2; ; // logr_ordr(2, logr_msg, fn, _reprt) //## file to be loaded first load_more(ordr_1); // load_some(this_sender, '', '', [ordr_1], '', _reprt, true) var max = 10; // var t = 10; // timeout before attempt completes // t = 20; // timeout before attempt completes t = 5; // timeout before attempt completes var p = Promise.reject(); done = false; j = 0; found = false for(var i=0; i'] ) //## for p = p.then(processResult).catch(errorHandler) // can also chain; eg p.catch(_attempt(i)).then(test).catch(rejectDelay); see links function attempt(i, t, j) { ; // console.log('attempt i ' + i + ' t ' + t ) if (done) { ; // console.log('done') } else { try { ; // console.log('trying...') var rand = Math.random(); tagl = document.getElementsByTagName('script') ; //console.log('tagl', tagl) // found = false for (var i = 0; i < tagl.length; ++i) { ; //console.log('tagl[' + i + ']', tagl[i].src ) if (tagl[i].src.includes(ordr_1)) { found = true ; // console.log('found', found) return; }; } //## check if new script added ## better ways to do this? // if (MD.loader[MD.loader.length-1].includes(ordr_1) ) { // MD.loader.length == 0 :( ; //console.log('found 2', found) if (found) { ; // console.log('found is true ', found) //## full #report if true if (_reprt) { ; // console.log(' load_ordr:: success ' + padlog(ordr_1, 75, 'L') + '[' + j + '] ' + MD.loader[MD.loader.length-1] ) }; load_more(ordr_2); //## see also error handler; this one doesn't seem to always work; not sure if both can co-exist done = true; found = false; throw found; } else { ; // console.log('found is NOT true ', found) j += 1; //## full #report if true if (_reprt) { ; // console.log(' load_ordr:: attempt ' + padlog(ordr_1, 75, 'L') + '[' + j + '] ' + MD.loader[MD.loader.length-1] ); }; setTimeout(function () { attempt(i, t, j) }, t + rand); // setTimeout // return rand; return found; }; } catch (rrr) { if (_reprt) { ; // console.log('attempt *catch* ' + rrr) }; }; } // done } //## attempt function test(val) { ; // console.log('loader.js test', val) if (MD.loader[MD.loader.length-1].includes(ordr_1) ) { ; // console.log('return val ' + val + ' ' + MD.loader[MD.loader.length-1] ); throw val; } else { ; // console.log(' load_ordr:: test retry ' + MD.loader[MD.loader.length-1] ); return val; } } //## test function processResult(res) { ; // console.log('processResult res ' + res + ' ' + MD.loader[MD.loader.length-1] ); } function errorHandler(err) { ; // console.log('errorHandler:: *catch* ' + err); if (_reprt) { ; console.log(who + ':: loader.js errorHandler *catch* _reprt ' + _reprt, err); }; load_more(ordr_2); } function rejectDelay(reason) { ; // console.log(' load_ordr:: rejectDelay t ' + t + ' reason', reason) return new Promise(function(resolve, reject) { setTimeout(reject.bind(null, reason), t); }); } } //## load_ordr /*//############################ load_test I.2 25/07/2023 working for 2 function load_test(ordr_1, ordr_2) { ; // console.log(' load_ordr:: [' + ordr_1 + ', ' + ordr_2 + ']' ) load_more(ordr_1) j = 0 var t = 50; var max = 5; tagl = document.getElementsByTagName('script') ; //console.log('tagl', tagl) found = false var p = Promise.reject(); for(var i=0; i0.4', rand) return rand; } } function test(val) { if(val < 0.5) { ; console.log('test <0.5', val) throw val; } else { ; console.log('test >0.5', val) return val; } } function processResult(res) { ; console.log('processResult res', res) console.log(res); } function errorHandler(err) { ; console.log('errorHandler err', err) console.error(err); } } */ /*//############################ load_next Z.1 09/06/2024 I.15 12/05/2024temp I.2 26/07/2023 working for 2 function load_next(next_indx) { ; // console.log(' load_next:: start ' , next_indx ); // console.log('load_one:: ' , next_indx ) //## from https://jsfiddle.net/duL0qjqe/3/ //# used in I.15; abandoned in V.1 //### NB at V.11 MD.loader redefined as {} not [], so this won't fly without fiddling return if (finish) {return}; arr = JSON.parse(localStorage.getItem('load_' + who.toLowerCase() + '_array')) ; // console.log('arr', arr) opt = JSON.parse(localStorage.getItem('load_' + who.toLowerCase() + '_optns')) ; // console.log('opt', opt) sendero = opt[0] //## options stored by load_sqnc; used internally; 'path' is used & reset everywhere! patho = opt[1] ; // console.log('load_next:: path', path) leadro = opt[2] arro = opt[3] tailro = opt[4] reporto = opt[5] ; // console.log('load_next:: reporto', reporto) installo = opt[6] //#report if (parseInt(next_indx) == arr.length) { fn = '' logr_msg = '' logr_msg += 'load_next D finish' logr_msg += ' last_file ' + arr[next_indx-1] ; // logr_next(1, logr_msg, fn, reporto) finish = true ; // console.log('set finish D ' + finish + ' who ' + prj) next_indx = 99 throw finish } fn = '' logr_msg = '' logr_msg += 'load_next ' logr_msg += ' next_indx ' + next_indx logr_msg += ' next_file ' + arr[next_indx] ; // logr_next(0, logr_msg, fn, reporto) //## define last_indx = next_indx -1 ; // console.log('load_next:: last_indx' , last_indx ) last_file = arr[last_indx] ; // console.log('load_next:: last_file' , last_file ) next_file = arr[next_indx] ; // console.log('load_next:: next_file' , next_file ) ; // console.log('last_file[' + last_indx + '] ' + last_file + ' next_file[' + next_indx + ']', next_file) j = 0 t = 50; // t = 5000; max = 5; found = false //## promise chain var p = Promise.reject(); for(var i=0; i