Follow me: @D_mitar

Most read posts recently



Feb 2nd 2012 × Using overloadSetter / overloadGetter to make flexible functions in mootools

This is a really helpful feature of MooTools that a few people know and use. Basically, there is a pair of 2 Function prototypes called ‘overloadGetter‘ and ‘overloadSetter‘. They are very simple in what they can provide. Say, you have a function that is a private property setter in a Class that takes 2 arguments: key & value. After a while, you may get bored having to call it all the time for every property you wish to add or having to write object loops for longer updates.

By defining using the Function decorator overloadSetter, you can – without any code adjustment – make your single key/value pair setter take an object and automatically iterate through the properties for you.

Similarly, by using the decorator overloadGetter, you can pass on multiple properties to retrieve that you are interested in – and it will return a single object with all your properties of interest as key.

Pretty cool?

Here’s a real world example. Let’s say, you are making a MVC Model Class that you’d like to use to deal with data objects and dispatch events.

(function() {

    // private real setter functions, not on prototype, see note on .set
    var _set = function(key, value) {
        // needs to be bound the the instance.
        if (!key || typeof value === undefined)
           return this;

        // no change? return. better rely on _.js _.eq but this is crude and works for primitives.
        if (this._attributes[key] && this._attributes[key] === value)
           return this;

        if (value === null) {
            this._attributes.delete(key); // delete = null.
        }
        else {
            this._attributes[key] = value;
        }
        // fire an event.
        return this.fireEvent("change:" + key, value);
    }.overloadSetter();   

    this.Model = new Class({

        _attributes: {}, // initial private object,

        Implements: [Options, Events],

        initialize: function(data, options) {
            data && typeOf(data) === 'object' && this.set(data);
            this.setOptions(options);
        },

        set: function() {
            // call the real getter. we proxy this because we want
            // a single event after all properties are updated
            _set.apply(this, arguments);
            this.fireEvent("change");
        },

        get: function(key) {
            // and the overload getter
            return (key && typeof this._attributes[key] !== undefined)
                ? this._attributes[key]
                : null;
        }.overloadGetter()

    });

})();

// now make an abstraction of your Model class for users
var User = new Class({

    Extends: Model,

    _attributes: {
        name: "Please enter your name",
        email: "Enter your email address"
    }

});

// initialise your User Model with default data and set some property change events
var user = new User(null, {
    "onChange:email": function(value) {
        // can dispatch to controller/view the change
        console.log("new email now is", value);
    },
    "onChange:name": function(value) {
        console.log("new name now is", value);
    },
    onChange: function() {
        console.log("data has changed!");
    }
});

// let's test this. initial set via single value set:
user.set("email", "dimitar@securemail.com");

// now let's try multiple properties at the same time:
user.set({
    "email": "dimitar@securemail.com",
    "name": "coda"
});

// get the name
console.log(user.get("name")); // coda;

// get multiple properties in a single object
console.log(user.get(["name","email"]));
// Object { name="coda",  email="dimitar@securemail.com"}

View and play with this on http://jsfiddle.net/dimitar/39x7T/

That’s about it. Please note that this Class was only created for the purposes of a demo and is not meant to be used in a real MVC pattern – look at Composer.js (for MooTools), Backbone.js or Ember.js – formerly Sprout Core – if you are interested in doing serious event-based application development.


Apr 22nd 2011 × Tutorial: write a small content slider Class in MooTools and extend it

Difficulty: moderate
Framework: MooTools 1.3 (no previous ver compatibility)
Dependencies: MooTools-more 1.3.1 (no compat mode, just Element.Delegation required to build it)
DocType: HTML5 – optional

Before I begin, if you have not made a MooTools Class before, I suggest you first read this tutorial I made last month on creating a Sliding Tips plugin, which is a good entry level.

I imagine you’d want to see what the end result will will look like before you waste your time reading so:
http://jsfiddle.net/dimitar/Ea4Gp/show/ – the Base Class, cross-fading
http://jsfiddle.net/dimitar/Ea4Gp/5/show/ – the Extended Class with alternative transition effect
http://jsfiddle.net/dimitar/Ea4Gp/5/ – the editable fiddle to play with.
preview

Anyway. I just had cause to write a small class that allows me to swap between various content divs/panes that are part of the DOM and allow for any markup/child elements within. I thought I’d share the code as I could not readily find one that did things how I wanted them to be.

