building a reorderable list (via dnd) using core-l

2019-08-03 12:40发布

问题:

I'm trying to build a reusable polymer web component that supports reordering items via drag and drop in a core-list. The interface I want is essentially the same as core-list

<reorderable-list id="list" data="{{data}}" height="60">
  <template>
    <some-reorderable-element>
    </some-reorderable-element>
  </template>
</reorderable-list>

My current thinking is that I want each row to be wrapped as follows

<a-container>
    <draggable-member>
       <some-reorderable-element>
       </some-reorderable-element>
    </draggable-member>
</a-container>

where a-container always remains in place and is sized exactly the size of a row and draggable-member gets detached on drag from a-container and attached to the core-drag-drop avatar. As a-container remains in place this should keep the row there with just the background colour I'm hoping.

The part I'm struggling with is how to instantiate the above structure for each row.

I was heading down a path like

<core-list-dart id="list" data="{{data}}" height="{{height}}">
  <template>
    <a-container>
      <draggable-member id="member" draggable="true">
        <content></content>
      </draggable-member>
    </a-container>
  </template>
</core-list-dart>

Not sure if this is heading in the right direction. Catch is that core-list is only going to instantiate copies of a-container. Somehow I'd need to instantiate copies of draggable-member and the user provided content for each row too but not sure how best to do that or if this is even a good approach.

Edit

Well after pulling my hair out for a few hours I thought I had the answer by doing it programatically like

@CustomTag('reorderable-list')
class ReorderableList extends PolymerElement with Observable {
  @published ObservableList data;
  @published double height;


  ReorderableList.created() : super.created() {
  }

  attached() {
    final CoreList coreList = new Element.tag('core-list-dart');
    coreList.data = data;
    coreList.height = height;

    final TemplateElement template = new Element.tag('template');

    final MemberContainer mc = new Element.tag('member-container');
    final ReorderableMember rm = new Element.tag('reorderable-member');
    rm.children.addAll(children);
    mc.append(rm);
    template.content.append(mc);
    coreList.append(template);

    shadowRoot.append(coreList);
  }
}

This almost gives me what I want but not quite as I'm not putting things in the shadow dom. Back to the drawing board :-(

Edit2

I thought I had it nailed but now it seems that the ContentElement behaves in mysterious ways. When the row items are using <content></content> then this content disappears when you detach the component from it's usual parent and add it to the avatar. Presumably the ContentElement works dynamically and can no longer find the associated content. I'll have to get a better understanding of how that works to see if I can get around it.

<link rel="import" href="../../../../packages/core_elements/core_style.html">
<link rel="import" href="../../../../packages/core_elements/core_drag_drop.html">

<core-style id="draggable-list-item" unresolved>
  #row-container, #drag-container {
    padding: none;
    margin: none;
    display: block;
  }

  #row-container {
    border: solid 1px black;
  }
}

</core-style>

<polymer-element name="draggable-list-item" on-drag-start="{{startDrag}}">
  <template>

    <core-style ref="draggable-list-item"></core-style>
    <core-drag-drop></core-drag-drop>

    <div id="row-container">
      <drag-container id="drag-container">
        <content></content>
      </drag-container>
    </div>

  </template>
  <script type="application/dart" src="draggable_list_item.dart"></script>
</polymer-element>


<polymer-element name="drag-container" draggable="true">
  <template>
    <style>
      #foo {
        border: solid 1px blue;
        height: 100%;
        width: 100%;
      }
    </style>

    <div id="foo">
      <p>Hola</p>
      <content></content>
    </div>
  </template>
</polymer-element>

--

library ui.polymer.core.list.item.draggable;

import 'package:polymer/polymer.dart';
import 'dart:html';
import 'package:logging/logging.dart';
import 'dart:js';

Logger _log = new Logger('ui.polymer.core.item.draggable');


@CustomTag('draggable-list-item')
class DraggableListItem extends PolymerElement with Observable {
  bool multi;

  DraggableListItem.created() : super.created() {
    _log.finer('DraggableListItem.created');
  }
  void startDrag(CustomEvent e, JsObject dragInfo) {
    _log.finest('DraggableListItem dragging ${identityHashCode(e)} '
      '${e.target}, ${e.currentTarget}, ${e.detail}, ${dragInfo}');
    _log.finest('e.target == e.currentTarget => ${identical(e.target, e.currentTarget)}');
    final HtmlElement avatar = dragInfo['avatar'];

    final rect = getBoundingClientRect();

    avatar.style.setProperty('width', '${rect.width}px');
    avatar.style.setProperty('height', '${rect.height}px');

    final dragContainer = $['drag-container'];

    final rowContainer = $['row-container'];

    rowContainer.style.setProperty('width', '${rect.width}px');
    rowContainer.style.setProperty('height', '${rect.height}px');

    dragContainer.style.setProperty('width', '${rect.width}px');
    dragContainer.style.setProperty('height', '${rect.height}px');

    avatar.append(dragContainer.clone(true));
    dragContainer.style.display = 'none';
    dragInfo['drag'] = drag;
    dragInfo['drop'] = drop;
  }

