Combine approaches for easy, lightweight JavaScript user information detection

Basic browser/user detection can be important for providing an enhanced user experience. For example, making a best-guess detection of the user’s IP address (subject to the usual caveats about VPN use) can help in pre-selecting a user’s country of origin, enabling faster form fill-outs. The user’s preferred language, especially on first access of a site, is obviously of huge importance; detecting it early with a non-intrusive script can help avoid clunkier approaches such as passing out language-differentiated URLs to globalized versions of your site, which may be passed between users with unintended consequences.

Especially for a site which already makes use of JavaScript, an unobtrusive script can make a difference. And while there are fairly robust libraries such as platform.js, they often do not address all concerns of detecting language, geoIP, and browser. Adding extra dependencies to a site, in terms of maintenance burden and JS payload, is desirable to avoid when possible.

It’s easy enough to create a tiny script which merges freely available geoIP with basic browser/language detection, as shown below. There are many free geoIP web services available these days; a first implementation can simply rely on one, perhaps adding a fallback as necessary, though eventually a more robust solution may become necessary. (Edit: The freegeoip.net service will become paid-only in July 2018.)

The simple demo code, with browser and OS versions not yet filled in, appears below, and may be run at JSFiddle . This could, for example, be passed as a JSON bundle in a call to a web service, or submitted with a page. While the code may be reused freely, as it essentially cobbles together multiple simple well-known methods into an easy-to-use package, the emphasis is on the approach: a lightweight script that avoids heavy dependencies and still captures useful information about a user. As always, use such information responsibly, especially keeping in mind GDPR regulations which will have taken full effect by May 2018.

function UserInfo() {
  var self = this;
  this.language = ((navigator.languages ? navigator.languages[0] : (navigator.language || navigator.userLanguage)) + '').substring(0, 2);
  this.browserName = '';
  this.browserVersion = '';
  this.osName = '';
  this.osVersion = '';
  this.country = '';
  this.ipAddress = '';
  this.city = '';
  this.region = '';
  this.postalCode = '';
  
  if (navigator && navigator.userAgent) {
    var ua = (navigator.userAgent + '').toLowerCase();
    var platform = ua;
    
    var i = platform.indexOf('(');
    if (i > -1)
      platform = platform.substring(i + 1);

    i = platform.indexOf(')');
    if (i > -1)
      platform = platform.substring(0, i);
      
    if (platform.indexOf('windows') > -1)
      this.osName = 'Windows';
    else if (platform.indexOf('android') > -1)
      this.osName = 'Android';
    else if (platform.indexOf('macintosh') > -1 || platform.indexOf('macos') > -1)
      this.osName = 'macOS';
    else if (platform.indexOf('iphone') > -1 || platform.indexOf('ipad') > -1)
      this.osName = 'iOS';
  }

  if (!!window.chrome && !!window.chrome.webstore)
    this.browserName = 'Chrome';
  else if (typeof InstallTrigger !== 'undefined')
    this.browserName = 'Firefox';
  else if (/*@cc_on!@*/false || !!document.documentMode)
    this.browserName = 'IE';
  else if (/constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification))
    this.browserName = 'Safari';
  else if ((!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0)
    this.browserName = 'Opera';
  else if (!!window.StyleMedia)
    this.browserName = 'Edge';
  
try {
    $.getJSON('//freegeoip.net/json/?callback=?', function(data) {
      self.ipAddress = data.ip + '';
      self.city = data.city + '';
      self.region = data.region_code + '';
      self.country = data.country_code + '';      
      self.postalCode = data.zip_code + '';
    });
} catch (err) {}

  this.ids = []; 
  
  this.addID = function(sn, st, idt, i) {
    var newID = {};
    newID.systemName = sn + '';
    newID.systemType = st + '';
    newID.idType = idt + '';
    newID.id = i + '';      
    this.ids.push(newID);
  };  
}

 

The minified version is relatively compact, at 1560 characters:

function UserInfo(){var i=this;if(this.language=((navigator.languages?navigator.languages[0]:navigator.language||navigator.userLanguage)+"").substring(0,2),this.browserName="",this.browserVersion="",this.osName="",this.osVersion="",this.country="",this.ipAddress="",this.city="",this.region="",this.postalCode="",navigator&&navigator.userAgent){var e=(navigator.userAgent+"").toLowerCase(),o=e.indexOf("(");o>-1&&(e=e.substring(o+1)),(o=e.indexOf(")"))>-1&&(e=e.substring(0,o)),e.indexOf("windows")>-1?this.osName="Windows":e.indexOf("anddroid")>-1?this.osName="Android":e.indexOf("macintosh")>-1||e.indexOf("macos")>-1?this.osName="macOS":(e.indexOf("iphone")>-1||e.indexOf("ipad")>-1)&&(this.osName="iOS")}window.chrome&&window.chrome.webstore?this.browserName="Chrome":"undefined"!=typeof InstallTrigger?this.browserName="Firefox":document.documentMode?this.browserName="IE":/constructor/i.test(window.HTMLElement)||"[object SafariRemoteNotification]"===(!window.safari||safari.pushNotification).toString()?this.browserName="Safari":window.opr&&opr.addons||window.opera||navigator.userAgent.indexOf(" OPR/")>=0?this.browserName="Opera":window.StyleMedia?this.browserName="Edge":(isChrome||isOpera)&&window.CSS&&(this.browserName="Blink");try{$.getJSON("//freegeoip.net/json/?callback=?",function(e){i.ipAddress=e.ip+"",i.city=e.city+"",i.region=e.region_code+"",i.country=e.country_code+"",i.postalCode=e.zip_code+""})}catch(i){}this.ids=[],this.addID=function(i,e,o,s){var t={};t.systemName=i+"",t.systemType=e+"",t.idType=o+"",t.id=s+"",this.ids.push(t)}}

 

Advertisements

JavaScript: to prototype or not?

A recurring topic of some discussion among JavaScript users is whether to use prototype or instance/constructor-inner methods when defining JavaScript objects.

Prototype methods can be incredibly useful, but are in essence much like C# extension methods (which are incredibly useful for similar reasons). The primary use of both is to add functionality to a class/object type over which one does not have control. In this way, both C# extension methods and JavaScript prototype methods can even add functionality to foundational platform types in each language or environment. (Proponents of using prototype methods for everything sometimes point out that there is a small memory-use difference between prototype and non-prototype functions, but that difference is completely negligible for client-side development.)

Methods defined inside an object constructor, directly on “this”, have a major advantage in correct object-oriented design, however. That’s because methods inside the constructor can also access private properties/variables defined inside the constructor. Prototype methods cannot do this, as this example clearly demonstrates. A minor additional benefit is that instance methods defined this way are contained inside the constructor, a more object-oriented style than the extension-method feel of prototype methods.

This difference means that constructor-inner methods are the only correct choice for constructing object-oriented JavaScript. The alternate, using prototype methods only, would be akin to designing a C# or Java class with no private fields or properties, a clear violation of the encapsulation principle.

It’s clear that at least for client-side development, prototype methods may be useful to decorate a pre-existing API–but when designing one’s own object-oriented JavaScript, there is only one appropriate choice.

Scale text with a minimum size using pure CSS

Creating a responsive design is best done without unnecessary reliance on toolkits, frameworks and the like, especially where basic formatting can be controlled by CSS instead of JavaScript. The set of tools in CSS3 is of course limited compared to that in JavaScript, but one aspect of responsive design–scaling text–can often by performed satisfactorily in pure CSS.

One part of the puzzle is to scale text smoothly according to the size of the viewport. For this, the oft-overlooked vw unit can work wonders. However, without a min-font-size property available in CSS3, simply scaling to zero wouldn’t work in a wide range of situations, and a pure percentage-based scaling without a foundation might also not scale appropriately. In general, for usability purposes it’s best to use the smallest size of text that satisfies both the readability requirement and relative size requirement at the smallest viewport size, and scale up from there.

Luckily, by using the CSS calc() function, we can supply a floor value for font sizes and scale up from there. This provides a functional equivalent for the missing min-font-size, since the vw-based component of the calculated size can safely go to zero, approaching the limit of the constant base size. Using this approach, one might create some classes to apply standard font sizes as in the following, apply them to heading tags, etc.

Try it for yourself! Note that the linked demo includes a moderate amount of viewport height in the calculation as well, to theoretically better fill a proposed space, and also includes the viewport height in scaling the line height for each font size (again above a safe minimum).

