Source: ui/resizable.js

goog.provide('pear.ui.Resizable');
goog.provide('pear.ui.Resizable.EventType');

goog.require('goog.fx.Dragger');
goog.require('goog.fx.Dragger.EventType');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Size');
goog.require('goog.string');
goog.require('goog.style');
goog.require('goog.ui.Component');
goog.require('goog.ui.Component.EventType');



/**
 * Column Resizable
 * Code Adapted from : http://goo.gl/VA43nv
 * @param {Element} element
 * @param {?Object=} opt_data
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
 * @constructor
 * @extends {goog.ui.Component}
 */
pear.ui.Resizable = function(element, opt_data, opt_domHelper) {
  goog.ui.Component.call(this, opt_domHelper);

  opt_data = opt_data || {};

  /**
   * Root Element
   * @type {Element}
   * @private
   */
  this.rootElement_ = goog.dom.getElement(element);

  /**
   * @type {number}
   * @private
   */
  this.minWidth_ = goog.isNumber(opt_data.minWidth) ? opt_data.minWidth : 0;
  /**
   * @type {number}
   * @private
   */
  this.maxWidth_ = goog.isNumber(opt_data.maxWidth) ? opt_data.maxWidth : 0;
  /**
   * @type {number}
   * @private
   */
  this.minHeight_ = goog.isNumber(opt_data.minHeight) ? opt_data.minHeight : 0;
  /**
   * @type {number}
   * @private
   */
  this.maxHeight_ = goog.isNumber(opt_data.maxHeight) ? opt_data.maxHeight : 0;

  /**
   * @type {number}
   * @private
   */
  this.handles_ = opt_data['handles'] || pear.ui.Resizable.Position.ALL;

  /**
   * List of resizable handle draggers
   * @type {Object.<number,goog.fx.Dragger>}
   * @private
   */
  this.handleDraggers_ = {};

  /**
   * List of resizable handles
   * @type {Object.<number,Element>}
   * @private
   */
  this.handlers_ = {};

  this.setupResizableHandler_();

  /**
   * @private
   * @type {goog.math.Coordinate?}
   */
  this.handlerOffsetCoord_ = null;
  /**
   * @private
   * @type {goog.math.Coordinate?}
   */
  this.elementCoord_ = null;
  /**
   * @private
   * @type {goog.math.Size?}
   */
  this.elementSize_ = null;

};
goog.inherits(pear.ui.Resizable, goog.ui.Component);


/**
 * @enum {string}
 *
 */
pear.ui.Resizable.EventType = {
  RESIZE: 'resize',
  START_RESIZE: 'start_resize',
  END_RESIZE: 'end_resize'
};


/**
 * Resizable Handle postion
 * @enum {number}
 */
pear.ui.Resizable.Position = {
  RIGHT: 2, //E
  BOTTOM: 4, //S
  LEFT: 8, //W
  TOP: 16,  //N
  TOP_LEFT: 32, //NW
  TOP_RIGHT: 64, //NE
  BOTTOM_RIGHT: 128, //SE
  BOTTOM_LEFT: 256, //SW
  ALL: 511,
  NONE: 0
};


/**
 * Get Resizable Handle by Position
 * @param  {pear.ui.Resizable.Position} position handle position
 * @return {Element} Resizable Handle
 */
pear.ui.Resizable.prototype.getResizehandle = function(position) {
  return this.handlers_[position];
};


/**
 *
 * Setup all handles
 * @private
 */
