Dynamically resizing the Shadowbox.js modal window

Shadowbox.js is an excellent modal window component capable of handling everything from iFrames to Flash videos to image galleries. However, and odd as it seems, the out-of-the-box solution doesn’t have a resize() method with which to resize the modal window via JavaScript. The reason you might want to manually resize the modal window is in case the content inside changes, for example in the case of IFrames. Fortunately, implementing such a method is quite easy.

First, download Shadowbox. As of this version, the downloaded files are automatically minified, so in order to be able to properly add the needed code you will have to prettify the code, using a tool such as http://jsbeautifier.org/.

After you have prettified the code, scroll down right after the onWindowResize method, which should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
k.onWindowResize = function () {
    if (!P) {
        return
    }
    I();
    var K = g.player,
        S = R(K.height, K.width);
    t(S.width, S.left);
    E(S.innerHeight, S.top);
    if (K.onWindowResize) {
        K.onWindowResize()
    }
};

The reason I am including the onWindowResize method is because, the code being packed, the variable names might change in subsequent versions of Shadowbox, so you will have to take a look at an existing function. Now, right below the onWindowResize method, add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
k.dynamicResize = function (w, h) {
    if (!P) {
        return
    }
    I();
    g.player.width = w;
    g.player.height = h;
    var K = g.player,
        S = R(K.height, K.width);
    t(S.width, S.left);
    E(S.innerHeight, S.top);
    if (K.onWindowResize) {
        K.onWindowResize()
    }
};

Notice that there are only three lines that differ between the two methods. After adding this method (and perhaps minifying the code again), you can easily resize the modal like this:

1
Shadowbox.skin.dynamicResize(600, 400);

Or, if you want to do it from inside the iframe loaded within the modal:

1
window.parent.Shadowbox.skin.dynamicResize(600, 400);

Note that this method does not resize the window beyond the viewport dimensions. It will still be scaled down if it exceeds the viewport’s width or height.

Closure: Extend ui.DatePicker to highlight events

I got a change to play with the new Google Closure Library a bit, and since the DatePicker component is pretty nice, i figured i could enhance it a bit to suit a need i had some time ago: overlay data from a database, highlight the dates that correspond to events, and jump to the event’s page when clicking on a highlighted cell.

Example page
Example script

Step 1: provide the JSON events in HTML:

1
2
3
4
5
var events = [
    {date: '2009-11-05', url: 'http://www.example.com'},
    {date: '2009-11-07', url: 'http://www.example.com'},
    {date: '2009-11-10', url: 'http://www.example.com'}
];

Step 2: extend goog.ui.DatePicker:

1
2
3
4
5
6
7
8
9
10
goog.provide('goog.ui.EventsDatePicker');
 
goog.require('goog.ui.DatePicker');
 
goog.ui.EventsDatePicker = function(opt_events, opt_date, opt_dateTimeSymbols) {
    goog.ui.DatePicker.call(this, opt_date, opt_dateTimeSymbols);
 
    this.events = opt_events;
};
goog.inherits(goog.ui.EventsDatePicker, goog.ui.DatePicker);

Notice how inheritance is handled with Closure. Even though we call goog.inherits(), we must also call the superclass constructor for it to work.

Step 3: highlight events:

goog.ui.DatePicker has a setDecorator() method, that accepts a function as it’s parameter. The function should take a Date object and return a CSS class name to decorate the corresponding cell with.

It’s basically called once for every cell in the picker, and we are going to use this by checking whether an event exists for the date and return “goog-date-picker-event” if so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
goog.ui.EventsDatePicker = function(opt_events, opt_date, opt_dateTimeSymbols) {
    goog.ui.DatePicker.call(this, opt_date, opt_dateTimeSymbols);
 
    this.events = opt_events;
 
    // new line:
    this.setDecorator(this.eventDecorator);
};
goog.inherits(goog.ui.EventsDatePicker, goog.ui.DatePicker);
 
/**
 * Searches the events array and returns the event corresponding to passed date
 */
goog.ui.EventsDatePicker.prototype.findEventByDate = function (date) {
    var dateString = date.toIsoString(true);
    var event      = goog.array.find(this.events, function (event) { return event.date == dateString; });
 
    return event;
};
 
/**
 * Returns a CSS class name to use for cells that have events
 */
goog.ui.EventsDatePicker.prototype.eventDecorator = function (date) {
    var event = this.findEventByDate(date);    
 
    if (event !== null) {
        return 'goog-date-picker-event';
    }
};

Notice the use of goog.array.find() to find the event that matches the date.

Step 4: click events:

We will use the findEventByDate function defined above and DatePicker’s SELECT event to jump to an event’s URL when clicking on it’s date:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
goog.ui.EventsDatePicker = function(opt_events, opt_date, opt_dateTimeSymbols) {
    goog.ui.DatePicker.call(this, opt_date, opt_dateTimeSymbols);
    this.events = opt_events;
    this.setDecorator(this.eventDecorator);
 
    // new line:
    goog.events.listen(this, goog.ui.DatePicker.Events.SELECT, this.dateClicked, false, this);
};
/**
 * Checks if an event cell was clicked, and jumps to the event's URL if true
 */