.fontSizeXXSmallScaled { font-size:calc(.8em + 1vw); }
.fontSizeXSmallScaled { font-size:calc(.8em + 1.2vw); }
.fontSizeSmallScaled { font-size:calc(.9em + 1.4vw); }
.fontSizeMediumScaled { font-size:calc(1em + 1.5vw); }
.fontSizeLargeScaled { font-size:calc(1.25em + 1.6vw); }
.fontSizeXLargeScaled { font-size:calc(1.5em + 1.7vw); }
.fontSizeXXLargeScaled { font-size:calc(1.75em + 1.8vw); }

Dynamically resize elements sans JavaScript with the help of clutter-free CSS

I’m a big fan of code, including CSS, that’s as easy to use as possible. I’m not a fan of using certain large frameworks such as Bootstrap just to get responsive design onto a site, when something much smaller and simpler will often do. With a few CSS tricks, it’s possible to create CSS containers that will behave responsibly at your chosen viewport break-points, but that can be stripped down to a single class added to a container in many cases.

To that end, observe the following example, which applies table-style formatting at a large/desktop screen size wraps items into a grid-style layout at a medium size, with configurable options for numbers of columns at different numbers of child items; and finally collapses to an item width of 100%. You may also try it for yourself.

* { box-sizing: border-box }

.columnContainer:after {
   content: " ";
   visibility: hidden;
   display: block;
   height: 0;
   clear: both;
}

@media all and ( min-width: 768px ) {

  .columnContainer { 
    display: table;
    width: 100%;
  }
  .columnContainer > div { 
    display: table-cell;
  }

}

@media all and ( max-width: 767px ) and ( min-width: 481px ) {

  .columnContainer { 
     width: 100%;
  }

  .columnContainer > div {
    float: left;
    width: 100%;
  }

  .columnContainer > div:first-child:nth-last-child(2), .columnContainer > div:first-child:nth-last-child(2) ~ div {  width: 50%;  } 

  .columnContainer > div:first-child:nth-last-child(3), .columnContainer > div:first-child:nth-last-child(3) ~ div {  width: 33.3333%;  } 

  .columnContainer > div:first-child:nth-last-child(4), .columnContainer > div:first-child:nth-last-child(4) ~ div {  width: 50%;  } 
  .columnContainer > div:first-child:nth-last-child(4) ~ div:nth-child(2n+1) { clear: both !important; } 

  .columnContainer > div:first-child:nth-last-child(5), .columnContainer > div:first-child:nth-last-child(5) ~ div {  width: 20%; } 

  .columnContainer > div:first-child:nth-last-child(6), .columnContainer > div:first-child:nth-last-child(6) ~ div {  width: 33.3333%;  } 
  .columnContainer > div:first-child:nth-last-child(6) ~ div:nth-child(3n+1) { clear: both; } 
  
  .columnContainer > div:first-child:nth-last-child(7), .columnContainer > div:first-child:nth-last-child(7) ~ div {  width: 14.2857%; } 

  .columnContainer > div:first-child:nth-last-child(8), .columnContainer > div:first-child:nth-last-child(8) ~ div {  width: 25%;  } 
  .columnContainer > div:first-child:nth-last-child(8) ~ div:nth-child(4n+1) { clear: both !important; } 

  .columnContainer > div:first-child:nth-last-child(9), .columnContainer > div:first-child:nth-last-child(9) ~ div {  width: 33%;  } 
  .columnContainer > div:first-child:nth-last-child(9) ~ div:nth-child(3n+1) { clear: both !important; } 
  
  .columnContainer > div:first-child:nth-last-child(10), .columnContainer > div:first-child:nth-last-child(10) ~ div {  width: 20%;  } 
  .columnContainer > div:first-child:nth-last-child(10) ~ div:nth-child(5n+1) { clear: both !important; } 

  .columnContainer > div:first-child:nth-last-child(11), .columnContainer > div:first-child:nth-last-child(11) ~ div {  width: 9.0909%; } 

  .columnContainer > div:first-child:nth-last-child(12), .columnContainer > div:first-child:nth-last-child(12) ~ div {  width: 25%;  } 
  .columnContainer > div:first-child:nth-last-child(12) ~ div:nth-child(4n+1) { clear: both !important; } 

}

