SOURCE CODE: Uize.Widget.ThumbZoom
VIEW REFERENCE

/*______________
|       ______  |   U I Z E    J A V A S C R I P T    F R A M E W O R K
|     /      /  |   ---------------------------------------------------
|    /    O /   |    MODULE : Uize.Widget.ThumbZoom Class
|   /    / /    |
|  /    / /  /| |    ONLINE : http://www.uize.com
| /____/ /__/_| | COPYRIGHT : (c)2006-2012 UIZE
|          /___ |   LICENSE : Available under MIT License or GNU General Public License
|_______________|             http://www.uize.com/license.html
*/

/*
  OPTIONAL
    - Uize.Widget.Bar.Progress.js (if overlayed progress indicator during load is desired)
*/

/* Module Meta Data
  type: Class
  importance: 2
  codeCompleteness: 100
  testCompleteness: 0
  docCompleteness: 6
*/

/*?
  Introduction
    The =Uize.Widget.ThumbZoom= class wires up thumbnails that are linked to larger versions, so clicking them shows the larger versions with a zoom effect.

    *DEVELOPERS:* `Chris van Rensburg`
*/

Uize.module ({
  name:'Uize.Widget.ThumbZoom',
  required:[
    'Uize.Widget.ImagePort',
    'Uize.Widget.Drag',
    'Uize.Node',
    'Uize.Fade'
  ],
  builder:function (_superclass) {
    /*** Variables for Scruncher Optimization ***/
      var
        _true = true,
        _false = false,
        _Uize_Node = Uize.Node,
        _Uize_Widget = Uize.Widget
      ;

    /*** General Variables ***/
      var _shield;

    /*** Class Constructor ***/
      var
        _class = _superclass.subclass (
          function () {
            var _this = this;

            /*** Public Instance Properties ***/
              _this.showFade = Uize.Fade ({
                duration:350,
                curve:Uize.Fade.celeration (1,0),
                quantization:1
                /*?
                  Instance Properties
                    showFade
                      An instance of the =Uize.Fade= class.

                      NOTES
                      - see the related =hideFade= and =shieldFade= instance properties
                */
              });
              _this.hideFade = Uize.Fade ({
                duration:250,
                curve:Uize.Fade.celeration (0,1),
                startValue:1,
                endValue:0
                /*?
                  Instance Properties
                    hideFade
                      An instance of the =Uize.Fade= class.

                      NOTES
                      - see the related =showFade= and =shieldFade= instance properties
                */
              });
              _this.shieldFade = Uize.Fade ({
                duration:3000,
                curve:Uize.Fade.celeration (1,0),
                startValue:0,
                endValue:1
                /*?
                  Instance Properties
                    shieldFade
                      An instance of the =Uize.Fade= class.

                      NOTES
                      - see the related =hideFade= and =showFade= instance properties
                */
              });
          },
          function () {
            var _this = this;

            /*** create loading progress widget ***/
              if (_this._showLoadingProgress)
                _this._loadingProgress = _this.addChild ('loadingProgress',_Uize_Widget.Bar.Progress)
                /*?
                  Child Widgets
                    loadingProgress
                      An instance of the =Uize_Widget.Bar.Progress= class.
                */
              ;
          }
        ),
        _classPrototype = _class.prototype
      ;

    /*** Public Instance Methods ***/
      _classPrototype.wireUi = function () {
        var _this = this;
        if (!_this.isWired) {
          if (_this._showLoadingProgress && !_this._loadingProgress.getNode ()) {
            /* TO DO:
              - wireUi may get called after the document has loaded, so insertUi has to put the progress bar DOM into the page without using writeln
            */
            _this._loadingProgress.insertUi ();
            _this._loadingProgress.setNodeStyle ('',{position:'absolute'});
          }
          var
            _thumbNode, _zoomedImageNode,
            _showFade = _this.showFade,
            _hideFade = _this.hideFade,
            _shieldFade = _this.shieldFade
          ;
          function _prepareZoomedImageNode () {
            if (!_zoomedImageNode) {
              _zoomedImageNode = document.createElement ('img');
              _Uize_Node.setStyle (
                _zoomedImageNode,
                {
                  display:'none',
                  position:'absolute',
                  zIndex:50001
                }
              );
              _this.wireNode (
                _zoomedImageNode,
                'load',
                function (_event) {
                  _this._showLoadingProgress && _this._loadingProgress.set ({inProgress:_false});
                  _Uize_Node.display (_zoomedImageNode);
                  _Uize_Node.setOpacity (_zoomedImageNode,1);
                  var
                    _coords = _Uize_Node.getCoords (_thumbNode),
                    _windowDims = _Uize_Node.getDimensions (window),
                    _scaledImageCoords = _Uize_Widget.ImagePort.getScaledRect ({
                      portWidth:_windowDims.width,
                      portHeight:_windowDims.height,
                      rectWidth:_zoomedImageNode.width,
                      rectHeight:_zoomedImageNode.height,
                      alignX:.5,
                      alignY:.5,
                      sizingLowerBound:0,
                      sizingUpperBound:'fit',
                      sizingValue:.95,
                      maxScaling:1
                    }),
                    _documentElement = document.documentElement
                  ;
                  _scaledImageCoords.left += _documentElement.scrollLeft;
                  _scaledImageCoords.top += _documentElement.scrollTop;

                  /*** register document handlers for dismissing zoomed image ***/
                    var
                      _oldOnkeydown = document.onkeydown,
                      _oldOnmousemove = document.onmousemove,
                      _oldOnmousedown = document.onmousedown,
                      _initiatingClientX = _event.clientX,
                      _initiatingClientY = _event.clientY
                    ;
                    function _dismissImage () {
                      _showFade.stop ();
                      document.onmousemove = _oldOnmousemove;
                      document.onmousedown = _oldOnmousedown;
                      document.onkeydown = _oldOnkeydown;
                      _shieldFade.stop ();
                      _Uize_Node.display (_shield,_false);
                      _hideFade.start ();
                      return _false;
                    }
                    document.onkeydown = document.onmousedown = _dismissImage;
                    document.onmousemove = function (_event) {
                      _event = _event || event;
                      return (
                        _event.clientX != _initiatingClientX || _event.clientY != _initiatingClientY
                          ? _dismissImage ()
                          : _false
                      );
                    };

                  /*** setup fade properties and initiate zoom out ***/
                    function _getCoordsStyleProperties (_coords) {
                      return {left:_coords.left,top:_coords.top,width:_coords.width,height:_coords.height};
                    }
                    _showFade.start ({
                      startValue:_getCoordsStyleProperties (_coords),
                      endValue:_getCoordsStyleProperties (_scaledImageCoords)
                    });
                }
              );
              document.body.appendChild (_zoomedImageNode);
              if (!_shield)
                _shield = _Uize_Widget.Drag.insertShield ({
                  zIndex:50000,
                  backgroundColor:'#000'
                })
              ;
              _showFade.wire ({
                'Changed.value':
                  function () {
                    _Uize_Node.setStyle (_zoomedImageNode,_showFade.valueOf ());
                  },
                Done:
                  function () {
                    _Uize_Node.setOpacity (_shield,0);
                    _Uize_Node.display (_shield);
                    _shieldFade.start ();
                  }
              });
              _hideFade.wire ({
                'Changed.value':
                  function () {
                    _Uize_Node.setOpacity (_zoomedImageNode,_hideFade);
                  },
                Done:
                  function () {
                    _Uize_Node.display (_zoomedImageNode,_false);
                  }
              });
              _shieldFade.wire ({
                'Changed.value':
                  function () {
                    _Uize_Node.setOpacity (_shield,_shieldFade);
                  }
              });
            }
          }
          /*** wire up the thumb nodes ***/
            function _handleThumbNodeClick () {
              _thumbNode = this;
              var
                _clickHandled = _false,
                _linkNode = _thumbNode
              ;
              while (_linkNode && _linkNode.tagName != 'A')
                _linkNode = _linkNode.parentNode
              ;
              if (_linkNode) {
                var _href = _linkNode.getAttribute ('href');
                if ((_this._imageValidator || /./).test (_href)) {
                  _prepareZoomedImageNode ();
                  /* TO DO:
                    if the src is the same as the zoomedImageNode, then don't wait for the load event
                  */

                  /*** position progress bar and activate ***/
                    if (_this._showLoadingProgress) {
                      var _coords = _Uize_Node.getCoords (_thumbNode);
                      _this._loadingProgress.setNodeStyle (
                        '',
                        {
                          left:_coords.x + 3,
                          top:_coords.y + _coords.height - 1 - _this._loadingProgress.getNode ().offsetHeight - 3
                        }
                      );
                      _this._loadingProgress.set ({inProgress:_true});
                    }

                  /*** initiate loading of large image ***/
                    _Uize_Node.setStyle (
                      _zoomedImageNode,
                      {width:'',height:'',left:-10000,top:-10000}
                    );
                    _zoomedImageNode.src = _linkNode.href;
                    _clickHandled = _true;
                }
              }
              return !_clickHandled;
            }
            _Uize_Node.doForAll (
              _Uize_Node.find (_this._thumbNodes),
              function (_node) {
                _node.onclick = Uize.returnFalse;
                _this.wireNode (_node,'click',_handleThumbNodeClick);
              }
            );
        }
      };

    /*** Register Properties ***/
      _class.registerProperties ({
        _imageValidator:{
          name:'imageValidator',
          value:/\.(jpg|jpeg|gif|png)/
          /*?
            State Properties
              imageValidator
                A regular expression.
          */
        },
        _showLoadingProgress:{
          name:'showLoadingProgress',
          value:_false
          /*?
            State Properties
              showLoadingProgress
                A boolean.
          */
        },
        _thumbNodes:{
          name:'thumbNodes',
          value:[]
          /*?
            State Properties
              thumbNodes
                An array.
          */
        }
      });

    return _class;
  }
});