Feature requirements.
- I can move a square box.
- I can resize a square box by stretching four edges.
- Many square boxes can exist.
I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class
. But I think that using module pattern is better in this case.
Module Pattern
- Pros
- Can manage only focused element. Don't need to care about unfocused elements.
- Can control event handling in it.
- Can hide private variables and methods.
- Cons
- Maybe need many changes by changing features.
- Test issue.
Class
- Pros
- Can manage and maintain properties of each square box.
- Can be more clean code.
- Cons
- Should manage many instances.
- Should add additional information to the DOM for finding each instance.
So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?
https://jsfiddle.net/docf1t40/2/
<!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}
function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}
function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}
function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}
function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;
switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}
width = Math.max(50, width);
height = Math.max(50, height);
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}
function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate\((-?\d+.?\d*)px ?, ?(-?\d+.?\d*)px\)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}
focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);
userAction = e.target.dataset.userAction;
}
}
function onMouseUp(e) {
blurElement();
userAction = null;
}
function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}
return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();
document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>