My solution for a dynamically updating image randomizer in JavaScript
My latest focus is mastering web development, specifically ASP.Net MVC with Bootstrap and jQuery. There’s a huge amount of information to learn but none of it is particularly difficult. About the most foreign part for me has been dealing with JavaScript scope and context.
Example. I have a single page application (SPA) that is going to host an image. Every time the page updates I want to display a different image from a list at random. It’s easy to find examples on how to do this. However, the examples only work on a full page load or only for one location. Since this is a SPA there’s rarely a page load (I’m extensively using AJAX against my Web API) and I plan on more than one image list.
Here’s my solution. This was initially based on the “Chicken Dinner” plugin from http://chickendinner.sosweetcreative.com/
ImageRandomizer = { // Define a defaults object defaults: { altTag: ["alt tag"], fadeInTime: 1000, imageList: ['image1.jpg'], cssBG: false }, // Define the Init function Init: function(optionsIn){ // Declare options in the local scope. // Note: this requires a polyfill for older browsers. var options = Object.assign({}, this.defaults, optionsIn); $(document).on("spa:refresh", function(event, xhr, settings) { // We reference the options var in the closure. var rand = Math.floor(Math.random() * options.imageList.length); var filePath = options.imagePath + options.imageList[rand]; var altTag = options.altTag[0]; if (options.altTag.length > rand) { altTag = options.altTag[rand]; } var element = $(options.selector); $(element).css('display', 'none').fadeIn(options.fadeInTime); if (options.cssBG == 'true') { $(element).css('background-image', 'url(' + filePath + ')'); } else { $(element).attr({ src: filePath, alt: altTag }); } }); } };
A lot of stuff is happening here.
In JavaScript pretty much everything is an object except intrinsics like numbers and strings. You can treat each object as a dictionary. So I start off defining ImageRandomizer as an object with a defaults object that defines some fields and initialize them to default values.
I then add a new function object named Init to ImageRandomizer. Init is kind of like a member function in that it has access to the this object which has a defaults field.
Inside of Init I reference fields on defaults and optionsIn to properly initialize the locally declared (scoped) options variable. When a function is invoked its operating context is determined by where it was DEFINED – the context is instantiated from the definition scope. So when I call ImageRandomizer.Init elsewhere in my code the context picked up will be the local block defined above. And that local block will have access to options. The context is actually a chain of context called a closure. When you reference a variable it walks the closure chain to find the first one in scope.
A quick example.
function howmany(val){ var lights = val; return function() { return lights; }; } var Picard = howmany(4); var lights = 5; var x = Picard();
The anonymous function returned from howmany has a lights variable in scope one level up. When I save that function object reference in Picard I prevent that closure from being freed. After I declare a new lights variable in the global scope and invoke Picard the new variable is ignored and the original closure is used by the nested function and a value of 4 is returned to x. The anonymous function is the ONLY way to access that nested lights variable in the closure. JavaScript isn’t an OO language, but this is one way you can do encapsulation.
So when I make the following call in my initialization code it dynamically creates a new context for the function and that context is initialized based off the parameters I passed in.
ImageRandomizer.Init( { selector: '.firstImage', imagePath: '/Content/images/Media/', imageList: firstImageList });
Now if all I did was initialize options and returned then the context for this call would also be freed as it’s refcount went to zero. But I also add an event handler. Since the context is still being referenced within that event handler it won’t be deleted.
So when I make a second Init call a second context is created and a second anonymous event handler function is created and registered that uses that second context.
ImageRandomizer.Init( { selector: '.secondImage', imagePath: '/Content/images/Media/', imageList: secondImageList });
A new event listener is declared with this code:
$(document).on( "spa:refresh", function() { // We reference the options var in the closure. var rand = Math.floor(Math.random() * options.imageList.length); ... });
The second parameter to the on function is a new anonymous function. It references the options variable in the surrounding scope, meaning it keeps a reference to the current closure. This code is adding the function to the document object and telling it to fire every time the “spa:refresh” event fires. (The $ in JavaScript refers to the jQuery library which simplifies common JS tasks.)
The HTML for this is very simple:
<div> <img class="randomImageLeft" src="#" /> </div> <div> <img class="randomImageRight" src="#" /> </div>
And when I want to load new images I simply fire my new custom event like so (in this case it’s after a successful AJAX call):
$(document).trigger("spa:refresh");
If you’re not at least a bit confused by all this you’re probably already a Javascript programmer. This stuff is very foreign for traditional procedural/imperative programming languages like C++ and C#. C# didn’t get lambda expressions until years after launch (C# 3.0 / 2008?), and even then it doesn’t have closures, just local scope.
I didn’t expect to like Javascript but once you get the hang of it it’s a very powerful language. Python is another language I’m learning but that’s on the back burner for now. I recommend checking it out as well.
Cheers.