Max
Max

Reputation: 13334

Design patterns: extending jQuery objects with chaining + namespacing

I need help with JavaScript/jQuery design patterns. Below is some HTML and JS code illustrating what I do, and further below are questions extrapolated from these examples.

Some HTML with nested divs to create a traversing scenario:

    <div class="box">
        BOX 1
        <div class="nested-boxes">
            <div class="box">
                BOX 1-1
                <div class="nested-boxes">
                    <div class="box">
                        BOX 1-1-1
                    </div>
                </div>
            </div>
            <div class="box">
                BOX 1-2
            </div>
        </div>
    </div>

And here is the Javacript code:

// First I declare functions that I want to have available globally
// under a common namespace. Those functions reproduce that namespace as
// a prefix in their name to avoid conflict.
$.extend(true, window, {
    MyFunctions: {
        Boxes: {
            getBox: myfunctions_boxes_getBox
        },
        Circles: {
            // getCircle: myfunctions_circle_getCircle
        }
        // Squares....
    }
});

// I write functions which I want to use on jQuery objects as so:
function myfunctions_boxes_getNestedBoxes() {
    return this.find('.nested-boxes').first().children('.box');
}

function myfunctions_boxes_showLabel() {
    return this.find('span').first().text();   
}

// I then add those functions as new methods to my jQuery objects:
function myfunctions_boxes_getBox($element) {
    var $box = $element.closest('.box');
    $box.getParentBox = myfunctions_boxes_getParentBox;
    $box.getNestedBoxes = myfunctions_boxes_getNestedBoxes;
    $box.showLabel = myfunctions_boxes_showLabel;
    console.log('getBox',$box);
    return $box;
}

// Traversing functions call each other to make sure I retrieve a jQuery object with all
// my custom methods:
function myfunctions_boxes_getParentBox() {
    var $parent_box = myfunctions_boxes_getBox(this.closest('.box').parents('.box').first());
    console.log('getParentBox',$parent_box);
    return $parent_box;
}

Now this is what my code looks like:

// I first need to call a global function:
$box = MyFunctions.Boxes.getBox($('#box-1-1'));

// Then I can start chaining my methods
$box.getParentBox().getNestedBoxes().each(function(){
    // however as soon as I use a native jQuery method, I end up with
    // a jQuery object which doesn't have my custom methods ans I need
    // to use a global function again.
    console.log($(this), MyFunctions.Boxes.getBox($(this)).showLabel());
});

A jsFiddle showing this code in action (should it help you understand what I do) is available.

Q1: How can I write my functions without having to repeat the namespace as a prefix in their name (e.g. myfunctions_boxes_) while avoiding conflicts with third party code?

Every time I create a new function that I want to use as a custom method on a jQuery object (e.g. getParentBox, getNestedBoxes...) I have to manually map it inside one of my functions (i.e. myfunctions_boxes_getBox):

Q2: Is there a way to automatically map my custom methods?

The below question may be related to the above one but I prefer asking it separately as I feel they're not exactly the same

As soon as I use a native jQuery method (e.g. each in above example), I end up with jQuery objects which don't have my custom methods and I need to call one of my global functions again to retrieve the same object but with my custom methods attached to it.

Q3: Would it make sense to create a jQuery plugin for my global functions to keep the OO nature of me code (see below example)?

// plugin declaration (the getBox function should be modified to make use of this)
jQuery.fn.getBox = MyFunctions.Boxes.getBox
// then my code becomes cleaner:
$('#box-1-1').getBox().getParentBox().getNestedBoxes().each(function(){
   console.log($(this).getBox().showLabel());
});

Upvotes: 1

Views: 428

Answers (1)

g45rg34d
g45rg34d

Reputation: 9670

Q1: How can I write my functions without having to repeat the namespace as a prefix in their name (e.g. myfunctions_boxes_) while avoiding conflicts with third party code?

The problem right now is that your functions are global, so you need to append namespace to its name in order to prevent name-conflict (however this still doesn't guarantee safety from name-collisions). What you should do is wrap your entire javascript code in a self-invoking function, for example:

(function {
    $.extend(true, window, {
        MyFunctions: {
            Boxes: {
                getBox: getBox
            }
        }
    });

    function getBox($element) {
        // Do something.
        return $element;
    }
}());

The getBox function is now private and you don't have to worry about name-collisions with third party libraries/etc.

(only tackling the first question for now)

Upvotes: 1

Related Questions