4
\$\begingroup\$

I've created a custom slide show container to use with Jekyll. This is the platform used by Github pages. It works by converting plain text Markdown to HTML.

I wanted to be able to embed the slide shows in the Markdown as inline HTML, so the following is a usage example:

some_file.markdown:

... text ...

<div class="slideshow-container">
    <div class="slideshow-img-list">
        <img src="path/img-a.png">
        <img src="path/img-b.png">
        <img src="path/img-c.png">
    </div>
</div>

... more text ...

A JavaScript+jQuery function then processes elements with class slideshow-container when the page finishes loading to expand the above into:

<div class="slideshow-container">
    <div class="slideshow-img-list">
        <img src="path/img-a.png" id="slideshow-img-0">
        <img src="path/img-b.png" id="slideshow-img-1">
        <img src="path/img-c.png" id="slideshow-img-2">
    </div>
    <div class="slideshow-buttons">
        <a id="slideshow-btn-0" href="javascript:;" class="slideshow-active-button"></a>
        <a id="slideshow-btn-1" href="javascript:;"></a>
        <a id="slideshow-btn-2" href="javascript:;"></a>
    </div>
</div>

The purpose behind the dynamic processing is to reduce the amount of boilerplate HTML I have to write in the markdown whenever I wish to specify a list of images for a slide show.

Following are the function that does the transformation, plus the styles I'm using and a sample showing the result.

function setUpSlideShowContainers() {

    /* The id counters are used to attribute sequential ids to each
     * <img>/<a> inside the slide-show containers of a page. */
    var globalImageId         = 0;
    var globalButtonId        = 0;
    var slideFadeMilliseconds = 300;

    function activeButton(button) {
        button.className = "slideshow-active-button";
    }

    function deactivateButtons(buttonsArray) {
        for (var i = 0; i < buttonsArray.length; ++i) {
            buttonsArray[i].className = "";
        }
    }

    $(".slideshow-container").each(function(index, slideShowContainer) {
        if (!slideShowContainer) {
            throw "Null 'slideShowContainer'!";
        }

        var imgContainer = slideShowContainer.firstElementChild;
        if (!imgContainer || imgContainer.className != "slideshow-img-list") {
            throw "Missing 'slideshow-img-list' div!";
        }

        /* Append this property to the container so that we can keep
         * track of the currently selected image in the slide-show. */
        slideShowContainer.currentSlideImgId = globalImageId;

        var i;
        var imageCount = 0;
        var imageIds   = [];
        var childNodes = imgContainer.childNodes;

        for (i = 0; i < childNodes.length; ++i) {
            /* Might have text nodes mixed between the images. */
            if (childNodes[i].nodeName.toUpperCase() != "IMG") {
                continue;
            }

            /* Give it a sequential name. */
            childNodes[i].id = "slideshow-img-" + globalImageId;
            imageIds.push(globalImageId);

            /* Fade all the images except the fist one,
             * so that our transition works properly. */
            if (imageCount != 0) {
                $("#" + childNodes[i].id).fadeOut(0);
            }

            ++imageCount;
            ++globalImageId;
        }

        var allButtons             = [];
        var buttonsContainer       = document.createElement("DIV");
        buttonsContainer.className = "slideshow-buttons";

        for (i = 0; i < imageCount; ++i) {
            var anchorButton     = document.createElement("A");
            anchorButton.id      = "slideshow-btn-" + globalButtonId;
            anchorButton.href    = "javascript:;";
            anchorButton.imageId = imageIds[i];

            /* First button/img in a slide-show is the default active one. */
            if (i == 0) {
                anchorButton.className = "slideshow-active-button";
            }

            anchorButton.addEventListener("click", function(event) {
                var slideShow     = slideShowContainer
                var otherButtons  = allButtons;
                var clickedButton = event.target;

                if (clickedButton.imageId == slideShow.currentSlideImgId) {
                    return; /* Clicking the currently active button; Do nothing. */
                }

                var idCurrent = "#slideshow-img-" + slideShow.currentSlideImgId;
                var idClicked = "#slideshow-img-" + clickedButton.imageId;

                /* Fade-out the current and fade-in the next one
                 * once the fade-out effect is finished. */
                $(idCurrent).fadeOut(slideFadeMilliseconds,
                    function() {
                        $(idClicked).fadeIn(slideFadeMilliseconds);
                    }
                );

                slideShow.currentSlideImgId = clickedButton.imageId;

                deactivateButtons(otherButtons);
                activeButton(clickedButton);
            });

            allButtons.push(anchorButton);
            buttonsContainer.appendChild(anchorButton);

            ++globalButtonId;
        }

        slideShowContainer.appendChild(buttonsContainer);
    });
}

$(document).ready(function() {
    setUpSlideShowContainers();
});
.slideshow-container {
    width:         500px;
    height:        330px;
    overflow:      hidden;
    position:      relative;
    margin:        0 auto;
    border-radius: 5px;

    -webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), 0 0 25px rgba(0, 0, 0, 0.2) inset;
    -moz-box-shadow:    0 4px 8px rgba(0, 0, 0, 0.5), 0 0 25px rgba(0, 0, 0, 0.2) inset;
    -o-box-shadow:      0 4px 8px rgba(0, 0, 0, 0.5), 0 0 25px rgba(0, 0, 0, 0.2) inset;
    box-shadow:         0 4px 8px rgba(0, 0, 0, 0.5), 0 0 25px rgba(0, 0, 0, 0.2) inset;
}