@media all and ( max-width: 480px ) {
  .columnContainer > div { width: 100% }
}

This implementation depends on some CSS3 features working in tandem. At the medium viewport size, by combining the :first-child and :nth-last-child(n) (with following siblings) selectors in tandem, the widths and wrapping for child items can be set specifically based on the number of child items. To create a new row, the nth child is then selected for clearing. An implementation without using floats is also possible; the main technique here is the selection of different layout techniques based on the number of children.

Once this has been implemented, adding a single class, in this case “columnContainer”, on a parent element will hook all this up so it just works. It would also be possible to combine this technique, of course, with named custom classes that applied different widths to columns and the like.

Applying shadow overlays with pure CSS

Shadow or darkening overlays can be useful for providing contrast between a container’s background and content, as well as providing an easy way to differentiate presentation for button mouseovers and similar situations. Older methods of providing such useful inner shadows relied on using an extra div or other container, to which one would apply a color and opacity. However, there’s a better way.

The desired effect can be achieved using two related tricks: the now well-supported opacity feature of rgba colors, as well as the “inset” property of the CSS3 box shadow which applies the box shadow inside an element. The trick is to set the shadow width to greater than half the size of the element (any greater size will do), to ensure that the shadow covers the entire element. Using this approach, one might specify the following to achieve a 50% shading:

box-shadow: rgba(0, 0, 0, .5) 0 0 0 1000000px inset;

Try it for yourself.

Dynamically adding JavaScript and CSS using a resource-tracking object type

This is a quick demonstration of a few techniques in basic JavaScript: creating a custom JavaScript object type with properties and methods; dynamically adding both JavaScript and CSS references in a cross-browser-safe way; adding JavaScript async; and adding inline JavaScript and CSS. The resource loader tracks added resources, to ensure that they are loaded only once. Try it for yourself (open your browser’s developer tools to see logging output).

// A JavaScript type which manages references to scripts and stylesheets
function ResourceManager() {
  var addedReferences = {};
  var addedResources = {};
  
  this.isLoggingEnabled = false;
  
  this.referenceExists = function(url) {
  	if (url in addedReferences) {
    	this.log('Resource ' + url + ' already added');
      return true;
    }
    else return false;
  }
  
  this.resourceExists = function(text) {
  	if (text in addedResources) {
    	this.log('Text resource already added');
      return true;
    }
    else return false;
  }
  
  this.addScriptReference = function(url, addAsync, addDeferred) {
    if (typeof(url) == 'undefined' || this.referenceExists(url)) return;
    this.log('Adding script reference: ' + url + (addAsync ? ' [async]' : '') + (addAsync ? ' [deferred]' : ''));
        
    var script = document.createElement('script');
    script.src = url;
    if (addAsync) script.async = true;
    if (addDeferred) script.defer = true;
    document.head.appendChild(script); 
    
    addedReferences[url] = url;
  };
  
  this.addScript = function(text) {
    if (typeof(text) == 'undefined' || this.resourceExists(text)) return;
    this.log('Adding script');
      
    var script = document.createElement('script');
    script.appendChild(document.createTextNode(text));
    document.head.appendChild(script); 
    
    addedResources[text] = text;
  };
  
  this.addStyleSheetReference = function(url) {
    if (typeof(url) == 'undefined' || this.referenceExists(url)) return;
    this.log('Adding script reference: ' + url + (addAsync ? ' [async]' : '') + (addAsync ? ' [deferred]' : ''));
    
    var ss = document.createElement('link');
    ss.rel = 'stylesheet';
    ss.type = 'text/css';
    ss.href = url;
    head.appendChild(ss);
    
    addedReferences[url] = url;
  };  
    
  this.addStyleSheet = function(text) {
    if (typeof(text) == 'undefined' || this.resourceExists(text)) return;
    this.log('Adding style sheet');
    
    var ss = document.createElement('style');
    ss.type = 'text/css';
    
    if (ss.styleSheet){
      ss.styleSheet.cssText = text;
    } else {
      ss.appendChild(document.createTextNode(text));
    }
      
    document.head.appendChild(ss);  
      
    addedResources[text] = text;
  };    
    
  this.log = function(message) { if (this.isLoggingEnabled) console.log(message); };
}

