samedi 19 septembre 2015

Rectangle selection in Ember.js

I am still creating my own list view. I am now tackling the rectangle selection, ie: letting the user draw a rectangle to select several items at once.

Overview of desired flow

  • List receives mousedown event, stores location. ✔
  • List tracks mousemove until either mouseup or mouse goes some distance from stored location. ✔
  • ??? creates an overlay that covers all viewport, and a div inside this. ✘
  • ??? adjusts the div's size and position to materialize a rectangle selection. ✔
  • ??? destroys the div and the overlay on mouseup and apply the selection. ✔

The overlay is required to track the pointer even when it leaves the list and wanders around the page. In a perfect world, we'd use setCapture but it only exists on IE and Firefox.

Also, there are a few gadgets, such as scrolling the list when the selection box touches the edges. Not an issue, but it means there is some communication - bound value or action - needed with the list component if I delegate things.

The issue

How to implement the overlay and div properly in ember. In a way it can be abstracted an re-used.

Attempts

  1. Set some property on the list and let the template create and destroy a sub-component.

    {{#if showSelectionBox}}{{ui-selection-box origin=selectionOrigin rect=selectionBox}}{{/if}}
    
    

    I am not too happy with that one. Thing is, the selection box needs to track the state of the DOM node of the view (calling getBoundingClientRect, getting scrollTop and such). It ends up either accessing parentView.element or passing a DOM element as a bound property. Either way, it seems very fragile.

    Also, the overlay ends up being in the list, and having the overlay's div inside the <ol> seems wrong.

    And last but not least, the component is meant to be inheritable, and requiring the inheriting component to copy-paste a whole block of template code seems inherently bad design.

  2. Plain old DOM manipulation. As ugly and tedious as it sounds. With the creating of the object from the list, tracking its state, old-school callbacks, addEventListener and stuff. There has to be a better way.

  3. Manually instantiating a component and appending it to the application's rootElement.

    var SelectionBox = this.container.lookupFactory('component:ui-selection-box');
    var box = SelectionBox.create({parentView: this, /* ... */});
    box.container = this.container;
    box.appendTo(this.container.lookup('application:main').rootElement);
    
    

    That looked neat when I had the idea. But getting it to work requires diving into the framework internals. So far I set the container, the renderer, actions, got the template lookup right… and it still doesn't completely work. And at this point the code is so tied to the inner workings of ember's component hooks it is likely to break at every new version.

  4. As a service? I did not try that one yet. The service would need to know about the component's DOM element, probably interact with some dedicated outlet on the application template. I believe such a local behavior should not leak that much into the whole application.

So…

Is one of the attempts I made better than the others, and why?

Is there some official / non-official pattern or story for doing this?




Aucun commentaire:

Enregistrer un commentaire