<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>webtuesday</title>
    <style type="text/css">
<!--/*--><![CDATA[/*><!--*/
@media projection {
body {font-size:2.2em;}
.screen { display:none; }
.projection {margin:0;padding:0;display:block;list-style:none;}
.projection > li { display:block;page-break-after:always;
    border-top:0.2em solid #111;}
}

body{background:#ccc;color:#000;}

.first {text-align:center;}

h2 {font-size:3em;border-top:.05em solid #666;margin:0;}
.demo {text-align:center;padding:1em 0;}
button, input, .label {font-size:2em;padding:.2em;}
.area {display:-moz-inline-box;display:inline-block;width:5em;height:5em;background-color:#f00;margin:.2em}
.label {background:#ffc;padding:.2em;margin:.2em;line-height:2em;}

pre, code {background:#fff;}
pre {font-size:.9em;}
pre b {font-weight:bold;color:blue;}
pre i {font-style:italic;color:green;}
pre .string {color:purple;}

/*]]>*/-->
    </style>
    <script type="text/javascript" src="jquery-1.2.6.min.js"></script>
    <script type="text/javascript">
<!--//--><![CDATA[//><!--
function Event() {
    this.oListeners = [];
    this.oObjects = [];
}
Event.prototype = (function() {
    return {
        constructor: Event,
        register: function(callback, obj) {
            obj = typeof obj !== "undefined" ? obj : this;
            var index = this.oObjects.indexOf(obj);
            if(index < 0) {
                index = this.oObjects.push(obj) - 1;
                this.oListeners[index] = [];
            }
            this.oListeners[index].push(callback);
            return this;
        },
        unregister: function(callback, obj) {
            obj = obj||this;
            var index = this.oObjects.indexOf(obj);
            if(index >= 0) {
                var i = this.oListeners.indexOf(callback);
                if(i >= 0 ){
                    this.oListeners[index].splice(i, 1);
                }
            }
            return this;
        },
        notify: function(data, obj) {
            data = data||[];
            if(typeof obj !== "undefined") {
                var index = this.oObjects.indexOf(obj);
                if(index >= 0) {
                    for(var i=0, l=this.oListeners[index].length; i<l; i++) {
                        this.oListeners[index][i].apply(obj, data);
                    }
                }
            } else {
                for(var i=0, l=this.oListeners.length; i<l; i++) {
                    for(var j=0, k=this.oListeners[i].length; j<k; j++) {
                        this.oListeners[i][j].apply(this.oObjects[i], data);
                    }
                }
            }
            return this;
        }
    };
}());

var PubSub = function() {};
PubSub.prototype = (function() {
    // private static
    var oChannels = {};
    
    // public
    return {
        constructor: PubSub,
        subscribe: function(channel, callback, obj) {
            obj = obj||this;
            var oEvent = new Event().register(callback, obj);
            if(!(channel in oChannels)) {
                oChannels[channel] = [];
            }
            oChannels[channel].push(oEvent);
            return this;
        },
        publish: function(channel, data, obj) {
            obj=obj||this;
            if(channel in oChannels) {
                for(var i=0, l=oChannels[channel].length; i<l; i++) {
                    oChannels[channel][i].notify(data, obj);
                }
            }
            sBroadcastChannel = channel.replace(/^(\/.+)\/[^*]+$/, '$1/*');
            if(sBroadcastChannel != channel) {
                return this.publish(sBroadcastChannel, data);
            } else if (sBroadcastChannel !== "/*"){
                return this.publish(sBroadcastChannel.replace(/^(.*\/).+\/(\*)$/, '$1$2'), data);
            } else {
                return this;
            }
        }
    };
}());

function connect(sourceObject, sourceMethod, destObject, destMethod) {
    var copy = sourceObject[sourceMethod],
        temp,
        applyTo = this;

    // make it flexible
    if(typeof sourceObject !== "object") {
        destMethod = destObject;
        destObject = sourceMethod;
        sourceMethod = sourceObject;
        sourceObject = this;
    }
    if(typeof destObject !== "object") {
        destMethod = destObject;
        destObject = this;
        applyTo = sourceObject;
    } else {
        applyTo = destObject;
    }
    
    sourceObject[sourceMethod] = (typeof copy === "function") ?
        function() {
            copy.apply(this, arguments);
            if(typeof destMethod === "string")
                destObject[destMethod].apply(applyTo, arguments);
            else
                destMethod.apply(applyTo, arguments);
        }:
        function() {
            if(typeof destMethod === "string")
                destObject[destMethod].apply(applyTo, arguments);
            else
                destMethod.apply(applyTo, arguments);
        };
}

function Observable() {
    //var variables = {};
};
Observable.prototype = (function() {
    // private
    function observer(key) {
        this.variables = this.variables||{};
        if(!(key in this.variables)) {
            this.variables[key] = new Event();
        }
        return this.variables[key];
    };

    return {
        constructor: Observable,
        get: function(key) {
            return this[key];
        },
        set: function(key, value) {
            this[key] = value;
            this.notify(key);
            return this;
        },
        observe: function(key, fn, obj) {
            obj = obj||this;
            observer.call(this, key).register(fn, obj);
            return this;
        },
        notify: function(key) {
            observer.call(this, key).notify([this.get(key)]);
            return this;
        }
    }
})();

//--><!]]>
    </script>

</head>
<body>
<div class="screen">
 Projection for Opera slideshow.
</div>
<ol class="projection">
    <li class="first">
        <h2>JavaScript Events</h2>
        <h3>What can we learn from Desktop Applications?</h3>
        <p style="text-align:center;font-size:200%;">
            <strong>warning:</strong> code ahead!
        </p>
        <p><a href="http://liip.to/js">http://liip.to/js</a></p>
        <p>Yoan Blanc &lt;yoan@dosimple.ch&gt;, 2008, doSimple.ch</p>
    </li>
    <li>
        <h2>DOM level 0</h2>
        <h3><i>à la</i> C# (<code>delegate</code>)</h3>
        <pre><b>document</b>.getElementById(<span class="string">"button"</span>).onclick = <b>function</b>() {
    
    <b>document</b>.getElementById(<span class="string">"area"</span>)
        .style.backgroundColor = <span class="string">"blue"</span>;

}; </pre>
    </li>
    <li>
        <h2>DOM level 0: demo</h2>
        <p class="demo">
            <button id="dom_level_0_button">Paint it blue</button><br />
            <span id="dom_level_0_area" class="area"></span>
        </p>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
document.getElementById("dom_level_0_button").onclick = function() {
    document.getElementById("dom_level_0_area").style.backgroundColor = "blue";
};
//--><!]]>
        </script>

    </li>
    <!-- Signal Slot -->
    <li>
        <h2>Signal/Slot</h2>
        <h3>Yes, Trolltech's Qt® (<code>dojo.connect</code>)</h3>
        <pre><b>function</b> helloWorld() {
    alert(<span class="string">"Hello World!"</span>);
}
<b>function</b> onButtonClicked(label) {
    <b>return</b> <b>function</b>() {
        <b>document</b>.getElementById(label)
            .style.backgroundColor = <span class="string">"blue"</span>;
    };
}
<b>var</b> button = <b>document</b>.getElementById(<span class="string">"button"</span>);
connect(button, <span class="string">"onclick"</span>, <span class="string">"helloWorld"</span>);
connect(button, <span class="string">"onclick"</span>, onButtonClicked(<span class="string">"area0"</span>));
connect(button, <span class="string">"onclick"</span>, onButtonClicked(<span class="string">"area1"</span>));</pre>
    </li>
    <li>
        <h2>Signal/Slot - demo</h2>
        <p class="demo">
            <button id="signal_slot_button">Paint them blue</button><br />
            <span id="signal_slot_area_0" class="area"></span>
            <span id="signal_slot_area_1" class="area"></span>
        </p>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
var button = document.getElementById("signal_slot_button");
function helloWorld() {
    alert("Hello World!");
}
function onButtonClicked(label) {
    return function() {
        document.getElementById(label).style.backgroundColor = "blue";
    };
}
connect(button, "onclick", "helloWorld");
connect(button, "onclick", onButtonClicked("signal_slot_area_0"));
connect(button, "onclick", onButtonClicked("signal_slot_area_1"));
//--><!]]>
        </script>

    </li>
    <!-- DOM 1 -->
    <li>
        <h2>DOM level 1</h2>
        <h3>like Java AWT/Swing (<code>java.util.EventListener</code>) </h3>
        <pre><b>var</b> button = <b>document</b>.getElementById(<span class="string">"button"</span>);
<i>// the W3C way (not the MSIE one)</i>
button.addEventListener(<span class="string">"click"</span>, <b>function</b>(event) {
    <b>document</b>.getElementById(<span class="string">"area0"</span>)
        .style.backgroundColor = <span class="string">"blue"</span>;
}, <b>false</b>);
button.addEventListener(<span class="string">"click"</span>, <b>function</b>(event) {   
    <b>document</b>.getElementById(<span class="string">"area1"</span>)
        .style.backgroundColor = <span class="string">"blue"</span>;
}, <b>false</b>);</pre>

    </li>
    <li>
        <h2>DOM level 1: demo</h2>
        <p class="demo">
            <button id="dom_level_1_button">Paint them blue</button><br />
            <span id="dom_level_1_area_0" class="area"></span>
            <span id="dom_level_1_area_1" class="area"></span>
        </p>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
document.getElementById("dom_level_1_button").addEventListener("click", function() {
    document.getElementById("dom_level_1_area_0").style.backgroundColor = "blue";
}, false);
document.getElementById("dom_level_1_button").addEventListener("click", function() {
    document.getElementById("dom_level_1_area_1").style.backgroundColor = "blue";
}, false);

//--><!]]>
        </script>

    </li>
    <!-- Custom -->
    <li>
        <h2>Custom Events!</h2>
        <p>The <em>Holy Grail</em> to simple and modular <strong>Rich Internet Applications</strong>.</p>
        <h3>Which one do I pick?</h3>
        <ul>
            <li>your own!</li>
            <li>jQuery</li>
            <li>YUI</li>
        </ul>
    </li>

    <li>
        <h2>GoF Observer pattern</h2>
        <h3>like YUI! (<code>CustomEvent</code>)</h3>
        <pre><b>function</b> colorIt(color) {
    <b>document</b>.getElementById(<b>this</b>.id).
        style.backgroundColor = color;
};
<b>var</b> evt = <b>new</b> Event().
    register(colorIt, {id: <span class="string">"event_area_0"</span>}).
    register(colorIt, {id: <span class="string">"event_area_1"</span>});

<i>// You're not forced to do DOM level 0 (of course)</i>
<b>document</b>.getElementById(<span class="string">"event_button_0"</span>).onclick = <b>function</b>() {
    evt.notify([<span class="string">"blue"</span>]);
};
<b>document</b>.getElementById(<span class="string">"event_button_1"</span>).onclick = <b>function</b>() {
    evt.notify([<span class="string">"green"</span>]);
};</pre>
    </li>
    <!-- GoF -->
    <li>
        <h2>GoF Observer pattern - demo</h2>
        <p class="demo">
            Paint them in <button id="event_button_0">blue</button> or 
            <button id="event_button_1">green</button><br />
            <span id="event_area_0" class="area"></span>
            <span id="event_area_1" class="area"></span>
        </p>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
makeItBlue = function(color) {
    document.getElementById(this.id).style.backgroundColor = color;
};
var blue = new Event().
    register(makeItBlue, {id: "event_area_0"}).
    register(makeItBlue, {id: "event_area_1"});

document.getElementById("event_button_0").onclick = function() {
    blue.notify(["blue"]);
};
document.getElementById("event_button_1").onclick = function() {
    blue.notify(["green"]);
};
//--><!]]>
        </script>
    </li>
    <!-- jQuery -->
    <li>
        <h2>jQuery</h2>
        <h3>with names, like GTK (GObject)</h3>
        <pre>$(<span class="string">"#button"</span>).
    bind((<span class="string">"click"</span>, <b>function</b>(event) {
        <b>var</b> self = <b>this</b>;
        $.getJSON(<span class="string">"http://feeds.delicious.com"</span>+
            <span class="string">"/feeds/json/lejoe/?callback=?"</span>,
            <b>function</b>(data) {
                $(self).triggerHandler(<span class="string">"data"</span>, [data]);
            });
    }). <i>// no necessary chained</i>
    bind(<span class="string">"data"</span>, <b>function</b>(event, data) {
        $(<span class="string">"#label"</span>).html(data[0].u);
    });</pre>
    </li>
    <li>
        <h2>jQuery - demo</h2>
        <p class="demo">
            <button id="jquery_button">What's most recent Joël's bookmark?</button><br />
            <span id="jquery_label" class="label"></span>
        </p>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
$("#jquery_button").
    bind("click", function(event) {
        var self = this;
        $.getJSON("http://feeds.delicious.com/" +
            "feeds/json/lejoe/?callback=?",
            function(data) {
                $(self).triggerHandler("data", [data]);
            }
        );
    }).
    bind("data", function(event, data) {
        $("#jquery_label").html(data[0].d + "<br>" + data[0].u);
    });

//--><!]]>
        </script>
    </li>
    <li>
        <h2>PubSub</h2>
        <h3>XMPP/Bayeux inspired</h3>
        <pre>
<i>Publisher -&gt; channel "data"</i>
Blue (odd) -&gt; /buttons/odd "blue"
Blue (event) -&gt; /buttons/even "blue"
Blue (both) -&gt; /buttons/both "blue"

Green (odd) -&gt; /buttons/odd "green"
Green (event) -&gt; /buttons/even "green"
Green (both) -&gt; /buttons/both "green"

<i>channel -&gt; action (Subscriber)</i>
/buttons/odd -&gt; paint the odd buttons in "color"
/buttons/even -&gt; paint the even buttons in "color"
/buttons/both -&gt; fire the /buttons/odd and /buttons/even events
/buttons/* -&gt; log the current color.
        </pre>
    </li>
    <li>
        <h2>Demo</h2>
        <p class="demo">
            Paint them in blue: <button id="event_button_odd_0">odd</button>, 
            <button id="event_button_even_0">even</button> or 
            <button id="event_button_both_0">both</button>.<br />
            Paint them in green: <button id="event_button_odd_1">odd</button>, 
            <button id="event_button_even_1">even</button> or 
            <button id="event_button_both_1">both</button>.<br />
            <span id="pubsub_area_0" class="area"></span>
            <span id="pubsub_area_1" class="area"></span>
            <span id="pubsub_area_2" class="area"></span>
            <span id="pubsub_area_3" class="area"></span><br />
            <span id="pubsub_label" class="label"></span>
        </p>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
var oPubsub = new PubSub();
makeItBlue = function(color) {
    document.getElementById(this.id).style.backgroundColor = color;
};
document.getElementById("event_button_odd_0").onclick = function() {
    oPubsub.publish("/buttons/odd", ["blue"]);
};
document.getElementById("event_button_even_0").onclick = function() {
    oPubsub.publish("/buttons/even", ["blue"]);
};
document.getElementById("event_button_both_0").onclick = function() {
    oPubsub.publish("/buttons/both", ["blue"]);
};
document.getElementById("event_button_odd_1").onclick = function() {
    oPubsub.publish("/buttons/odd", ["green"]);
};
document.getElementById("event_button_even_1").onclick = function() {
    oPubsub.publish("/buttons/even", ["green"]);
};
document.getElementById("event_button_both_1").onclick = function() {
    oPubsub.publish("/buttons/both", ["green"]);
};
oPubsub.
    subscribe("/buttons/odd", function(color) {
        document.getElementById("pubsub_area_0").style.backgroundColor = color;
        document.getElementById("pubsub_area_2").style.backgroundColor = color;
    }).
    subscribe("/buttons/even", function(color) {
        document.getElementById("pubsub_area_1").style.backgroundColor = color;
        document.getElementById("pubsub_area_3").style.backgroundColor = color;
    }).
    subscribe("/buttons/both", function(color) {
        this.
            publish("/buttons/odd", [color]).
            publish("/buttons/even", [color]);
    }).
    subscribe("/buttons/*", function(color) {
       document.getElementById("pubsub_label").innerHTML = "Color is: "+color; 
    });
//--><!]]>
        </script>
    </li>
    <li>
        <h2>Observable</h2>
        <h3>The Objective-C KVO</h3>
        <pre><i>// Label is Observable</i>
<b>var</b> Label = <b>function</b>(value) {
    this.value = value;
}
Label.prototype = <b>new</b> Observable();
Label.constructor = Label;

<b>var</b> myLabel = new Label(<span class="string">""</span>);
        </pre>
    </li>
    <li>
        <h3>Observable (cont.)</h3>
        <pre>
<i>// Actors</i>
document.getElementById(<span class="string">"input_1"</span>).onkeyup = <b>function</b>() {
    <strong>myLabel.set</strong>(<span class="string">"value"</span>, this.value.toLowerCase());
}
document.getElementById(<span class="string">"input_2"</span>).onkeyup = <b>function</b>() {
    myLabel.set(<span class="string">"value"</span>, this.value.toUpperCase());
}

<i>// Observers</i>
<strong>myLabel.observe</strong>(<span class="string">"value"</span>, <b>function</b>(value) {
    document.getElementById(<span class="string">"input_1"</span>).value = value;
});
myLabel.observe(<span class="string">"value"</span>, <b>function</b>(value) {
    document.getElementById(<span class="string">"input_2"</span>).value = value;
});
myLabel.observe(<span class="string">"value"</span>, <b>function</b>(value) {
    document.getElementById(<span class="string">"label"</span>).innerHTML = encodeURIComponent(value);
});


        </pre>
    </li>
    <li>
        <h2>Observable</h2>
        <p class="demo">
            <input id="o_i_1" value="Two"  />
            <input id="o_i_2" value="inputs"  /><br />
            <span id="o_l" class="label">and a span.</span>
        </p>
        <script type="text/javascript">
var Label = function(value) {
    this.value = value;
}
Label.prototype = new Observable();
Label.constructor = Label;

var myLabel = new Label("");

document.getElementById("o_i_1").onkeyup = function() {
    myLabel.set("value", this.value.toUpperCase());
}
document.getElementById("o_i_2").onkeyup = function() {
    myLabel.set("value", this.value.toLowerCase());
}

myLabel.observe("value", function(value) {
    document.getElementById("o_i_1").value = value;
});
myLabel.observe("value", function(value) {
    document.getElementById("o_i_2").value = value;
});
myLabel.observe("value", function(value) {
    document.getElementById("o_l").innerHTML = encodeURIComponent(value);
});
        </script>
    </li>
    <li>
        <h2>The End</h2>
        <h3>Thank you very much</h3>
        <svg id="canvas4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="800" height="600">
        </svg>
        <script type="text/javascript">
<!--//--><![CDATA[//><!--
var svgns = "http://www.w3.org/2000/svg",
    canvas4 = document.getElementById("canvas4"),
    rect0 = document.createElementNS(svgns, "svg:rect"),
    max = 100,
    size = 30,
    halfsize = size/2,
    gap = 0;

for(var i=0; i<size; i++) {
    for(var j=0; j<size; j++) {
        var ci = Math.abs(i-halfsize),
            cj = Math.abs(j-halfsize);
        if((ci*ci)+(cj*cj) <= ((halfsize-1)*(halfsize-1))) {
        (function(i, j) {
            var rect = rect0.cloneNode(false),
                color = "#"+(i%10)+(j%10)+(Math.round((i+j)/2)%10);
            rect.setAttribute("x", i*max/size+(gap/2));
            rect.setAttribute("y", j*max/size+(gap/2));
            rect.setAttribute("width", max/size-gap);
            rect.setAttribute("height", max/size-gap);
            rect.setAttribute("style", "fill:"+color);
            rect.addEventListener("mouseover", function() {
                oPubsub.publish("/svg/row"+i, ["#fff"]);
                oPubsub.publish("/svg/column"+j, ["#fff"]);
            }, false);
            rect.addEventListener("mouseout", function() {
                oPubsub.publish("/svg/row"+i);
                oPubsub.publish("/svg/column"+j);
            }, false);
            oPubsub
                .subscribe("/svg/row"+i, function(data) {
                    data=data||color;
                    rect.setAttribute("style", "fill:"+data);
                })
                .subscribe("/svg/column"+j, function(data) {
                    data=data||color
                    rect.setAttribute("style", "fill:"+data);
                });
            canvas4.appendChild(rect);
        }(i, j));
        }
    }
}

//--><!]]>
        </script>

    </li>
</ol>
<script type="text/javascript">
<!--//--><![CDATA[//><!--

//--><!]]>
</script>
</body>
</html>