goog.ui.EventsDatePicker.prototype.dateClicked = function (event) {
    var date  = event.date;
    var event = this.findEventByDate(date);
    if (event !== null && event.url != '') {
        document.location.href = event.url;
    }
};

That’s it :). It’s a short example, but it covers some of the basic aspects of inheritance, event listeners, and some of the utility methods. Closure looks nice :)

Example page
Example script

Prototype: Hotkeys

Am scris un mic plugin pentru Prototype ce permite inregistrarea oricator hotkeys si combinatii de taste (poate inclusiv sa “suprascrie” combinatii de taste ale browserului, in genul CTRL+S).

Documentatia si codul sunt pe pagina de la Google Code, o sa redau si aici exemplul: Presupunem ca vrem sa rulam functia add cu doi parametri la apasarea tastelor ALT+A:

1
2
3
4
function add(a, b) {
    alert(parseInt(a) + parseInt(b));
}
Hotkeys.bind("alt+a", add, 3, 4);

Pentru mai multe feature-uri, precum si lista de alias-uri pentru taste, consultati pagina proiectului.

FTL jump … now!

Fiecare fisier JavaScript, CSS, imagine, Flash, etc. afisat utilizatorului reprezinta un request catre server. Pentru fiecare asemenea fisier browserul trimite cererea, serverul o preia, proceseaza, serveste fisierul cerut, si tot asa. La site-urile “mari” se simte. In load-ul serverului, in latimea de banda consumata, in experienta utilizatorilor.

Urmand recomandarile echipei de la Yahoo!, o sa va arat la ce solutie am ajuns.

Dar mai intai … cifre. Am ales Okazii.ro drept studiu de caz (toate masuratorile sunt facute cu YSlow, folosind o versiune salvata a primei pagini):

Fara FTL (pagina salvata aici)

Cu FTL (pagina salvata aici)

Calculat la traficul lor (aprox. 82.000 vizitatori unici pe zi), netinand cont de cache in nici unul din cazuri, asta se traduce in o diferenta de 902.000 de request-uri si 14.1 GB trafic lunar.

Cum se foloseste

Presupunand urmatoarea structura de directoare:

/public
    /css
    /js
index.php

Descarcati arhiva si extrageti fisierele in root asa incat noua structura va fi:

/ftl
    /cache
    /lib
    CSS.php
    File.php
    JS.php
    config.php
    index_css.php
    index_js.php
/public
    /css
    /js
index.php

Mutati fisierele index_css.php si index_js.php din directorul /ftl in directorul /public/css, respectiv /public/js, si redenumiti-le in index.php:

/ftl
    /cache
    /lib
    CSS.php
    File.php
    JS.php
    config.php
/public
    /css
        index.php
    /js
        index.php
index.php

Adaugati liniile urmatoare in .htaccess:

1
2
3
4
5
6
7
8
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType application/x-javascript A315360000
    ExpiresByType text/css A315360000
</IfModule>
 
RewriteEngine On
RewriteRule ^public/(js|css)/([a-z0-9\.]+)\.(js|css)$    public/$1/index.php?hash=$2

Includeti fisierele necesare in index.php:

1
2
3
4
<?php
    include("ftl/JS.php");
    include("ftl/CSS.php");
?>

Totul e pregatit, tot ce mai trebuie sa facem e sa incarcam in pagina fisierele. Spre exemplu, ca sa incarc in <head> cateva fisiere CSS si JS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
    $css = new FTL_CSS();
    $js  = new FTL_JS();
 
    $css->add('reset')
        ->add('typo')
        ->add('layout')
        ->add('error')
        ->add('ie', 'lte IE 7');
 
    $js->add('prototype')
       ->add('sizzle')
       ->add('effects')
       ->add('lightbox');
 
    echo $css->render();
    echo $js->render();
?>

Nu uitati sa setati drepturi de scriere pe subdirectoarele din directorul /ftl/cache.

Codul HTML generat va arata in felul urmator:

1
2
3
<link rel="stylesheet" type="text/css" media="screen" href="public/css/1e9dd525d1fbe94f7b8aede22cc141f5.1242750618.css" />
<!--[if lte IE 7]><link rel="stylesheet" type="text/css" media="screen" href="public/css/25400724d7370b0b29c9369d9af3dd21.1242750626.css" /><![endif]-->
<script type="text/javascript" src="public/js/56c728a5a7689ab1663359a7edff160a.1242751025.js"></script>

Doua fisiere .css (unul pentru cele standard, unul incarcat doar pentru IE), si un fisier .js. 3 request-uri in loc de 9, si o dimensiune totala de aprox. 6.5 ori mai mica. Fisierele sunt atat minified cat si gzipped, folosind librariile CSSTidy, respectiv Packer

Suna bine, dar ce se intampla cand …

Prototype: Carousel – Update

A venit vremea ca si Carousel.js sa primeasca un binemeritat update, si sa fie mutat in noua lui casa, Google Code.

Asadar, update-urile si bug-tracking-ul vor fi de acum aici.

PS: Cateva din noilea feature-uri sunt in exemple.