pear.ui.Resizable.prototype.setupResizableHandler_ = function() {
  if (this.handles_ & pear.ui.Resizable.Position.RIGHT) {
    this.addResizableHandler_(pear.ui.Resizable.Position.RIGHT,
        ['e', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.BOTTOM) {
    this.addResizableHandler_(pear.ui.Resizable.Position.BOTTOM,
        ['s', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.LEFT) {
    this.addResizableHandler_(pear.ui.Resizable.Position.LEFT,
        ['w', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.TOP) {
    this.addResizableHandler_(pear.ui.Resizable.Position.TOP,
        ['n', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.TOP_LEFT) {
    this.addResizableHandler_(pear.ui.Resizable.Position.TOP_LEFT,
        ['nw', 'corner', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.TOP_RIGHT) {
    this.addResizableHandler_(pear.ui.Resizable.Position.TOP_RIGHT,
        ['ne', 'corner', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.BOTTOM_RIGHT) {
    this.addResizableHandler_(pear.ui.Resizable.Position.BOTTOM_RIGHT,
        ['se', 'corner', 'handle']);
  }
  if (this.handles_ & pear.ui.Resizable.Position.BOTTOM_LEFT) {
    this.addResizableHandler_(pear.ui.Resizable.Position.BOTTOM_LEFT,
        ['sw', 'corner', 'handle']);
  }
};


/**
 *
 * Add handle
 * @param {pear.ui.Resizable.Position} position
 * @param {Array} classNames
 * @private
 */
pear.ui.Resizable.prototype.addResizableHandler_ =
    function(position, classNames) {
  var dom = this.getDomHelper();
  var handle = dom.createDom('div');
  goog.array.forEach(classNames, function(value) {
    goog.dom.classlist.add(handle, this.getCSSClassName() + '-' + value);
  },this);
  this.rootElement_.appendChild(handle);

  var dragger = new goog.fx.Dragger(handle);
  dragger.defaultAction = function() {};

  this.getHandler().
      listen(dragger, goog.fx.Dragger.EventType.START,
          this.handleDragStart_).
      listen(dragger, goog.fx.Dragger.EventType.DRAG,
          this.handleDrag_).
      listen(dragger, goog.fx.Dragger.EventType.END,
          this.handleDragEnd_);

  this.handleDraggers_[position] = dragger;
  this.handlers_[position] = handle;

  // Supress MouseMove,MouseOver  Events on these Handles

  this.getHandler().
      listen(handle, goog.events.EventType.MOUSEDOWN,
          this.handleEvents_);
};


/**
 *
 * Setup Limits
 * @param {goog.math.Coordinate} coord
 * @param {goog.math.Size} size
 * @param {pear.ui.Resizable.Position} position
 * @private
 */
pear.ui.Resizable.prototype.setupMinAndMaxCoord_ =
    function(coord, size, position) {

  this.leftX_ = 0;
  this.rightX_ = 0;
  this.topY_ = 0;
  this.bottomY_ = 0;

  if (position & 64) {
    this.leftX_ = coord.x;
    this.rightX_ = coord.x;
  }else {
    this.leftX_ = coord.x - (this.maxWidth_ - size.width);
    this.rightX_ = coord.x + (size.width - this.minWidth_);
  }

  if (position & 256) {
    this.topY_ = coord.y;
    this.bottomY_ = coord.y;
  }else {
    this.topY_ = coord.y - (this.maxHeight_ - size.height);
    this.bottomY_ = coord.y + (size.height - this.minHeight_);
  }

};


/**
 * Root Class name
 * @return {string}
 */
pear.ui.Resizable.prototype.getCSSClassName = function() {
  return 'pear-ui-resizable';
};


/**
 * Retrieves the computed value of the position CSS attribute.
 * @param {Element} el  Element
 * @return {string}
 * @private
 */
pear.ui.Resizable.prototype.getComputedPosition_ = function(el) {
  var positionStyle = goog.style.getComputedPosition(el);
  return positionStyle;
};


/**
 * @param {Element} el
 * @return {goog.math.Coordinate}
 * @private
 */
pear.ui.Resizable.prototype.getPosition_ = function(el) {
  var coord;
  var positionStyle = this.getComputedPosition_(el);
  //if (positionStyle === 'absolute') {
  coord = goog.style.getPosition(this.rootElement_);
  // }else if (positionStyle === 'relative') {
  //  coord = goog.style.getRelativePosition(this.rootElement_);
  //}
  return coord;
};


/**
 * @param {Element} el
 * @return {goog.math.Size}
 * @private
 */
pear.ui.Resizable.prototype.getSize_ = function(el) {
  //var size = goog.style.getSize(el);
  // Expensive - clousre getSize returns the border box size
  // and setHeight actually set the
  // style attribute height
  var size = goog.style.getContentBoxSize(el);
  return size;
};


/**
 * @param {goog.events.BrowserEvent} e
 * @private
 */
pear.ui.Resizable.prototype.handleEvents_ = function(e) {
  e.preventDefault();
};


/**
 * @param {goog.events.Event} ge
 * @private
 */
pear.ui.Resizable.prototype.handleDragStart_ = function(ge) {

  var dragger = (/** @type {?goog.fx.Dragger} */ (ge.currentTarget));
  var position = this.getDraggerPosition_(dragger);
  var targetPos = goog.style.getPosition(dragger.target);

  var size = this.getSize_(this.rootElement_);
  var coord = this.getPosition_(this.rootElement_);
  var coordBorder = goog.style.getBorderBox(this.rootElement_);

  this.setupMinAndMaxCoord_(coord, size, position);

  this.handlerOffsetCoord_ = new goog.math.Coordinate(targetPos.x, targetPos.y);
  this.elementCoord_ = coord;
  this.elementSize_ = new goog.math.Size(size.width, size.height);

  // TODO : Calculate final size here
  this.dispatchResizableEvent_(pear.ui.Resizable.EventType.START_RESIZE, null);
};


/**
 * @param {goog.events.Event} ge
 * @private
 * @return {boolean}
 */
pear.ui.Resizable.prototype.handleDrag_ = function(ge) {
  var deltaWidth = 0, deltaHeight = 0, newX = 0, newY = 0;
  var dragger = (/** @type {?goog.fx.Dragger} */ (ge.currentTarget));
  var position = this.getDraggerPosition_(dragger);

  var el = this.rootElement_;
  var size = this.getSize_(el);
  var coord = this.getPosition_(el);


  // this.debug('DRAG', dragger);

  if (position & 194) { /* RIGHT, TOP_RIGHT, BOTTOM_RIGHT */
    size.width = this.elementSize_.width +
        dragger.deltaX -
        this.handlerOffsetCoord_.x;
  }

  if (position & 388) { /* BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT */
    size.height = this.elementSize_.height +
        dragger.deltaY -
                      this.handlerOffsetCoord_.y;
  }

  if (position & 296) {/* LEFT, TOP_LEFT, BOTTOM_LEFT */
    size.width = this.elementSize_.width -
        dragger.deltaX +
        this.handlerOffsetCoord_.x;
    coord.x = this.elementCoord_.x +
        dragger.deltaX -
                this.handlerOffsetCoord_.x;
  }

  if (position & 112) {/* TOP, TOP_LEFT, TOP_RIGHT */
    size.height = this.elementSize_.height -
        dragger.deltaY +
        this.handlerOffsetCoord_.y;
    coord.y = this.elementCoord_.y +
        dragger.deltaY -
                this.handlerOffsetCoord_.y;
  }


  // Now size the containers.
  this.resize_(el, size, coord, position);

  if (goog.isFunction(el.resize)) {
    el.resize(size);
  }

  ge.preventDefault();
  return false;
};


/**
 * @param {goog.events.Event} ge
 * @private
 */
pear.ui.Resizable.prototype.handleDragEnd_ = function(ge) {
  ge.preventDefault();
  // TODO : Calculate final size here
  this.dispatchResizableEvent_(pear.ui.Resizable.EventType.END_RESIZE, null);
};


/**
 * @param {Element} element
 * @param {goog.math.Size} size
 * @param {goog.math.Coordinate} coord
 * @param {pear.ui.Resizable.Position} position
 * @private
 */
pear.ui.Resizable.prototype.resize_ = function(element, size, coord, position) {
  var newSize = new goog.math.Size(Math.max(size.width, 0),
      Math.max(size.height, 0));
  //376 = LEFT, TOP_LEFT, BOTTOM_LEFT, TOP, TOP_RIGHT
  if (this.minWidth_ > 0) {
    newSize.width = Math.max(newSize.width, this.minWidth_);
    coord.x = ((position & 376) && newSize.width === this.minWidth_) ?
        this.rightX_ : coord.x;
  }
  if (this.maxWidth_ > 0) {
    newSize.width = Math.min(newSize.width, this.maxWidth_);
    coord.x = ((position & 376) && newSize.width === this.maxWidth_) ?
        this.leftX_ : coord.x;
  }
  if (this.minHeight_ > 0) {
    newSize.height = Math.max(newSize.height, this.minHeight_);
    coord.y = ((position & 376) && newSize.height === this.minHeight_) ?
                  this.bottomY_ : coord.y;
  }
  if (this.maxHeight_ > 0) {
    newSize.height = Math.min(newSize.height, this.maxHeight_);
    coord.y = ((position & 376) && newSize.height === this.maxHeight_) ?
        this.topY_ : coord.y;
  }

  this.dispatchResizableEvent_(pear.ui.Resizable.EventType.RESIZE,
      newSize.clone());

  // TODO: this needs to be fixed
  //goog.style.setBorderBoxSize(element, newSize);
  goog.style.setWidth(element, newSize.width);
  goog.style.setHeight(element, newSize.height);

  // 2px are causing issue - i think it's margin , certainly it's not border
  //http://msdn.microsoft.com/en-us/library/hh781509(v=vs.85).aspx
  var marginbox = goog.style.getMarginBox(element);
  coord.x = coord.x - marginbox.left;
  coord.y = coord.y - marginbox.top;

  if (this.getComputedPosition_(element) === 'absolute') {
    goog.style.setPosition(element, coord);
  }
};


/**
 * @param {goog.fx.Dragger} dragger
 * @return {pear.ui.Resizable.Position}
 * @private
 */
pear.ui.Resizable.prototype.getDraggerPosition_ = function(dragger) {
  for (var position in this.handleDraggers_) {
    var p = goog.string.toNumber(position);
    if (this.handleDraggers_[p] === dragger) {
      return (/** @type {pear.ui.Resizable.Position} */(p));
    }
  }
  return pear.ui.Resizable.Position.NONE;
};


/**
 * @return {number}
 */
pear.ui.Resizable.prototype.getMinWidth = function() {
  return this.minWidth_;
};


/**
 * @param {number} width
 */
pear.ui.Resizable.prototype.setMinWidth = function(width) {
  this.minWidth_ = width;
};


/**
 * @return {number}
 */
pear.ui.Resizable.prototype.getMaxWidth = function() {
  return this.maxWidth_;
};


/**
 * @param {number} width
 */
pear.ui.Resizable.prototype.setMaxWidth = function(width) {
  this.maxWidth_ = width;
};


/**
 * @return {number}
 */
pear.ui.Resizable.prototype.getMinHeight = function() {
  return this.minHeight_;
};


/**
 * @param {number} height
 */
pear.ui.Resizable.prototype.setMinHeight = function(height) {
  this.minHeight_ = height;
};


/**
 * @return {number}
 */
pear.ui.Resizable.prototype.getMaxHeight = function() {
  return this.maxHeight_;
};


/**
 * @param {number} height
 */
pear.ui.Resizable.prototype.setMaxHeight = function(height) {
  this.maxHeight_ = height;
};


/**
 * get handle instance
 * @param {pear.ui.Resizable.Position} position
 * @return {Element}
 */
pear.ui.Resizable.prototype.getHandle = function(position) {
  return this.handlers_[position];
};


/**
 * [dispatchResizableEvent description]
 * @param  {string} type [description]
 * @param  {?goog.math.Size} size [description]
 * @private
 */
pear.ui.Resizable.prototype.dispatchResizableEvent_ = function(type, size) {
  var evt = new pear.ui.ResizableEvent(type, this, size);
  this.dispatchEvent(evt);
};


/** @inheritDoc */
pear.ui.Resizable.prototype.disposeInternal = function() {
  pear.ui.Resizable.superClass_.disposeInternal.call(this);

  for (var position in this.handleDraggers_) {
    var p = goog.string.toNumber(position);
    this.handleDraggers_[p].dispose();
  }
  this.handleDraggers_ = {};
  for (var position in this.handlers_) {
    var p = goog.string.toNumber(position);
    goog.events.unlisten(this.handlers_[p], goog.events.EventType.MOUSEDOWN,
        this.handleEvents_);
    goog.dom.removeNode(this.handlers_[p]);
  }
  this.handlers_ = {};
};



/**
 * Object representing GridHeaderCellEvent.
 *
 * @param {string} type Event type.
 * @param {pear.ui.Resizable} target
 * @param {?goog.math.Size} size
 * @extends {goog.events.Event}
 * @constructor
 */
pear.ui.ResizableEvent = function(type, target, size) {
  goog.events.Event.call(this, type, target);

  /**
   * @type {?goog.math.Size}
   */
  this.size = size;
};
goog.inherits(pear.ui.ResizableEvent, goog.events.Event);