Pick a Position

/* Scrub */

const Scrub = {
  finiteNumber: function(value, defaultValue) {
    return Number.isFinite(value) ? value : defaultValue;
  },
  units: function(value, defaultValue) {
    switch(value) {
      case "px":
      case "pt":
      case "em": return value;
      default: return defaultValue;
    }
  }
};

/* Pairs of Vetted Values */

/* Position */

/** A location expressed in terms of x and y values and units.
 *  Synonymous with OrderedPairWithUnits or OrderedPairAndUnits.
 */
function Position(x, y, units) {
  return OrderedPairWithUnits.call(this, x, y, units);
}
Position.defaultUnits = "px";

/** An object composed of an OrderedPair object and units.
 */
function OrderedPairAndUnits(x, y, units) {

  // Private Constant
  const thisObject= this;
  const orderedPair = new OrderedPair(x, y);

  // Public Methods
  thisObject.getXAndUnits = function() {
    return orderedPair.getX().toString() + units;
  }
  thisObject.getYAndUnits = function() {
    return orderedPair.getY().toString() + units;
  };
  thisObject.getX = function() {
    return orderedPair.getX();
  };
  thisObject.getY = function() {
    return orderedPair.getY();
  };
  thisObject.setX = function(newX) {
    orderedPair.setX(newX);
    return thisObject;
  };
  thisObject.setY = function(newY) {
    orderedPair.setY(newY);
    return thisObject;
  };
  thisObject.getUnits = function() {
    return units;
  };
  thisObject.setUnits = function(newUnits) {
    units = Scrub.units(newUnits, Position.defaultUnits);
    return thisObject;
  };
  thisObject.toString = function() {
    return "(" + thisObject.getXAndUnits() +
           "," + thisObject.getYAndUnits() + ")";
  };

  // Initialization
  thisObject.setUnits(units);

  return thisObject;
}

/** An object that is an OrderedPair object with units.
 */
function OrderedPairWithUnits(x, y, units) {

  // Private Constant
  const thisObject = OrderedPair.call(this, x, y);

  // Public Methods
  thisObject.getXAndUnits = function() {
    return thisObject.getX().toString() + units;
  }
  thisObject.getYAndUnits = function() {
    return thisObject.getY().toString() + units;
  };
  thisObject.getUnits = function() {
    return units;
  };
  thisObject.setUnits = function(newUnits) {
    units = Scrub.units(newUnits, Position.defaultUnits);
    return thisObject;
  };
  thisObject.toString = function() {
    return "(" + thisObject.getXAndUnits() +
           "," + thisObject.getYAndUnits() + ")";
  };

  // Initialization
  thisObject.setUnits(units);

  return thisObject;
}

/* Shapes */

/** An object consisting of an HTML element that represents a shape.
 */
function Shape(opts, getElement) {

  const thisObject = this;
  const element = document.createElement("div");

  if (opts === undefined) opts = {};
  let units = (opts.units !== undefined) ?
      opts.units : Shape.default.units;
  let color = (opts.color !== undefined) ?
      opts.color : Shape.default.color;
  let position = (opts.position !== undefined) ?
      opts.position : Shape.default.position;

  thisObject.appendElementTo = function(otherElement) {
    otherElement.append(element);
  };
  thisObject.removeElementFrom = function(otherElement) {
    otherElement.remove(element);
  }
  thisObject.getUnits = function() {
    return units;
  };
  thisObject.setUnits = function(newUnits) {
    return setUnits(newUnits);
  };
  thisObject.getPosition = function() {
    return position;
  };
  thisObject.setPosition = function(newPosition) {
    return setPosition(newPosition);
  };
  thisObject.getColor = function() {
    return color;
  };
  thisObject.setColor = function(newColor) {
    return setColor(newColor);
  }

  function setPosition(p) {
    element.style.top = p.getYAndUnits();
    element.style.left = p.getXAndUnits();
    return thisObject;
  }
  function setColor(c) {
    element.style.backgroundColor = color;
    return thisObject;
  }
  function setUnits(u) {
    units = u;
    return thisObject;
  }

  element.style.position= "absolute";
  setPosition(position);
  setColor(color);
  setUnits(u);

  return getElement === true ? element : thisObject;
}
Shape.default = {
  position: new Position(),
  units: "px",
  color: "black"
}

/** An object derived from Shape that represents a rectangle.
 */
function Rectangle(width, height, opts, getElement) {

  const thisObject = this;
  const element = Shape.call(thisObject, opts, true);

  if (width === undefined) width = Rectangle.default.width;
  if (height === undefined) height = Rectangle.default.height;
  element.style.width = width.toString() + thisObject.getUnits();
  element.style.height = height.toString() + thisObject.getUnits();

  return getElement === true ? element : thisObject;
}
Rectangle.default = {
  width: 1,
  height: 1,
};

/** An object derived from Rectangle that represents a square.
 */
function Square(width, opts, getElement) {
  return Rectangle.call(this, width, width, opts, getElement);
}

/** An object derived from Square that represents a circle.
 */
function Circle(width, opts, getElement) {

  const thisObject= this;
  const element = Square.call(thisObject, width, opts, true);

  element.style.borderRadius = "50%";

  return getElement === true ? element : thisObject;
}