  drag(JsObject dragInfo) {
    print('drag');
    final HtmlElement avatar = dragInfo['avatar'];
    print(avatar);
  }
  drop(JsObject dragInfo) {
    print('drop');
    final HtmlElement avatar = dragInfo['avatar'];
    print(avatar);
  }
}


@CustomTag('drag-container')
class DragContainer extends PolymerElement with Observable {
  bool multi;

  DragContainer.created() : super.created() {
    _log.finer('DragContainer.created');
    print('DragContainer.created');
  }
}

--

<core-list-dart height="100" data="{{data}}">
  <template>
    <draggable-list-item>
      <test-row></test-row>
    </draggable-list-item>
  </template>
</core-list-dart>

回答1:

I got my attempt to wrap the <core-list-dart> working so far (without drag-n-drop).

import 'package:polymer/polymer.dart';
import 'package:core_elements/core_list_dart.dart';

@CustomTag('reorderable-list')

class ReorderableList extends PolymerElement {

  /// Constructor used to create instance of ReorderableList.
  ReorderableList.created() : super.created();

  @PublishedProperty(reflect: true)
  List<String> data;

  attached() {
    super.attached();
    var template = querySelector('template');
    var coreList = (shadowRoot.querySelector('core-list-dart') as CoreList);
    coreList.append(template);
  }
}
<link rel="import" href="../../packages/polymer/polymer.html">
<link rel="import" href="../../packages/core_elements/core_list_dart.html">

<polymer-element name="reorderable-list">
  <template>
    <core-list-dart data="{{data}}"></core-list-dart>
    <content></content>
  </template>
  <script type="application/dart" src="reorderable_list.dart"></script>
</polymer-element>

usage (basically the same as <core-list-dart>)

<link rel="import" href="../../packages/polymer/polymer.html">
<link rel="import" href="../../packages/core_elements/core_list_dart.html">
<link rel="import" href="drag_container.html">
<link rel="import" href="draggable_member.html">
<link rel="import" href="test_element.html">
<link rel="import" href="reorderable_list.html">

<polymer-element name="app-element">
  <template>
    reorderable-list
    <reorderable-list data="{{strings}}">
      <template id="app-element-reorderable">
        <drag-container>
          <draggable-member id="member" draggable="true">
            <test-element>{{name}}</test-element>
          </draggable-member>
        </drag-container>
      </template>
    </reorderable-list>

  </template>
  <script type="application/dart" src="app_element.dart"></script>
</polymer-element>
import 'package:polymer/polymer.dart';

class Item {
  String name;
  Item(this.name);
  bool selected = false;

  String toString() => name;
}

@CustomTag('app-element')

class AppElement extends PolymerElement {

  @observable
  List strings = toObservable([new Item('Anton'), new Item('Berta'), new Item('Caesar'), new Item('Dora')]);

  AppElement.created() : super.created();
}

I think it should work when <reorderable-list> implements the drag-n-drop event handlers.



回答2:

I've added all this code to https://bitbucket.org/andersmholmgren/ah_polymer_stuff. Everyone is free to make use of this.

I'm very close now. It is working in that I can move rows around.

The last obstacle is that I'm losing part of the row contents in the avatar during drag. I'm pretty sure it comes from my last trick.

The element is now defined as

<polymer-element name="draggable-list-item" on-drag-start="{{startDrag}}">
  <template>

    <core-style ref="draggable-list-item"></core-style>
    <core-drag-drop></core-drag-drop>

    <div id="row-container">
      <div id="drag-container">
        <content></content>
      </div>
    </div>

  </template>
  <script type="application/dart" src="draggable_list_item.dart"></script>
</polymer-element>

and you use it like

<core-list-dart height="100" data="{{data}}">
  <template>
    <draggable-list-item>
      <test-row data="{{self}}"></test-row>
    </draggable-list-item>
  </template>
</core-list-dart>

where test-row looks like

<polymer-element name="test-row"  unresolved>
  <template>
    <h1>hello I'm a row {{data}}</h1>
  </template>
</polymer-element>

So the test row (<test-row data="{{self}}"></test-row>) is what ends up as the content in

<div id="row-container">
  <div id="drag-container">
    <content></content>
  </div>
</div>

As I mentioned earlier ContentElement behaves in mysterious ways. In order to get it to render properly I did the following

final dragContainer = $['drag-container'];

final ContentElement content = dragContainer.querySelector('content');
print(content.getDistributedNodes());
final dragDiv = new Element.tag('div');
final clonedNodes = content.getDistributedNodes().map((n) =>
    n.clone(true));
clonedNodes.forEach((n) {
  dragDiv.append(n);
});

dragDiv.style.width = '${rect.width}px';
dragDiv.style.height = '${rect.height}px';
dragDiv.style.border = "solid blue 1px";

avatar.append(dragDiv);
dragContainer.style.display = 'none';

I had to get and clone the distributed nodes of the content. This works fine except my suspicion is that this is also why I lose part of the row on drag.

Normally the rows render as

hello I'm a row data item null with value 2

the data item null with value 2 part comes from the toString of the data class I'm using.

On drag that part disappears and all that displays is

hello I'm a row

Somewhere along the line that part disappears and I'm not sure how to fix it

Sadly this only works in dartium. In chrome when you drag the avatar seems to just stay top left and never moves. I suspect this might just be a big in the core_drag_drop in dart.