First, let us examine the requirements from a HTML point of view: a parent node (element) contains all the divs that will rotate. The Class needs to pick them up and enhance them. Should javascript be available, the user gets to see multiple content divs, otherwise–just the first one (progressive enhancement). Google has to be able to read text and follow any links within the content divs.

The resulting HTML markup looks like this:

<section class="rotator" id="rotator">
    <div class="pane" style="background-image:url(http://fragged.org/img/home/hunting-home.jpg)">
        <div class="info">
            <h2>Hunting CS style</h2>
            Built for your `camping` pleasure.
        </div>
    </div>
    <div class="pane hide" style="background-image:url(http://fragged.org/img/home/fishing-home.jpg)">
        <div class="info">
            <h2>Fishing? Really? </h2>
            Fishing is for mongs. <a href="#">Click here</a>
        </div>
    </div>
    <div class="pane hide" style="background-image:url(http://fragged.org/img/home/tourism-home.jpg)">
       <div class="info rambling">
           <h2>Rambling and walking</h2>
           Wish you were here? Can't blame you, it's lame.
        </div>
    </div>
</section>

The accompanying CSS that goes along with the markup above:

section.rotator {
    border-bottom: 4px solid #000;
    width: 700px !important;
    height: 280px !important;
    overflow: hidden;
    position: relative;
}

section.rotator div.pane {
    position: absolute;
    width: 700px;
    height: 280px;
    background-repeat: no-repeat;
    background-position: left top;
}

Come the MooTools magic. The Class is _very_ simple:

this.contentSwapper = new Class({

    // mootools options and events mixins as standard
    Implements: [Options, Events],

    // some defaults...
    options: {
        delay: 3000,
        selector: "div",
        controlLeft: "http://fragged.org/img/home/moveLeft.png",
        controlRight: "http://fragged.org/img/home/moveRight.png"
    },

    initialize: function(element, options) {
        // base constructor.

        // check to see if it's called with an element...
        this.element = document.id(element);
        if (!this.element)
            return;

        this.setOptions(options);

        // grab the child divs
        this.elements = this.element.getChildren(this.options.selector);
        this.index = 0;

        // call some methods we are about to define.
        this.attachControls();
        this.startRotation();
        this.attachEvents();
        this.fireEvent("ready");
    },

Once again, I like to break up all the things the Class does into small methods – ideally, as small as possible without getting into ridiculous territory where you write a method that returns a property you can access directly (eg, this.getSize = function() { return this.size; }).

attachControls is a cool method, as it makes use of some of MooTools 1.3′s new features: Slick and zen-like element construction.

attachControls: function() {
    this.controls = $$(
        new Element("img#moveLeft.contentControl[title=Previous][src={controlLeft}]".substitute(this.options)).inject(this.element, "top"),
        new Element("img#moveRight.contentControl[title=Next][src={controlRight}]".substitute(this.options)).inject(this.element, "top")
    );
},

Notice the $$() around the controls. This will create a MooTools HTML collection that is iteration-able (.each or any methods). The collection is basically like an array with added Element prototypes working. Other than that, nothing too complicated takes place – makes a new image with id #moveLeft, class .contentControl, sets the title and the src property.

To attach events: we want the rotation to stop when the user mouses over the main element (section.rotator) so they can read in peace and possibly click any call to action it may have.

attachEvents: function() {
    this.element.addEvents({
        mouseenter: this.stopRotation.bind(this),
        mouseleave: this.startRotation.bind(this),
        "click:relay(img.contentControl)": this.move.bind(this)
    });
},

Hence, mousenter calls our stop method and mouseleave starts again. The “click:relay(img.contentControl)” is MooTools’ way to doing Event Delegation. Saves us you having to bind 2 events and callbacks to both controls by just binding to the parent element.

The movement methods. Very minimal logic, they literally just set variables.

move: function(e, el) {
    // based upon image id, it will match a method name.
    this[el.get("id")](); // call it dynamically
},

moveLeft: function() {
    // set the next frame coming to previous one or last
    var next = (this.index == 0) ? this.elements.length-1 : this.index-1;
    this.swapFrames(next);
    this.fireEvent("left");
},

moveRight: function() {
    // set the next frame coming to next one or first
    var next = (this.index < this.elements.length-1) ? this.index+1 : 0;
    this.swapFrames(next);
    this.fireEvent("right");
},

Now we get to the more interesting parts. The actual swapping of the content is the core of the Class and is what we will modify later when we extend it to make it work differently.

The base class version does a VERY simple transition based around tweening the opacity of the 2 relevant content panes. One fades in while the other fades out.

swapFrames: function(next) {
    // element currently visible is in this.index (as key of this.elements array)
    var curEl = this.elements[this.index];

    // clean up from before, just in case
    curEl.get("tween").removeEvents();

    // reset element and set tween options.
    curEl.set({
        "tween": {
            link: "cancel",
            onComplete: function() {
                // add a css class that sets display to none, for example
                this.element.addClass("hide");
            }
        },
        styles: {
            opacity: 1 // initial opacity reset in case quick clicks
        }
    }).fade(0);

    // now set our new visible frame from this.moveLeft/Right argument
    this.index = next;

    var newEl = this.elements[this.index];
    //clean up, reset, show and fade in.
    newEl.get("tween").removeEvents();
    newEl.setStyle("opacity", 0).removeClass("hide").fade(1);
}

The end result: panes will cross-fade between each other, giving a morphing impression. Less is more, as they say.

Finally, we have 2 small methods that start and stop the automatic rotation. To keep the user interested in your site content, you really need to take no more than 2.5 second from their initial impression of the site or they may _subconciously_ lose interest and just bounce based upon the failure to connect with content shown from the start.

startRotation: function() {
    clearInterval(this.timer);
    this.controls.fade(.5);
    this.timer = this.moveRight.periodical(this.options.delay, this);
    this.fireEvent("start");
},
stopRotation: function() {
    clearInterval(this.timer);
    this.controls.fade(1);
    this.fireEvent("stop");
}

// and end class...
});

