diff -r 322d0feea350 -r 89ef5ed3c48b src/cm/media/js/lib/yui/yui_3.10.3/docs/event/synth-example.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui_3.10.3/docs/event/synth-example.html Tue Jul 16 14:29:46 2013 +0200 @@ -0,0 +1,778 @@ + + + + + Example: Creating an Arrow Event for DOM Subscription + + + + + + + + + + +
+
+

+
+ + +

Example: Creating an Arrow Event for DOM Subscription

+
+
+
+
+ +
+

+ This example will illustrate how to use the synthetic event creation + API. We'll create an arrow event that fires in response + to the user pressing the arrow keys (up, down, left, right) and adds a + direction property to the generated event. +

+ +

Subscribing to this new event will look like this:

+
node.on("arrow", onArrowHandler);
+ + +

+ Support will also be added for delegation, allowing a single subscriber + from a node higher up the DOM tree, to listen for the new event + emanating from its descendant elements. +

+ +
containerNode.delegate("arrow", onArrowHandler, ".robot");
+ +

+ This example is not applicable to touch devices, since they don't have arrow keys. +

+
+ +
+
+

Step 1. to the arrow event.
+ +

+

Step 2. Click on a toaster-bot and move it around with the arrow keys.

+ +
+ + +
+ + +
+ + + +
+ +

on, fire, and detach

+ +

+ The three interesting moments in the lifecycle of a DOM event subscription + are +

+ +
    +
  1. The event is subscribed to
  2. +
  3. The event is fired
  4. +
  5. The event is unsubscribed from
  6. +
+ +

+ Create a new synthetic DOM event with `Y.Event.define( name, + config )`. Define the implementation logic for the + on and detach moments in the configuration. + Typically the condition triggering the event firing is set up in the + on phase. +

+ +
Y.Event.define("arrow", {
+    on: function (node, sub, notifier) {
+        // what happens when a subscription is made
+
+        // if (condition) {
+            notifier.fire(); // subscribers executed
+        // }
+    },
+
+    detach: function (node, sub, notifier) {
+        // what happens when a subscription is removed
+    }
+});
+ + +

+ In the case of arrow handling, the trigger is simply a key event with a + keyCode between 37 and 40. There are a few browser quirks with arrow + handling that warrant listening to keydown for some browsers and + keypress for others, so we'll take care of that transparently for arrow + subscribers. +

