Generating the bootstrap carousel on my homepage

Using a little bit of JavaScript to generate the carousel, rather than hand-crafting the marup

Back in 2017 I wrote about bootstrapping a carousel onto my homepage (was it really that long ago!?) and ever since then I've been adding, removing and modifying items in the carousel by copying, pasting and editing chunks of HTML. Needless to say this is something that's been somewhat error prone, brittle and has made me quite reluctant to modify the carousel unless I absolutely had to. A little while ago I saw a post on meta.stackexchange.com that has a listing of all the data-dumps that are prepared from the Stack Exchange network, but what does this have to do with carousels? Well.... the list is generated from a JavaScript script which avoids the need to hand-crank the markup whenever a new data-dump is generated. This was a "light-bulb" moment, which is a bit embarassing for someone who's generally a massive "automate when you can" kinda person.

To do this I've created a simple JavaScript file in Visual Studio Code to which I do the following to generate the markup:

  1. Open it
  2. Hit F5 to start debugging
  3. Choose 'Node.js' when asked which environment I want to run the script in (I'm not sure if this is something that's present in VS Code by default or something that I've added at some point!)
  4. Copy the markup out of the Debug Console

It's quick and dirty, but it's a lot less painful than editing the markup by hand.

Describing the carousel entries

First up is the script that I use to describe the carousel entries. Each carousel entry consists of an image, a title and a description:

class CarouselItem
{
    constructor(imageSrc, postSrc, title, description, inverse)
    {
        this.imageSrc = imageSrc;
        this.postSrc = postSrc;
        this.title = title;
        this.description = description;
        this.inverse = inverse === undefined ? false : true;
    }
}

They also contain an inverse flag which I use to flip the colour of the text because some of the images lend themselves to having white text instead of black, for readability. There's not a lot else to say about this class structure, so next up is an example of a couple of populated items:

const carouselEntries = 
[
    new CarouselItem('/Media/Default/CarouselImages/bolognese_pasta_bake.jpg',
    '/recipes/lamb-hotpot', 'Bolognese Conchigli pasta bake',
    'The conchigli that I use for this pasta bake are quite thick, lending them an almost meaty texture that really gives the pasta bake some substance'),

    new CarouselItem('/Media/Default/CarouselImages/sea_bass_parcel_carousel_2.jpg',
    '/recipes/sea-bass-parcels-with-asian-flavours', 'Sea Bass parcels with asian flavours',
    'Sea Bass parcels with garlic, ginger, mirin and other goodies')
]

For the purposes of this post I've not listed each and every entry in the carousel as that would be very long, very boring and very repetitive. The only part of the CarouselItem class that's not used here is the inverse parameter to its constructor, because the majority of carousel items seem to look okay without inverting the colour I've opted to omit this when not required as it makes it more obvious when it is required.

Generating the carousel markup

Because I run this straight in Visual Studio Code and copy the output HTML, the execution harness that comes straight after the carousel entries is pretty simple:

const markup = generateMarkupForCarousel('carousel-example-generic');
console.log(markup);

This runs the generateMarkupForCarousel method, passing in the ID I want to use for the carousel and then sends the generated markup to the console. The code for this method, actually two methods as I have another method I call out to that generates the carousel indicators / slides separately from the container and navigation buttons for the carousel, does a lot of concatenation and looks like this:

function generateMarkupForCarousel(carouselId)
{
    const markupForSlidesAndIndicators = generateMarkupForIndicatorsAndSlides(carouselId);
    const finalCarousel = [];
    finalCarousel.push('<div class="carousel slide" id="' + carouselId + '" data-ride="carousel">');
    finalCarousel.push(markupForSlidesAndIndicators);
    // Controls: Previous
    finalCarousel.push('<a class="left carousel-control" role="button" href="#' + carouselId + '" data-slide="prev">');
    finalCarousel.push('<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>');
    finalCarousel.push('<span class="sr-only">Previous</span>');
    finalCarousel.push('</a>');
    // Controls: Next
    finalCarousel.push('<a class="right carousel-control" role="button" href="#' + carouselId + '" data-slide="next">');
    finalCarousel.push('<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>');
    finalCarousel.push('<span class="sr-only">Next</span>');
    finalCarousel.push('</a>');
    finalCarousel.push('</div>'); // class="carousel slide"

    return finalCarousel.join('\r\n');
}

function generateMarkupForIndicatorsAndSlides(carouselId)
{
    const indicators = [];
    const slides = [];
    // indicators / slides pre-amble
    indicators.push('<ol class="carousel-indicators">');
    slides.push('<div class="carousel-inner" role="listbox">');
    let counter = 0;
    for (const entry of carouselEntries)
    {
        const indicatorCssClass = counter == 0 ? 'class="active" ' : '';
        const slideItemCssClass = counter == 0 ? 'class="item active"' : 'class="item"';
        const titleStyle = entry.inverse ? ' style="color: white;"' : '';
        indicators.push('<li ' + indicatorCssClass + 'data-target="#' + carouselId + '" data-slide-to="' + counter + '"></li>');
        slides.push('<div ' + slideItemCssClass + '>');
        slides.push('<img alt="' + entry.title + '" src="' + entry.imageSrc + '" />');
        slides.push('<div class="carousel-caption">');
        slides.push('<h3><a href="' + entry.postSrc + '">' + entry.title + '</a></h3>');
        slides.push('<div' + titleStyle + '>' + entry.description + '</div>');
        slides.push('</div>'); // class="carousel-caption"
        slides.push('</div>'); // class="item"
        counter++;
    }
    // indicators / slides closing
    indicators.push('</ol>');
    slides.push('</div>');

    const generatedMarkup = indicators.join('\r\n') + '\r\n' + slides.join('\r\n');

    return generatedMarkup;
}

The code could probably be more concise, there's certainly some duplication in the generation of the Previous and Next controls that could be elided and probably a reduction in the amount of code overall.

What's next?

As this is "just" a chunk of JavaScript I could probably embed it in the page and have the carousel generated when the page loads. This would certainly help with page load time, as it looks a little like this at the moment:

Developer Tools showing the sequence of images that end up being loaded during page load for my homepage

Whilst all the images are being loaded in parallel, loading is being triggered before the rest of the page assets, including jQuery which sits a little bit further down. I should probably also look at optimising the images as that's 5.1MB of images being downloaded there which is a little sub-optimal.

About Rob

I've been interested in computing since the day my Dad purchased his first business PC (an Amstrad PC 1640 for anyone interested) which introduced me to MS-DOS batch programming and BASIC.

My skillset has matured somewhat since then, which you'll probably see from the posts here. You can read a bit more about me on the about page of the site, or check out some of the other posts on my areas of interest.

No Comments

Add a Comment