Selecting unthemed HTML descendant elements using CSS :not()

In my recent work, I’ve been constructing a CSS framework that needs to support multiple themes: the ability to override many aspects of display formatting by applying a CSS class to a parent element, affecting all descendants. However, in order to make this work properly in actual practice, it’s may be desirable to apply default theming rules to even children without an ancestor theme-tagged element. (In my situation, this requirement applies because the mentioned CSS framework will be used to support microsites, which should be presented with a “good” look and feel even if the implementation of the site has been somewhat sloppy.)

The CSS :not() selector works well for this, as implemented in CSS3:

:not([class*=Theme]) * {
  // Sample selecting all unthemed elements, where themes are applied with *Theme class names on ancestors
}

This can easily, of course, be applied with different descendant selectors. Try the example.

Safely navigating object hierarchies in JavaScript using prototype methods

New: Dynamically evaluate C# expressions and execute C# scripts with a single statement, from anywhere in a .NET application. Click here for more info.

Anyone who’s dealt with a deeply nested set of properties in JavaScript, whether through use of an extensive third-party JavaScript API or a custom library, has likely run into the problem of safely accessing such structures. One can of course hand-roll many ugly, hard-to-read statements like the following, adding on to the maintenance burden of the code (splitting lines as necessary for length, of course):

if (a && a.b && a.b.c && a.b.c.d && a.b.c.d.e) { doSomethingWith(a.b.c.d.e); }

The main alternative to this approach is to make such accesses within a try-catch block, but this is generally slower by at least a couple of orders of magnitude when exceptions are thrown, so not always useful in tight loops and other performance-sensitive situations. It’s also arguably an abuse of the try/catch mechanism.

Luckily, a less unsavory solution with fairly good performance can be adopted using JavaScript prototype methods. Here’s a reference implementation, and you can also try it for yourself (with timings):

