quinta-feira, 8 de janeiro de 2015

jQuery UI Draggable / Resizable with constrain and CSS scale transformation

There are a bunch of questions and solutions to the problems between the interaction of these technologies, these are the better solutions that I found:
  1. http://stackoverflow.com/questions/10212683/jquery-drag-resize-with-css-transform-scale
  2. https://gungfoo.wordpress.com/2013/02/15/jquery-ui-resizabledraggable-with-transform-scale-set/
  3. http://stackoverflow.com/questions/17098464/jquery-ui-draggable-css-transform-causes-jumping
Although they make an improvement, they aren't totally accurate. What they basically do is a calculation of the right position / size that the object must have after the interaction.

You can experiment with the example below, drag the small square to right. Your mouse will be outside the parent div before you hit the edge.

And now, the same example, with the solution presented in [3].

Nice, now the small square moves with the mouse, but it go beyond the parent when dragging or resizing and this was the tricky part to solve. I spend a day trying to figure out how I could solve this, so I'm sharing my solution.

First let's solve the draggable problem. The "bug" (jQuery UI guys don't want to address it, so it's not a bug) occurs because inside jQuery UI it's use absolute event positions.

Think about the parent without the scale, it will be bigger, right? So it's size to jQuery UI is beyond the limit of the scaled down version. What we need to do is inform jQuery UI that our representation is smaller.

I tried in many ways not monkey patch jQuery UI, but these efforts were fruitless. I had to expose the "contaiment" var in the ui parameter passed to callbacks. With this little modification I could use the start and stop callbacks to make jQuery UI work with my scaled containment sizes.

var dragFix, startFix, stopFix;

window.myApp = {
  layout: {
    zoomScale: 1
  },
  draggable: {
    _uiHash: function() {
      return {
        helper: this.helper,
        position: this.position,
        originalPosition: this.originalPosition,
        offset: this.positionAbs,
        containment: this.containment
      };
    }
  }
};

$.ui.draggable.prototype._uiHash = myApp.draggable._uiHash;

startFix = function(event, ui) {
  ui.containment[2] *= myApp.layout.zoomScale;
  return ui.containment[3] *= myApp.layout.zoomScale;
};

stopFix = function(event, ui) {
  ui.containment[2] /= myApp.layout.zoomScale;
  return ui.containment[3] /= myApp.layout.zoomScale;
};

dragFix = function(event, ui) {
  var deltaX, deltaY;
  deltaX = ui.position.left - ui.originalPosition.left;
  deltaY = ui.position.top - ui.originalPosition.top;
  ui.position.left = ui.originalPosition.left + deltaX / myApp.layout.zoomScale;
  return ui.position.top = ui.originalPosition.top + deltaY / myApp.layout.zoomScale;
};

Nice and clean, don't you think? After solving this, I guess that making resizable works would be easy. The inner workings of this can't be very different, right? Wrong, dead wrong. What I think will be solved in 5 minutes, take the hole day.

The resizable code is very different. I expected to see the same algorithms to apply movement constraints and other operations. This way, I had to find a new way to inform jQuery UI about my constraints.

After a day tinkering with resizable code trying to find a solution that need minimal changes to jQuery UI, like with the draggable code, I was unable to find a solution that I liked.

First, I realized that I need to change the methods e, w, n and s in _change to get correct widths and heights according to my zoom scale. This was something that could be done in the "resize" callback, but in this case it's too late in the algorithm, the scaled position is needed by internal methods, before we get a chance to change it.

After this I thought that I had ended, but resizing an element that isn't in the position 0, 0 make it grow beyond the edge of the parent. Digging a bit more, I found that I need some way to change the "woset" and "hoset" calculation, but I don't find anyway to do this without monkey patching the entire method.

The final solution is this:

Maybe you are asking yourself why I'm monkey patching. This is because I'm use Rails and I want to have the benefits of the asset pipeline.

I'm not very proud of it, so I would love to know better ways to accomplish my final result in a simple manner. If you know any, please share!

sábado, 3 de janeiro de 2015

New kid on the S3 direct upload block

Sometime ago I saw the post [Small Bites] Direct Upload para S3: a Solução Definitiva! do Akita. Ow, fantastic! I was just playing with S3 Direct Uploads solutions that are described around the web and that could save me lots of time and give me a well polished solution.

I start playing with refile and how I could integrate it with my application. It don't take much time to became disappointed.

I already use paperclip in my application, it's very well integrated, meets my requirements and I'd like how it organize files, so adapt what I have to refile would be a pain, but it opened my head that S3 Direct Upload can be simpler, so I decide to experiment.

I'd like how s3_direct_upload interact with S3, keeping the filename. This way I decide to make a hybrid of s3_direct_upload and refile: s3-upnow has born.

The idea of the gem is to be backend agnostic. For now it works only with paperclip, so if it's your upload gem, give a try to s3-upnow and your feedback. I guess that support others upload gems is not difficult.

I know that it's not well polished, but it's already working for me and maybe can work for you. If you find scenarios that it's causing problems, please let me know!

Happy hacking!