1

As you'll see below, I'm trying to write some logic that has to be repeated twice - once for the horizontal direction, and a second time for the vertical direction. The problem is that each direction has different function names and it's not like other scenarios where you can iterate via integers. Is there a more elegant way to rewrite it?

// Some calculations along the horizontal axis
someElementX.addEventListener('some-event', function(e){
  var someResult = e.clientX - thisElement.style.left;
  var someOtherResult = e.clientY; // Also some quick orthogonal calculation
});

// Some calculations along the vertical axis
someElementY.addEventListener('some-event', function(e){
  var someResult = e.clientY - thisElement.style.top;
  var someOtherResult = e.clientX; // Also some quick orthogonal calculation
});

Here's a way I've managed to do it, but while the code duplication is eliminated, I feel like it's incredibly confusing and hardly maintainable.

// Set up a "direction" loop
var loopParams =
  { 0: { xy: 'x', clientXY: { 1: 'clientX', 2: 'clientY' }, side: 'left' } 
  , 1: { xy: 'y', clientXY: { 1: 'clientY', 2: 'clientX' }, side: 'top'  }
  };

// Run the loop for each direction
for( var i = 0; i < 2; i++ )
{
  var xy       = loopParams[i].xy;
  var clientXY = loopParams[i].clientXY;
  var side     = loopParams[i].side;

  someElements[xy].addEventListener('some-event', function(e){
    var someResult      = e[clientXY[1]] - thisElement.style[side];
    var someOtherResult = e[clientXY[2]]; // Some quick orthogonal calculation
  });
}

Any ideas?

Also, I wasn't sure what to call the question. Is there a better terminology to describe what I'm trying to do here?

Anson Kao
  • 5,256
  • 4
  • 28
  • 37
  • 1
    This won't even work because you're missing a closure. Try to use an array literal, and instead of assigning it to a `loopParams` variable use `.forEach` with a callback. But in general, I think the direction is right. – Bergi Aug 07 '14 at 20:16
  • Hi Bergi, what do you mean by missing a closure? Also, a full answer is very welcome and I would gladly upvote! – Anson Kao Aug 07 '14 at 20:21

1 Answers1

2

In general, your approach was fine. However your code has the infamous Loop issue - all installed event handlers will use the variable values from the last iteration.

You should use an array literal instead of an object literal, which also has the advantage that you don't need the loopParams variable and the explicit for-loop when you can just use forEach - getting the closure for free.

[
  {dir:'x', posProp:'clientX', otherProp:'clientY', side:'left'},
  {dir:'y', posProp:'clientY', otherProp:'clientX', side:'top'}
].forEach(function(loopParam) {
  someElements[loopParam.dir].addEventListener('some-event', function(e){
    var someResult      = e[loopParam.posProp] - thisElement.style[loopParam.side];
    var someOtherResult = e[loopParam.otherProp]; // Some quick orthogonal calculation
  });
});

Of course you may use more succint variable & property names.

You do need to weight the benefits of eleminating duplication against the added complexity. For only two similar functions (if they're really as short as in your question) with so many (4) different pieces the advantage gained does not seem large enough to make this rewrite necessary.


Other techniques that could be applied here are a) the closure factory - removing the need for a parameter object:

function addXYHandler(el, posProp, otherProp, side) {
  el.addEventListener('some-event', function(e){
    var someResult      = e[posProp] - thisElement.style[side];
    var someOtherResult = e[otherProp]; // Some quick orthogonal calculation
    // much "duplicate" code
  });
}
addXYHandler(someElementX, 'clientX', 'clientY', 'left');
addXYHandler(someElementY, 'clientY', 'clientX', 'top');

or b) just using different handlers, and outsourcing the common functionality in external functions:

function someResult(…) { … }
function someOtherResult(…) { … }
// or maybe use only one function with multiple parameters
var handlers = {
  x: function(e) {
    someResult(e.clientX - thisElement.style.left);
    someOtherResult(e.clientY);
  },
  y: function(e){
    someResult(e.clientY - thisElement.style.top);
    someOtherResult(e.clientX);
  }
});
for (var dir in handlers)
  someElements[xy].addEventListener('some-event', handlers[dir]);
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Bergi you are a godsend. Thank you. My real code indeed has much more complexity than the quasi-psuedo code I provided, so this refactoring is absolutely necessary. – Anson Kao Aug 07 '14 at 20:52