I am looking to port my existing website that uses a theme to utilise react components.
The theme has a lot of functions that makes the UI render properly (including several animations).
The theme's js imports a lot of other js libraries.
This means that there is no way for me to write the react version of whatever UI elements that the theme provides.
However, the actually elements can be used as 'dumb components' and I dont need any data bound functionality from them.
I can import my js libraries' dependencies in my public/html folder so thats not a big issue.
The real issue is my theme's library (which I will call scripts.js) not getting loaded every time there is a redirect using react-router.
Since react-router doesnt actually reload the page, there is no way for my scripts.js to know when to do its UI functions.
This discussion here
talks about wrapping the scripts.js in a function and calling it every time an on update happens at the Router level.
Since react-router 4 doesnt have onUpdate , I decided to subscribe to the history.
I am doing that like so:
import {createBrowserHistory} from 'history';
const history = createBrowserHistory();
history.listen(() => {
console.log("url has changed");
});
export {history};
Now I am able to know every time a route change happens, all I need now is to actually get the scripts.js to 'load'.
My scripts.js is very long and some of it is proprietary , so I will only post a snippet of it here.
mr = (function (mr, $, window, document){
"use strict";
mr = mr || {};
var components = {documentReady: [],documentReadyDeferred: [], windowLoad: [], windowLoadDeferred: []};
mr.status = {documentReadyRan: false, windowLoadPending: false};
$(document).ready(documentReady);
$(window).on("load", windowLoad);
function documentReady(context){
context = typeof context === typeof undefined ? $ : context;
components.documentReady.concat(components.documentReadyDeferred).forEach(function(component){
component(context);
});
mr.status.documentReadyRan = true;
if(mr.status.windowLoadPending){
windowLoad(mr.setContext());
}
}
function windowLoad(context){
if(mr.status.documentReadyRan){
mr.status.windowLoadPending = false;
context = typeof context === "object" ? $ : context;
components.windowLoad.concat(components.windowLoadDeferred).forEach(function(component){
component(context);
});
}else{
mr.status.windowLoadPending = true;
}
}
mr.setContext = function (contextSelector){
var context = $;
if(typeof contextSelector !== typeof undefined){
return function(selector){
return $(contextSelector).find(selector);
};
}
return context;
};
mr.components = components;
mr.documentReady = documentReady;
mr.windowLoad = windowLoad;
return mr;
}(window.mr, jQuery, window, document));
Another bit :
//////////////// Scroll Functions
mr = (function (mr, $, window, document){
"use strict";
mr.scroll = {};
var raf = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
mr.scroll.listeners = [];
mr.scroll.busy = false;
mr.scroll.y = 0;
mr.scroll.x = 0;
var documentReady = function($){
//////////////// Capture Scroll Event and fire scroll function
jQuery(window).off('scroll.mr');
jQuery(window).on('scroll.mr', function(evt) {
if(mr.scroll.busy === false){
mr.scroll.busy = true;
raf(function(evt){
mr.scroll.update(evt);
});
}
if(evt.stopPropagation){
evt.stopPropagation();
}
});
};
mr.scroll.update = function(event){
// Loop through all mr scroll listeners
var parallax = typeof window.mr_parallax !== typeof undefined ? true : false;
mr.scroll.y = (parallax ? mr_parallax.mr_getScrollPosition() : window.pageYOffset);
mr.scroll.busy = false;
if(parallax){
mr_parallax.mr_parallaxBackground();
}
if(mr.scroll.listeners.length > 0){
for (var i = 0, l = mr.scroll.listeners.length; i < l; i++) {
mr.scroll.listeners[i](event);
}
}
};
mr.scroll.documentReady = documentReady;
mr.components.documentReady.push(documentReady);
return mr;
}(mr, jQuery, window, document));
I am not well versed with the way these libraries are compiled from web pack to the browser, so sorry if the code block was inadequate/unnecessary
My question is, how do I make sure my script is loaded every time a react-routerpage change happens.
Related
I'm building a JS SDK to be exposed in window, and I went to look into how other SDKs do it.
Intercom does this:
var i = function() {
i.c(arguments);
};
i.q = [];
i.c = function(args) {
i.q.push(args);
};
Hotjar does this:
h.hj =
h.hj ||
function() {
(h.hj.q = h.hj.q || []).push(arguments);
};
Pendo does this:
o._q = o._q || [];
v = ['initialize', 'identify', 'updateOptions', 'pageLoad', 'track'];
for (w = 0, x = v.length; w < x; ++w)
(function(m) {
o[m] =
o[m] ||
function() {
o._q[m === v[0] ? 'unshift' : 'push'](
[m].concat([].slice.call(arguments, 0))
);
};
})(v[w]);
But I don't really understand what is the purpose of this code, and from what little I gathered, it seems related to which methods they expose in their global property.. Is this something I should worry when building a web SDK and should it be in my copy-paste snippet?
Well, I figured it out myself reading the minified code.
This stuff exists in order to make the methods fully available from the moment the JavaScript is interpreted by the browser, and since it would take some time for the actual JS asset to load via network, you could potentially miss your very first method calls.
Seems like its a queue intended to be used only when the SDK first loads, processing method calls that happened while it was loading.
I add this snippet to each javascript file used in my asp.net web api application to avoid multiple load :
Fullcalendar.js
blog = {};
blog.comments = blog.comments || {};
blog.comments.debugMode = false;
blog.isFirstLoad = function (namesp, jsFile) {
var isFirst = namesp.jsFile.firstLoad === undefined;
namesp.jsFile.firstLoad = false;
return isFirst;
};
$(document).ready(function () {
if (!blog.isFirstLoad(blog.comments, "fullcalendar.js")) {
return;
}
});
Sometimes I get a weird exception
Uncaught TypeError: Cannot read property 'firstLoad' of undefined
I need to know :
Why this happens?
How can I fix it?
A couple of problems there.
First, you shouldn't be loading the file more than once in the first place, so it shouldn't be necessary to go through this business of trying to figure out whether you've loaded it.
But if you want to do that:
The first practical issue is that you're always doing this:
blog = {};
...which means if there's already a blog global, you're wiping out its value and replacing it with an empty object. If you want to use an existing global's value or create a new one, do this:
var blog = blog || {};
That seems odd, but since repeated var declarations are fine (and don't change the variable), that will use an existing one's value, or if there isn't one (or its value is falsey) it will create a new one and initialize it with {}.
Then, the line
namesp.jsFile.firstLoad = false;
...looks for a property called jsFile on namesp and assumes it's not null or undefined. It doesn't look for a property using the jsFile argument's value.
To do that, use brackets notation:
namesp[jsFile].firstLoad = false;
Even then, though, you're assuming it's not null or undefined, but it may well be. You probably just wanted:
namesp[jsFile] = false;
Or possibly:
namesp[jsFile] = namesp[jsFile] ||{};
namesp[jsFile].firstLoad = false;
That said, it seems really odd to use blog.comments to track whether JavaScript files have been loaded. If the file may have already been loaded, just this will do it:
var fullCalendarLoaded;
if (fullCalendarLoaded) {
// It's already loaded
} else {
// It isn't, but it is now
fullCalendarLoaded = true;
// ...do your init...
}
Or if you have several of these and want to use a single global for it:
var loadedScripts = loadedScripts || {};
if (loadedScripts.fullCalendar) {
// Already loaded
} else {
// Not loaded yet
loadedScripts.fullCalendar = true;
// ...do init...
}
Or if using the filename is important:
var loadedScripts = loadedScripts || {};
function firstLoad(filename) {
if (loadedScripts[filename[) {
return false;
}
// Not loaded yet, remember we've loaded it now
loadedScripts[filename] = true;
return true;
}
Then:
if (firstLoad("fullcalendar.js")) {
// First load, do init...
}
It's fairly straightforward:
On your initial run, you define
blog = {};
blog.comments = blog.comments || {};
blog.comments.debugMode = false;
In theory, this means that on some loads, blog is:
var blog = {
comments: {
debugMode: false
}
}
You then pass blog.comments into your function isFirstLoad as the namesp parameter. In that function, you do the evaluation:
namesp.jsFile.firstLoad === undefined;
Well, you never defined the jsFile property of blog.comments. This means it is undefined. Trying to access the property firstLoad of an undefined variable will give you your error
Uncaught TypeError: Cannot read property 'firstLoad' of undefined
I'm writing a test where two browsers need to interact. The problem with simply forking the browser is that my page objects still reference the old browser. I didn't want to rewrite all of my PO's to take the browser as a parameter so I tried the first solution found in the link below where they overwrite the global variables with the new browser's version :
Multiple browsers and the Page Object pattern
However, changing the global variables doesn't seem to work as all the subsequent page object functions that I call are performed against the original browser instance. I have tried logging the window handler before and after the switch and they are indeed different which only baffles me further. Here's some of the code.
spec:
var MultiBrowserFunctions = require('../common/multiBrowserFunctions.js');
var HomePage = require('../home/home.po.js');
describe('blah', function(){
it('blah', function(){
MultiBrowserFunctions.openNewBrowser(true);
HomePage.initializePage();
});
});
MultiBrowserFunctions:
(function() {
var browserRegistry = [];
module.exports = {
openNewBrowser: function(isSameUrl){
if(typeof browserRegistry[0] == 'undefined'){
browserRegistry[0] = {
browser: browser,
element: element,
$: $,
$$: $$,
}
}
var tmp = browser.forkNewDriverInstance(isSameUrl);
var id = browserRegistry.length;
browserRegistry[id] = {
browser: tmp,
element: tmp.element,
$: tmp.$,
$$: tmp.$$,
}
switchToBrowserContext(id);
return id;
},
resetBrowserInstance : function(){
browserRegistry.splice(1,browserRegistry.length);
switchToBrowserContext(0);
}
}
function switchToBrowserContext(id){
console.log('---------------------------switching to browser: ' + id);
browser=browserRegistry[id].browser;
element=browserRegistry[id].element;
$=browserRegistry[id].$;
$$=browserRegistry[id].$$;
}
}());
My questions are:
(1) why doesn't this work?
(2) Is there some other solution that doesn't involve rewriting all of my po's?
What you can do is, save the browsers in different variables and then switch between them by overriding the globals via a utility or something.
describe('Switching browsers back and forth', function () {
var browserA, browserB;
it('Browser Switch', function () {
var browsers = {
a : browser,
b : browser.forkNewDriverInstance(true)
};
browserA = browsers.a;
browserB = browsers.b;
var browserAndElement = switchBrowser(browserB);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
var browserAndElement = switchBrowser(browserA);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
});
});
The switchBrowser() can look like following:
this.switchBrowser = function (currentBrowser) {
browser = currentBrowser;
element = currentBrowser.element;
return {
browser : browser,
element : element
}
}
In this way you don't have to rewrite your POs to take in the new globals.
Hope it helps!
Cheers
I have a single JS file that I use across the whole of my app. The top of it looks like this:
$(document).ready( function() {
if( $('#element-a').length ) {
var featureA = new ElementAFeature( $('#element-a') );
}
if( $('#element-b').length ) {
var featureB = new ElementBFeature( $('#element-b') );
}
// repeat ad nauseam for elements C thru Z etc etc
});
// actual objects and logic go here
It works, but it's sort of ugly. Short of running different scripts on different pages, is there any way of tidying this up?
In each page do something like this
window.MYAPP = window.MYAPP || {};
window.MYAPP.element = $("page-element");
window.MYAPP.feature = new ElementXFeature(window.MYAPP.element);
then modify your init script to
$(document).ready( function() {
var feature = window.MYAPP.feature;
//Use feature here.
});
If you are writing a lot of specific init code for each page you might wanna consider having both a global init method and defining a local one for each page and pass any context needed from the global init.
window.MYAPP.initMethod = function(context) {}
//in global init
if (typeof window.MYAPP.initMethod === "function") {
window.MYAPP.initMethod({ pageSpecificSetting : 0});
}
I'm implementing Stoyan Stefanov's javascript namespace function as I have been reading his very informative JavaScript Patterns book; in my web application but not sure if I'm using it the proper way
here is the funciton implementation i'm using on my web app on this page http://dalydd.com/projects/module_example/
var COOP = COOP || {};
COOP.namespace = function (ns_string) {
var parts = ns_string.split('.'),
parent = COOP,
i;
// strip redundant leading global
if (parts[0] === "COOP") {
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i += 1) {
// create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
};
COOP.namespace('sliderContainer')
COOP.sliderContainer = function () {
return slider = ($('#slider').length > 0) ? $('#slider') : $('#element_temp');
} // we need this at the beginning as others are dependent on it and call it initially
my goal is to check every new property of COOP to see if it exists before it's implemented --- so if I create a property of COOP called COOP.sliderContainer - I want to make sure COOP.sliderContainer does not exist already. when I use the namespace function it returns an object but COOP.sliderContainer is a function. I feel like I have to do an extra layer of abstraction in order to name this namespace function work properly like
var sliderContainer = COOP.namespace('sliderContainer');
sliderContainer.sliderContainer = function () {
return slider = ($('#slider').length > 0) ? $('#slider') : $('#element_temp');
}
this seems silly and redundant to me - is there a better way to do this?
any info is appreciated as always - the page has a direct link to the js file on it
namespace function is useful when create sub namespaces inside COOP, it will help to avoid multiple checkings. For example you want to create COOP.module.module1, you have to make 2 checks to see if module and module 1 are not defined or not.
However, in this case, sliderContainer is just a property of COOP. There's no need to use namespace. You just simply check it yourself:
if(COOP.sliderContainer === undefined){
// define it
}
EDIT
You can have a function handle that for you:
COOP.createProperty = function(name, prop){
if(COOP[name] === undefined){
COOP[name] = prop;
}
}
then
COOP.createProperty("sliderContainer", function(){
// do whatever you want
});