// Indicates whether an object has the indicated nested subproperty, which may be specified with chained dot notation 
// or as separate string arguments.
Object.prototype.hasSubproperty = function() {
	if (arguments.length == 0 || typeof(arguments[0]) != 'string') return false;  
  var properties = arguments[0].indexOf('.') > -1 ? arguments[0].split('.') : arguments;    
  var current = this;
  for(var x = 0; x  -1 ? arguments[0].split('.') : arguments;    
  var current = this;
  for(var x = 0; x < properties.length; x++) {
  	current = current[properties[x]];
    if ((typeof current) == 'undefined') return undefined;
  }  
  return current;
};

// Gets the indicated nested subproperty, which may be specified with chained dot notation or as separate arguments.
// If the specified subproperty (or any intervening object in the hierarchy) is not found, returns undefined.
Object.prototype.getSubproperty = function() {
	if (arguments.length == 0 || typeof(arguments[0]) != 'string') return false;  
  var properties = arguments[0].indexOf('.') > -1 ? arguments[0].split('.') : arguments;    
  var current = this;
  for(var x = 0; x < properties.length; x++) {
  	current = current[properties[x]];
    if ((typeof current) == 'undefined') return undefined;
  }  
  return current;
};

// Sets the indicated nested subproperty, which may be specified with chained dot notation or as separate arguments.
// If any intervening object in the hierarchy is not found, returns false, otherwise sets the value and returns true.
Object.prototype.setSubproperty = function() {
	if (arguments.length  -1 ? arguments[0].split('.') : Array.prototype.slice.call(arguments, 0, arguments.length - 1);    
  var parent, current = this;
  for(var x = 0; x < properties.length - 1; x++) {
  	current = current[properties[x]];
    if ((typeof current) == 'undefined') return false;
  }  
  current[properties[properties.length - 1]] = arguments[arguments.length - 1];
  return true;
};

Some observations: if you run the timings, you’ll note that the try-catch method is still quite fast when exceptions are not thrown, indicating that try-catch might be workable when exceptions are expected to be truly… exceptional. Still, in any but extraordinary conditions, the performance of the prototype-method approach should be quite fast enough, avoids worst-case performance, and is cleanest overall.

Emulating the Java BorderLayout in CSS

In putting together a CSS framework, I wanted to duplicate the functionality of the Java BorderLayout. There’s a fair amount of (generally partially complete) info on the web about how to do this, with many of the methods having one drawback or another, e.g. needing to put the center/body child container after both sidebars if using a float-based approach. As it turns out, the CSS table layout, introduced way back in CSS 2, makes it a snap. Try the JSFiddle for yourself.

The bone-simple CSS, with compass-point naming eschewed in favor of more standard CSS names:

.borderLayout {
  display: table;  
  width: 100%
}

.borderLayout .top {
	display: table-row;
}

.borderLayout .left {
    display: table-cell;
    vertical-align: middle;
    width: 10%;
  }

.borderLayout .center {
    display: table-cell;
    vertical-align: middle;
}

.borderLayout .right {
    display: table-cell;
    vertical-align: middle;
    width: 10%;
  }
  
.borderLayout .bottom {
	display: table-row;	
}

The layout is then quite simple to use, the main charm of BorderLayout, and can be safely nested as well. Try the JSFiddle for yourself.

A tiny, cross-browser script to intercept third-party JavaScript injection via document.write()

Many of us have encountered third-party scripts which use document.write(), particularly for script injection. Certain older analytics scripts use this approach ({cough} {cough} {Omniture!}). If you’re stuck maintaining an older site with a projected end-of-life, you may not have the time to upgrade those old bad scripts away, especially if they’re hosted externally and dynamically generated.

Unfortunately, many of those same scripts were written without async loading in mind, and by render blocking, blocking on the DOM or CSSOM, etc. can greatly decrease performance of your site; a second-plus of unnecessary page-load time is not uncommon. The problem is so pervasive that Google finally put its metaphorical foot down, ever so lightly, by decreasing support for third-party document.write() statements; as an immediate effect of this change, certain scripts may not even be loaded on mobile, defeating the purpose of loading the scripts in the first place.

This can leave us in a pickle: how to intercept document.write() calls by these third-party scripts without disabling analytics or other functionality for which they were included in the first place, ideally while decreasing page-load times through the use of async/defer loading?

This can be done by replacing document.write with a function that intercepts attempts to inject scripts in this way, and instead adds them dynamically to the DOM, while passing through all other non-script parameters to the original document.write() function:



// 1. Moves the document.write() method, for safekeeping
document.writeText = document.write;

// 2. Assigns a new function to document.write(), to serve as a middleman
document.write = function(parameter) {
    if (!parameter) return;
	
	var scriptPattern = /<script.*?src=['|"](.*?)['|"]/;
	if (scriptPattern.test(parameter)) {
		// Get the value of the src attribute
		var srcAttribute = scriptPattern.exec(parameter)[1]; 
		// Add the script to the DOM 
		var script = document.createElement('script');
		script.src = srcAttribute;
		document.head.appendChild(script); 
	}
	else {
		document.writeText(parameter);
	}	
};

The last piece of the puzzle, if you’re also interested in decreasing page-load time with these older libraries, is to load them asynchronously. Options here would include the following:

1. Use of libraries such as postscribe , jQuery etc., to help with async-loading external scripts in general.

2. Using the async and/or defer properties directly on the script tag including a legacy library (I’m unaware as of this writing of a way to do this cleanly with cross-browser support using the DOM and avoiding use of innerHTML). This may be recommended against, as discussed further below after #3.

3. Especially in the case of a legacy script which is relatively lightweight itself but uses document.write() extensively to inject script tags, you could allow the first script to load synchronously, but use the technique above to write the script tags yourself, adding async and defer as desired. This may significantly decrease page load time, because the bulk of render-blocking script loads are avoided. To use this technique, modify the code sample to use document.writeText()–or whatever you’ve called your placeholder for the old document.write()–to emit an HTML script tag, using the regex-parsed src value and adding adding “async defer” directly.

Adobe in particular has consistently recommended against loading DTM libraries async, and this legacy can be seen stretching back to the days of the fore-mentioned Omniture, which unfortunately does use document.write() extensively to load other scripts from dynamically generated code. Thus a working approach to get at least some such scripts loaded asynchronously, while avoiding page flicker, can be to leave the script-loading legacy script in place as-is, but intercept its calls to document.write().