Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change a Javascript singleton to something that can be used multiple times?

A bit of an architectural question...

I originally created a Javascript singleton to house methods needed to operate a photo gallery module in a template file for a CMS system. The original specification only called for one instance of this photo gallery module on a page. (The code below is a gross simplification of what I actually wrote.)

Shortly after releasing the code, it dawned on me that even though the specification called for one instance of this module, this code would fall apart if a page had two instances of the module (i.e. the user adds two photo galleries to a page via the CMS). Now, the HTML markup is safe, because I used class names, but how would I go about restructuring my Javascript and jQuery event listeners to be able to handle multiple modules? You may assume that each photo gallery has its own JSON-P file (or you may assume a single JSON-P file if you think it can be handled more elegantly with one JSON-P file).

I think my original jQuery event listeners might have to be converted to $.delegate(), but I have no clue what to do after that and what to do about converting my singleton. Any leads would be appreciated. If you offer code, I prefer readability over optimization.

I'm not asking this question, because I have an immediate need to solve the problem for work. I am asking this question to be forward-thinking and to be a better Javascript developer, because I am expecting to run into this problem in the future and want to be prepared.

Thank you for reading.

HTML

<div class="photoGalleryMod">
    <div class="photoGalleryImgBox"><img src="http://www.test.org/i/intro.jpg" alt="Intro Photo" /></div>
    <div class="photoGalleryImgCap"><p>Caption</p></div>
    <a href="#" class="photoGalleryPrevImgLnk"></a>
    <a href="#" class="photoGalleryNextImgLnk"></a>  
</div>

The Javascript is an external static file and makes a call to a JSON-P file via $.getSCript(), created by the CMS.

Javascript/jQuery

(function($) {
    photoGalleryModule = {
        json: '',
        numSlidesInJson: '',
        currentSlide: '',
        updateSlide: function (arg_slidNum) {
            /* Update the slide here */
        },
        init: function (arg_jsonObj) {
            this.json = arg_jsonObj;
            this.numSlidesInJson = this.json.photoGallerySlides.length;
            this.currentSlide = 0;
        }
    };

    $(document).ready(function() {
        $.getScript('./photogallery.json');

        $('.photoGalleryPrevImgLnk').live('click', function(event) {
            photoGalleryModule.currentSlide = photoGalleryModule.currentSlide - 1;
            photoGalleryModule.updateSlide(photoGalleryModule.currentSlide);
            event.preventDefault();
        });

        $('.photoGalleryNextImgLnk').live('click', function(event) {
            photoGalleryModule.currentSlide = photoGalleryModule.currentSlide + 1;
            photoGalleryModule.updateSlide(photoGalleryModule.currentSlide);
            event.preventDefault();
        });
    });
})(jQuery);

Contents of photo-gallery.json

photoGalleryModule.init(
{
    photoGallerySlides:
        [
            {
                type: 'intro',
                pageTitle: 'Intro Photo',
                imgUrl: 'http://www.test.org/i/intro.jpg',
                imgAltAttr: 'Intro photo',
                captionText: 'The intro photo',
            },
            {
                type: 'normal',
                pageTitle: 'First Photo',
                imgUrl: 'http://www.test.org/i/img1.jpg',
                imgAltAttr: 'First photo',
                captionText: 'the first photo',
            },
            {
                type: 'normal',
                pageTitle: 'Second Photo',
                imgUrl: 'http://www.test.org/i/img2.jpg',
                imgAltAttr: 'Second photo',
                captionText: 'the second photo',
            }
        ]
});
like image 610
Stephen Avatar asked Jun 03 '11 16:06

Stephen


2 Answers

I think the easiest way is to just turn your code into a plugin. So for the following HTML:

<div id="photoGallery1">
    <div class="photoGalleryImgBox"></div>
    <div class="photoGalleryImgCap"></div>
    <a href="#" class="photoGalleryPrevImgLnk"></a>
    <a href="#" class="photoGalleryNextImgLnk"></a>  
</div>

<div id="photoGallery2">
    ...
</div>

<div id="photoGallery3">
    ...
</div>

You would create the plugin with $.fn.photoGallery where you pass in an index as a parameter:

$.fn.photoGallery = function (index) {
    var $this = this,
        module = {
            json: '',
            numSlidesInJson: '',
            currentSlide: '',
            updateSlide: function (arg_slidNum) {
                /* Update the slide here */
            },
            init: function (arg_jsonObj) {
                module.json = arg_jsonObj;
                module.numSlidesInJson = module.json.photoGallerySlides.length;
                module.currentSlide = 0;
            }
        },
        events = {
            prev: function(e) {
                module.currentSlide = module.currentSlide - 1;
                module.updateSlide(module.currentSlide);
                e.preventDefault();
            },
            next: function(e) {
                module.currentSlide = module.currentSlide + 1;
                module.updateSlide(module.currentSlide);
                e.preventDefault();
            }
        };

    $.getScript('./photogallery' + index + '.json');

    $this.find('.photoGalleryPrevImgLnk').live('click', events.prev);
    $this.find('.photoGalleryNextImgLnk').live('click', events.next);
};

And then initiate each gallery like so:

$(document).ready(function(){
    $('#photoGallery1').photoGallery(1);
    $('#photoGallery2').photoGallery(2);
    $('#photoGallery3').photoGallery(3);
});

Where you have the files photogallery1.json, photogallery2.json and photogallery3.json that each invoke module.init({ ... }); with the necessary object data.

like image 86
mVChr Avatar answered Oct 06 '22 00:10

mVChr


Something like this should do the trick: (untested)

// jquery plugin: jquery.photogallery.js
$.fn.photoGallery = (function($){
    var PhotoGalleryModule = function(el, opts){
        $.extend(this, opts);
        this.el = $(el);
        // if there are multiple on the page do not re-bind or re-init
        if(!!this.el.data('photoGallery')) return el;

        this.numSlidesInJson = this.json.photoGallerySlides.length;
        this.bind();

    };
    PhotoGalleryModule.prototype = {
        updateSlide: function (arg_slidNum) {
            /* Update the slide here */
        },
        bind: function(){
            var self = this;
            this.el.find('.photoGalleryPrevImgLnk')
              .live('click', function(event) {
                self.currentSlide = self.currentSlide - 1;
                self.updateSlide(self.currentSlide);
                event.preventDefault();
            });   
            this.el.find('.photoGalleryNextImgLnk')
              .live('click', function(event) {
                self.currentSlide = self.currentSlide + 1;
                self.updateSlide(self.currentSlide);
                event.preventDefault();
            });
        } 
    };

    return function (opts) {
        return this.each(function () {
            $(this).data('photoGallery', 
                new PhotoGalleryModule(this, opts));
        });
    };
})(jQuery);

// activate
$(function(){
    var ready = function(){
        $('div.photoGalleryMod').photoGallery({
            // similar technique as below to load json
            json: { photoGallerySlides: { /*...*/} },
            currentSlide: 0
        }); 
    };
    // load script dynamically when needed
    ('photoGallery' in $.fn) ? ready() :
        $.getScript('/scripts/jquery.photogallery.js', ready);

});
like image 44
Josiah Ruddell Avatar answered Oct 06 '22 00:10

Josiah Ruddell