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.
Related
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.
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've wrote a small example for readability.. I'm trying to get my head around proper js app structure.
I'm new to writing larger js apps. Right now, I've got a constructor, and a whole bunch of prototype functions. I always thought you're NOT supposed to call (or return) from one function to another. But now, at the bottom of my app, I'm instantiating my constructor, then having to call a bunch of functions, as well as build in conditional statements to handle the execution, which seems totally wrong.
This is the idea I've been doing:
function TodaysFood(b, l)
{
this.breakfast = b;
this.lunch = l;
}
TodaysFood.prototype.firstMeal = function()
{
return console.log(this.breakfast);
}
TodaysFood.prototype.secondMeal = function()
{
return console.log(this.lunch);
}
var app = new TodaysFood("eggs", "sandwich");
app.firstMeal();
app.secondMeal();
I'm wondering if this function "linking" is proper?
function TodaysFood(b, l)
{
this.breakfast = b;
this.lunch = l;
}
TodaysFood.prototype.firstMeal = function()
{
return this.secondMeal(this.breakfast);
}
TodaysFood.prototype.secondMeal = function(firstMeal)
{
var twoMeals = [firstMeal, this.lunch];
return this.whatIAte(twoMeals);
}
TodaysFood.prototype.whatIAte = function(twoMeals)
{
return console.log(twoMeals);
}
var app = new TodaysFood("eggs", "sandwich");
app.firstMeal();
Stupid example, but I'm trying to understand how an app should flow. Should I be able to write my whole app in separate, but linked functions, then just kick the whole thing off by instantiating the constructor, and maybe calling one function. Or is the first example more correct -- writing independent functions, then handling the interaction between them after you've instantiate the constructor?
Thanks for any help.
You may want to make it modular, Ala Node.js or within the browser using RequireJS
Here is a slight variation of the second example you could consider, view fiddle
var TodaysFood = function (b, l) {
var self = this;
this.breakfast = b;
this.lunch = l;
this.firstMeal = function () {
console.log(this.breakfast);
return self;
};
this.secondMeal = function () {
console.log(this.lunch);
return self;
}
this.allMeals = function () {
return this.firstMeal().secondMeal();
};
}
var food = new TodaysFood('eggs', 'sandwich');
food.firstMeal().secondMeal().allMeals();
If you plan to use node.js or RequireJS then the above could be modularized by replacing the last two test lines of code with,
module.exports = TodaysFood;
If this is made modular then you would remove the constructor var TodaysFood = function(b, l) { ... and instead accept arguments for b & l within your individual methods like firstMeal & secondMeal. This would make it static and prevent collisions with the constructor values.
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
});