.slideshow-img-list {
    font-size: 0;
    width:     auto;
    position:  absolute;
}

.slideshow-img-list img {
    width:         100%;
    height:        100%;
    max-width:     500px;
    max-height:    330px;
    margin-top:    0px;
    margin-bottom: 0px;
}

.slideshow-buttons {
    position:        relative;
    margin-top:      62%;
    display:         flex;
    justify-content: center;
    align-items:     center;
}

.slideshow-buttons a {
    display:       inline-block;
    width:         15px;
    height:        15px;
    border-radius: 10px;
    border:        1px solid white;
    margin-left:   10px;
    margin-right:  10px;
}

.slideshow-active-button {
    background-color: white;
    cursor:           default;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div class="slideshow-container">
<div class="slideshow-img-list">
    <img src="https://bytebucket.org/glampert/ps2dev-tests/raw/f3a137f9030c823d554080ec8bd97b262904bdb9/screens/screen39.png">
    <img src="https://bytebucket.org/glampert/ps2dev-tests/raw/f3a137f9030c823d554080ec8bd97b262904bdb9/screens/screen15.png">
    <img src="https://bytebucket.org/glampert/ps2dev-tests/raw/f3a137f9030c823d554080ec8bd97b262904bdb9/screens/screen22.png">
</div>
</div>

I'm a weekend warrior when it comes to JavaScript and Web stuff, so I'm looking for any suggestions on how to improve this. Please don't spare any details! In particular, I'd like this to be as cross-browser as possible, so if I'm using something not portable, let me know how it can be fixed.

\$\endgroup\$
1
\$\begingroup\$

Most of the issues with your code revolve around not using jQuery to its full potential. Looping through some set of nodes, working only with those that are elements of a certain type, is what jQuery was made for, and is intended to provide a framework that behaves the same on all browsers.

There is a lot of stuff around assigning and tracking ids that isn't really necessary. It is almost always possible to refer to variables locally in a way that avoids the need for adding new properties to DOM elements.

The code below hasn't been tested but is intended to show different ways to achieve the same effects usually with simplification courtesy of jQuery. All of the global Image and Button id stuff is gone and replaced with an array local to each slide show group that keeps track of which button goes with each image. Everything that was being done with native JS but could be done with jQuery uses jQuery for consistency

There were a couple of things in your code worth mentioning specifically as they aren't covered in the code example.

The error checking while laudable was poorly handled. While its good to test for situations that will cause errors, when you find then simply throwing an exception that halts execution is little better than simply letting the errors happen. The example provided doesn't test for errors but if it did it would quietly return from the current function without throwing anything.

The CSS is neat and easy to read but don't forget that it can contain comments which are often more important that in JS. Once JS code is written and works it probably won't need changing for some considerable time. CSS on the other hand might be fiddled with at any time on the whim of some design aesthetic. Having comments in CSS to explain why certain values were chosen, which can be tweaked and which mustn't be change on pain of death, is very useful for anyone hoping to quickly made a few changes.

$(function () {

        // define all of the "Magic Numbers" at the beginning
  // http://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad

    var slideFadeMilliseconds = 300;
    var activeButtonClass='slideshow-active-button';
    var buttonGroupClass='slideshow-buttons';
    var slideShowClass='slideshow-container';


            // find all slideshows and iterate them
    $( "."+slideShowClass ).each(function(index, slideShowContainer) {

            // define array to contain the buttons and their
            // corresponding images
        var buttons=[];

            // record which button/image is currently active 
        var current=0;

            // build a div to contain the buttons
        var buttonsContainer=$('<div>').addClass(buttonGroupClass);
            //  and add it to the page
        slideShowContainer.append(buttonsContainer);


            // find all images in the slideShowContainer and iterate them
        $(slideShowContainer).find('img').each(function(i,img){

                // build a button
            var button=$('<a>').prop('href','javascript:;');

                // if it's the first button it should be active
            if(i==0) button.addClass(activeButtonClass);

                // record the button and its corresponding image
                // as an object with properties button and image
            buttons.push( { button:button, image:img } );

                // then add it to the page
            buttonsContainer.append(button);


            button.click(function(){

                    // fade out the old current image
                $(buttons[current].image).fadeOut(slideFadeMilliseconds,function(){
                    // then fade in this button
                    $(img).fadeIn(slideFadeMilliseconds);
                });

                    // set the old current button to be deactivated
                $(buttons[current].button).removeClass(activeButtonClass);

                    // activate the new current button
                $(img).addClass(activeButtonClass);

                    // update the current variabe to reflect the change
                current=i;
            });

        }).fadeOut(0).first().fadeIn(0);
            // hide all the images the show the first

    });


});
\$\endgroup\$
  • \$\begingroup\$ That seems like a neat solution, thanks, I'll test it latter today. Yep, I was expecting someone to bash me for not using the jQuery functionality, and that's a completely valid point, because I was lazy to browse the documentation and just wanted to get it working at once. The exceptions I have introduced as a self reminder, in case I forgot something when writing the image lists in the markdown. I usually prefer something that blows right in my face at the smallest error then to silently ignore and then struggle to debug. Good note about comment the css too! \$\endgroup\$ – glampert Aug 1 '15 at 1:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.