+ +
Y.Event.define("arrow", {
+    on: function (node, sub, notifier) {
+        var directions = {
+            37: 'left',
+            38: 'up',
+            39: 'right',
+            40: 'down'
+        };
+
+        // Webkit and IE repeat keydown when you hold down arrow keys.
+        // Opera links keypress to page scroll; others keydown.
+        // Firefox prevents page scroll via preventDefault() on either
+        // keydown or keypress.
+        // Bummer to sniff, but can't test the repeating behavior, and a
+        // feature test for the scrolling would more than double the code size.
+        var eventName = (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress';
+
+        // To make detaching the associated DOM event easy, store the detach
+        // handle from the DOM subscription on the synthethic subscription
+        // object.
+        sub._detacher = node.on(eventName, function (e) {
+            // Only notify subscribers if one of the arrow keys was pressed
+            if (directions[e.keyCode]) {
+                // Add the extra property
+                e.direction = directions[e.keyCode];
+
+                // Firing the notifier event executes the arrow subscribers
+                // Pass along the key event, which will be renamed "arrow"
+                notifier.fire(e);
+            }
+        });
+    },
+
+    detach: function (node, sub, notifier) {
+        // Detach the key event subscription using the stored detach handle
+        sub._detacher.detach();
+    }
+} );
+ + +

Add Delegation Support

+

+ Since the arrow event is simply a filtered keydown or keypress event, + no special handling needs to be done for delegate subscriptions. We will + extract the key event handler and use it for both on("arrow", ...) and + delegate("arrow", ...) subscriptions. +

+ +
Y.Event.define("arrow", {
+    // Webkit and IE repeat keydown when you hold down arrow keys.
+    // Opera links keypress to page scroll; others keydown.
+    // Firefox prevents page scroll via preventDefault() on either
+    // keydown or keypress.
+    _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
+
+    _keys: {
+        '37': 'left',
+        '38': 'up',
+        '39': 'right',
+        '40': 'down'
+    },
+
+    _keyHandler: function (e, notifier) {
+        if (this._keys[e.keyCode]) {
+            e.direction = this._keys[e.keyCode];
+            notifier.fire(e);
+        }
+    },
+
+    on: function (node, sub, notifier) {
+        // Use the extended subscription signature to set the 'this' object
+        // in the callback and pass the notifier as a second parameter to
+        // _keyHandler
+        sub._detacher = node.on(this._event, this._keyHandler,
+                                this, notifier);
+    },
+
+    detach: function (node, sub, notifier) {
+        sub._detacher.detach();
+    },
+
+    // Note the delegate handler receives a fourth parameter, the filter
+    // passed (e.g.) container.delegate('click', callback, '.HERE');
+    // The filter could be either a string or a function.
+    delegate: function (node, sub, notifier, filter) {
+        sub._delegateDetacher = node.delegate(this._event, this._keyHandler,
+                                              filter, this, notifier);
+    },
+
+    // Delegate uses a separate detach function to facilitate undoing more
+    // complex wiring created in the delegate logic above.  Not needed here.
+    detachDelegate: function (node, sub, notifier) {
+        sub._delegateDetacher.detach();
+    }
+});
+ + +

Use it

+

+ Subscribe to the new event or detach the event as you would any other DOM + event. +

+ +
function move(e) {
+    // to prevent page scrolling
+    e.preventDefault();
+
+    // See full code listing to show the data set up
+    var xy = this.getData();
+
+    switch (e.direction) {
+        case 'up':    xy.y -= 10; break;
+        case 'down':  xy.y += 10; break;
+        case 'left':  xy.x -= 10; break;
+        case 'right': xy.x += 10; break;
+    }
+
+    this.transition({
+        top : (xy.y + 'px'),
+        left: (xy.x + 'px'),
+        duration: .2
+    });
+}
+
+// Subscribe using node.on("arrow", ...);
+Y.one("#A").on("arrow", move),
+Y.one("#B").on("arrow", move)
+
+// OR using container.delegate("arrow", ...);
+subs = Y.one('#demo').delegate('arrow', move, '.robot');
+ + +

Bonus Step: to the Gallery!

+

+ Synthetic events are perfect candidates for Gallery modules. There are a + number already hosted there, and there are plenty of UI interaction + patterns that would benefit from being encapsulated in synthetic + events. +

+ +

+ The arrow event in this example is also + in the gallery, + but with additional functionality. Check out + its source + to see what you can do with synthetic events. +

+ +

Full Code Listing

+
<div id="demo">
+    <p>Step 1. <button type="button" id="attach" tabindex="1">subscribe</button> to the <code>arrow</code> event.<br>
+    <input type="checkbox" id="delegate" value="1" tabindex="1">
+        <label for="delegate">Use a delegated subscription</label></p>
+    <p>Step 2. Click on a toaster-bot and move it around with the arrow keys.</p>
+
+    <div id="homebase">
+        <img id="A" class="robot" tabindex="3" src="../assets/event/toast-8b-left.png" />
+        <img id="B" class="robot" tabindex="3" src="../assets/event/toast-8b-right.png" />
+    </div>
+
+    <button type="button" id="detach" tabindex="4">Detach subscriptions</button>
+</div>
+
+<script>
+YUI({ filter: 'raw' }).use('node', 'event-synthetic', 'transition', function (Y) {
+    Y.Event.define("arrow", {
+        // Webkit and IE repeat keydown when you hold down arrow keys.
+        // Opera links keypress to page scroll; others keydown.
+        // Firefox prevents page scroll via preventDefault() on either
+        // keydown or keypress.
+        _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
+
+        _keys: {
+            '37': 'left',
+            '38': 'up',
+            '39': 'right',
+            '40': 'down'
+        },
+
+        _keyHandler: function (e, notifier) {
+            if (this._keys[e.keyCode]) {
+                e.direction = this._keys[e.keyCode];
+                notifier.fire(e);
+            }
+        },
+
+        on: function (node, sub, notifier) {
+            sub._detacher = node.on(this._event, this._keyHandler,
+                                    this, notifier);
+        },
+
+        detach: function (node, sub, notifier) {
+            sub._detacher.detach();
+        },
+
+        delegate: function (node, sub, notifier, filter) {
+            sub._delegateDetacher = node.delegate(this._event, this._keyHandler,
+                                                  filter, this, notifier);
+        },
+
+        detachDelegate: function (node, sub, notifier) {
+            sub._delegateDetacher.detach();
+        }
+    });
+
+
+    var robotA = Y.one('#A'),
+        robotB = Y.one('#B'),
+        subs,
+        moving = false;
+
+    robotA.setData('x', parseInt(robotA.getStyle('left'), 10));
+    robotA.setData('y', parseInt(robotA.getStyle('top'), 10));
+    robotB.setData('x', parseInt(robotB.getStyle('left'), 10));
+    robotB.setData('y', parseInt(robotB.getStyle('top'), 10));
+
+    // create variables for image path/filenames
+    // Use 8bit pngs for IE
+    var imgBits = (Y.UA.ie && Y.UA.ie < 9) ? '-8b-' : '-24b-',
+        imgNamePre = '../assets/event/toast' + imgBits,
+
+        imgUp = imgNamePre + 'up.png',
+        imgDown = imgNamePre + 'down.png',
+        imgLeft = imgNamePre + 'left.png',
+        imgRight = imgNamePre + 'right.png';
+
+    Y.one('#A').setAttribute('src', imgLeft);
+    Y.one('#B').setAttribute('src', imgRight);
+
+    function move(e) {
+        // to prevent page scrolling
+        e.preventDefault();
+        if(moving){
+            return; // Don't move during a transition (a move)
+        }else{
+            moving = true; // During moving, block other arrow keys from moving
+        }
+                
+        var xy = this.getData(),
+            imgWidth,
+            imgHeight,
+            // var scale is used to make the image size and distance moved
+            // proportional to the Y position of the image
+            scale = (150 + xy.y) / 150, 
+            moveXDistance = 40,
+            moveYDistance = 20;
+           
+        switch (e.direction) {
+            case 'up':
+                if(xy.y < -100){ 
+                    moving = false;
+                    return; // Stop moving when image gets too small
+                }    
+                xy.y -= Math.round(moveYDistance * scale);
+                e.target.setAttribute('src', imgUp);
+                break;
+            case 'down':  
+                if(xy.y > 90){
+                    moving = false;
+                    return; // Stop moving when image gets too big
+                }    
+                xy.y += Math.round(moveYDistance * scale);
+                e.target.setAttribute('src', imgDown);
+                break;
+            case 'left':
+                xy.x -= Math.round(moveXDistance * scale);
+                e.target.setAttribute('src', imgLeft);
+                break;
+            case 'right':
+                xy.x += Math.round(moveXDistance * scale);
+                e.target.setAttribute('src', imgRight);
+                break;
+        }
+        scale = 150 / (150 - xy.y); // calculate scale with new Y dimentions
+        imgWidth = Math.round(scale * 180) + 'px';
+        imgHeight = Math.round(scale * 210) + 'px';
+        this.transition({
+            top : (xy.y + 'px'),
+            left: (xy.x + 'px'),
+            width: imgWidth,
+            height: imgHeight,
+            duration: .8
+        }, function(){
+            moving = false; // now that move is done, allow arrow keys to move again
+        });
+
+        this.setData('x', xy.x);
+        this.setData('y', xy.y);
+    }
+
+    function detachSubs() {
+        if (subs) {
+            subs.detach();
+            subs = null;
+            Y.all('.robot').removeClass('yui3-focused');
+        }
+    }
+
+    Y.one("#attach").on("click", function (e) {
+        detachSubs();
+
+        if (Y.one("#delegate").get('checked')) {
+            subs = Y.one('#demo').delegate('arrow', move, '.robot');
+        } else {
+            subs = new Y.EventHandle([
+                robotA.on("arrow", move),
+                robotB.on("arrow", move)
+            ]);
+        }
+    });
+
+    Y.one("#detach").on("click", detachSubs);
+    Y.all('.robot').on('focus', function(e){
+        if (subs) {
+            Y.all('.robot').removeClass('yui3-focused');
+            e.target.addClass('yui3-focused');
+        }
+    });
+
+    Y.all('.robot').on('click', function(e){
+        e.target.focus();
+    });
+
+});
+</script>
+ + +
+
+
+ +
+ +
+
+
+ + + + + + + + + + +