§ 3. The Shape Constructor Function

O
tell me all about
Anna Livia! I want to hear all

[Click.]

  1. Constructor Function: Shape
function Shape(style) {

  /* Private Fields */

  const thisShape = this;
  const element = document.createElement('div');

  /* Public Properties */

  this.measureProperties.forEach(function (propertyName) {
    Object.defineProperty(thisShape, propertyName, {
      get: function () { return getNumberPart(propertyName); },
      set: function (value) { setMeasure(propertyName, value); }
    });
  });
  this.simpleProperties.forEach(function (propertyName) {
    Object.defineProperty(thisShape, propertyName, {
      get: function () { return element.style[propertyName]; },
      set: function (value) { element.style[propertyName] = value; }
    });
  });
  Object.defineProperty(thisShape, 'angle', {
    get: function () {
      return FunctionCall.arguments(element.style.transform, 'rotate');
    }
  });

  /* Public Methods */

  this.render = function () {
    document.body.appendChild(element);
    return thisShape;
  };
  this.rotate = function (amt) {
    element.style.transform = 'rotate(' + amt + ')';
    return thisShape;
  };
  this.appendChild = function (other) {
    if (other instanceof Shape)
      other.appendTo(element);
    else
      element.appendChild(other);
    return thisShape;
  };
  this.appendTo = function (other) {
    other.appendChild(element);
    return thisShape;
  };

  /* Initialization */

  (function initialize() {
    const thisStyle = element.style;
    assign(thisStyle, thisShape.defaultStyle);
    assign(thisStyle, style);
  })();

  /* Private Utilities */

  function getNumberPart(propertyName) {
    return Measure.separate(element.style[propertyName]).number;
  }
  function setMeasure(property, value) {
    const m = Measure.separate(value.toString());
    const units = m.units.length > 0 ?
      m.units : Measure.units(element.style[property]);
    element.style[property] = m.number + units;
  }
}

Shape.prototype = Object.defineProperties({}, {
  measureProperties: {
    value: Object.freeze([
      'top', 'bottom',
      'left', 'right',
      'width', 'height',
      'borderWidth', 'borderRadius'
    ])
  },
  simpleProperties: {
    value: Object.freeze([
      'transform',
      'backgroundColor',
      'zIndex'
    ])
  },
  defaultStyle: {
    value: Object.freeze({
      backgroundColor: 'black',
      height: '100px',
      width: '100px',
      position: 'absolute'
    })
  },
  containsPoint: {
    value: function(x, y) {      
      return x > this.left &&
        x < this.left + this.width &&
        y > this.top &&
        y < this.top + this.height;
    }
  },
  containsCornerOrMiddle: {
    value: function(o) {

      const top = o.top,
        left = o.left,
        width = o.width,
        height = o.height,
        right = left + width,
        bottom = top + height,
        xMid = (left + right) / 2,
        yMid = (top + bottom) / 2;

      return this.containsPoint(xMid, yMid) ||
        this.containsPoint(xMid, top) ||
        this.containsPoint(right, yMid) ||
        this.containsPoint(xMid, bottom) ||
        this.containsPoint(left, yMid) ||
        this.containsPoint(left, top) ||
        this.containsPoint(right, top) ||
        this.containsPoint(left, bottom) ||
        this.containsPoint(right, bottom);
    }
  },
  intersectsWith: {
    value: function(o) {
      return this.containsCornerOrMiddle(o) ||
        o.containsCornerOrMiddle(this);
    }
  }
});