The controls collection we did earlier fades up and down based upon interaction: when it's stopped, the user is 'over' the element so we make the left and right more visible and we fade them out afterwards so they are less distracting.

This is it. To call the Class with your default options, all you need is:

new contentSwapper(document.id("rotator"));

You can, of course, attach event callbacks that respond to the ones the class has fired:

new contentSwapper.Fancy(document.id("rotator"), {
    onReady: function() {
        console.log("we are up!");
    },
    onStart: function() {
        console.log("and we are moving");
    },
    onStop: function() {
        console.log("Oh! they are reading with mouse on top!");
    }
});

... and so forth and so forth.

Simple. Now, imagine you are happy with your base Class but would like to extend it somewhat, by modifying it slightly to better suit your needs. In this case, we no longer desire to have a standard opacity cross-fade transition between panes so we will try to override that.

MooTools allows you to use the Extends: mutator(?) as part of your class declaration.

contentSwapper.Fancy = new Class({

    Extends: contentSwapper,

    initialize: function(element, options) {
        this.parent(element, options);
    },

We have now extended the base class and have everything it does inherited. But we want to actually override one of the methods to make things snazzy. We will try to make a vertical transition of the new pane over the old one as an experiment.

swapFrames: function(next) {
    var curEl = this.elements[this.index];
    // using morph for multiple CSS properties we will adjust
    // on the same timer, eg, opacity and margintop:
    curEl.get("morph").removeEvents();
    curEl.set({
        styles: {
            zIndex: 1000,
            opacity: 1
        },
        "morph": {
            link: "cancel",
            duration: 1000,
            onComplete: function() {
                this.element.addClass("hide");
            }
        }
    }).morph({
        // we really just want opacity as it looked better but experiment
        opacity: 0
    });

    this.index = next;

    // bring the new element in from the top, 280px.
    var newEl = this.elements[this.index];
    newEl.get("morph").removeEvents();
    newEl.removeClass("hide").setStyles({
        zIndex: 1001,
        marginTop: -280,
        opacity: 0
    }).morph({
        marginTop: 0,
        opacity: 1
    });
}

}); // end extended class

That's it. Now, you can improve that further by setting the marginTop offset into the Fancy options or read the height of this.element instead, your call.

To use the alternative and fancier way of transitioning, you do:

new contentSwapper.Fancy(document.id("rotator"));

Supplemental CSS required:

img.contentControl {
    position: absolute;
    margin-top: 90px;
    z-index: 10000;
    cursor: pointer;
    _cursor: hand;
}
#moveLeft {
    margin-left: 0;
}

#moveRight {
    margin-left: 662px;
}

Dead simple, isn't it? Have fun with MooTools. Any comments, questions or feedback, @D_mitar on Twitter or coda- on #mootools (irc.freenode.net).