Skip to content
This repository has been archived by the owner on Jul 30, 2018. It is now read-only.

Drag Meta Provider #637

Merged
merged 10 commits into from
Oct 6, 2017
Merged

Drag Meta Provider #637

merged 10 commits into from
Oct 6, 2017

Conversation

kitsonk
Copy link
Member

@kitsonk kitsonk commented Aug 17, 2017

Type: Feature

The following has been addressed in the PR:

  • There is a related issue
  • All code matches the style guide
  • Unit or Functional tests are included in the PR

Description:

This is a proof of concept meta provider which provides drag information to a widget instance. The provider provides a DragResults every time it is called. Those drag results provide if the node is being currently dragged isDragging and any change in position from the last time the provider was called.

An example of how this would look in a real world implementation would be something like this:

export interface ScrollBarProperties extends WidgetProperties {
  horizontal: boolean;
  position: number;
  size?: number;
  sliderMin?: number;
  sliderSize?: number;
  visible?: boolean;
  onScroll?(delta: number): void;
}

class ScrollBar extends WidgetBase<ScrollBarProperties> {
  const { key, horizontal } = this.properties;
  let isDragging = false;
  if (onScroll) {
    const dragResult = this.meta(Drag).get('slider');
    const delta = horizontal ? dragResult.delta.x : dragResult.delta.y;
    isDragging = dragResult.isDragging;
    onScroll(delta);
  }

  // some calculated styles to position the slider
  // the UI can also render different if the slider is being dragged
  return v('div', { key }, [ v('div', { key: 'slider' }) ]);
}

Because the DragController tracks the events from the window level, but is able to attribute it back to a particular node, tracking of the events keeps the drag state accurate. Here is an example of it in action:

dragging_example

Fixes: #571 (in part)

@kitsonk kitsonk changed the title POC: Drag Meta Provider Drag Meta Provider Aug 22, 2017
@kitsonk
Copy link
Member Author

kitsonk commented Sep 7, 2017

We should support pointer events as part of this provider and it should not need to deal with touch/mouse events then.

@dylans dylans added this to the 2017.09 milestone Sep 7, 2017
/**
* The movement of pointer during the duration of the drag state
*/
delta: Position;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we declare the interface before we use it? (I thought it was an from an import)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was favouring alphabetisation over use before declaration... one form of clarity causes another issue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so.

src/meta/Drag.ts Outdated

/**
* Return the delta position between two positions
* @param start The first posistion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

posistion -> position


private _getData(target: HTMLElement): { state: NodeData, target: HTMLElement } | undefined {
if (this._nodeMap.has(target)) {
return { state: this._nodeMap.get(target)!, target };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be so good if TS could know that we are in a block narrowed by this._nodeMap.has(target)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not surprised there’s an issue. That would be nice.

}
}

private _onDragStart = (e: PointerEvent) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why private _onDragStart = (e: PointerEvent) => { over private _onDragStart(e: PointerEvent) {?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we have event over e?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the lambda, is because the handler needs to be bound to the instance. When used as a handler, the this scope is the window I believe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha re scope. Over having a separate variable that binds scope to the method (we have used both patterns)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I like this better for class methods where it effects the code readability less, IMO. Using the bound variable makes a bit of indirection when reading the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, for private methods.

}

constructor() {
const win: Window = global.window;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't want to mask the global. We have tended to now do that, using doc or win when using a reference to a potential global.

src/meta/Drag.ts Outdated
// first time we see a node, we will initialize its state
if (!_nodeMap.has(node)) {
_nodeMap.set(node, {
dragResults: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use emptyResults?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apart from it being frozen... could we just deep assign the emptyResults when we return them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, empty results is valid here, I will use that

src/meta/Drag.ts Outdated

const controller = new DragController();

export default class Drag extends Base {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally we do a named export and a default export across widget-core.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem

src/meta/Drag.ts Outdated
const controller = new DragController();

export default class Drag extends Base {
private boundInvalidate = this.invalidate.bind(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_boundInvalidate

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

src/meta/Drag.ts Outdated

/**
* Return the x/y position for an event
* @param e The MouseEvent or TouchEvent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now the PointerEvent?

Copy link
Contributor

@pottedmeat pottedmeat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor stuff

const controller = new DragController();

export class Drag extends Base {
private _boundInvalidate: () => void = this.invalidate.bind(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should already be a bound version that comes from WidgetBase

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is its own .invalidate which isn't auto bound I believe... because this doesn't come from WidgetBase. I will double check though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the properties passed in at creation provides a bound invalidate, that is a private property on base and the public invalidate() calls that private property that is copied out, therefore when called from event handlers, the Base.invalidate() loses its context and needs to be statically bound. This is likely undesirable, and we should consider ensuring that Base.invalidate() will always call the right invalidate, but for now, this needs to stay this way to work. I will open an issue to consider the change to Base.


class DragController {
private _nodeMap = new WeakMap<HTMLElement, NodeData>();
private _dragging: HTMLElement | undefined = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this be private _dragging?: HTMLElement;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be, but considering I was explicitly assigning undefined, the property will always be on the instance. Felt more accurate to not be optional.

key: 'child1'
}),
v('div', {
innerHTML: 'Hello Wolrd',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it matters, but a typo :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

document.body.removeChild(div);
},

'non draggable node'() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth naming this differently. I was thinking non-draggable meant something marked as non-draggable by CSS or attributes and got confused - but this is testing that a node where drag events haven't been requested doesn't generate drag events.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename it to?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non listening node?

@kitsonk kitsonk merged commit 8fb3882 into dojo:master Oct 6, 2017
@kitsonk kitsonk modified the milestones: 2017.09, 2017.10 Oct 6, 2017
@kitsonk kitsonk added the beta3 label Oct 6, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants