-
-
Save hfitzwater/1361422324a7cd5f96c5f14a083d9c1c to your computer and use it in GitHub Desktop.
Nested drag and drop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<require from="tree-renderer"></require> | |
<h1> | |
Nested drag and drop | |
</h1> | |
<tree-renderer | |
node.bind="tree" | |
node-view-path="data-item-view.html" | |
node-model-path="data-item-view.js"> | |
</tree-renderer> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {DataItem} from 'data-item.js'; | |
import {TreeNode} from 'tree-node.js'; | |
export class App { | |
tree = new TreeNode( new DataItem('root'), true ); | |
constructor() { | |
let one = new TreeNode( new DataItem('one') ); | |
let oneA = new TreeNode( new DataItem('A') ); | |
let oneAone = new TreeNode( new DataItem('one A one') ); | |
oneA.addChildren( [oneAone] ); | |
let oneB = new TreeNode( new DataItem('B') ); | |
let oneC = new TreeNode( new DataItem('C') ); | |
one.addChildren( [oneA, oneB, oneC] ); | |
let two = new TreeNode( new DataItem('two') ); | |
let twoA = new TreeNode( new DataItem('A') ); | |
let twoB = new TreeNode( new DataItem('B') ); | |
let twoC = new TreeNode( new DataItem('C') ); | |
two.addChildren( [twoA, twoB, twoC] ); | |
this.tree.addChildren( [one, two] ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div style="padding:4px; border: 1px solid #ccc; background-color: #eee; width: 150px; cursor: move;"> | |
${ node.data.name } - ${ node.index } | |
<button style="float:right;" click.delegate="toggleChildren()"> v </button> | |
</div> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class DataItemView { | |
constructor() { | |
} | |
activate( model ) { | |
this.node = model; | |
} | |
toggleChildren() { | |
this.node.showChildren = !this.node.showChildren; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class DataItem { | |
name = null; | |
constructor( name ) { | |
this.name = name; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>Aurelia</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style> | |
.gu-mirror{position:fixed!important;margin:0!important;z-index:9999!important;opacity:.8;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";filter:alpha(opacity=80)}.gu-hide{display:none!important}.gu-unselectable{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.gu-transit{opacity:.2;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";filter:alpha(opacity=20)} | |
</style> | |
</head> | |
<body aurelia-app> | |
<h1>Loading...</h1> | |
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/jspm_packages/system.js"></script> | |
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/config.js"></script> | |
<script src="https://cdn.rawgit.com/bevacqua/dragula/master/dist/dragula.js"></script> | |
<script> | |
System.import('aurelia-bootstrapper'); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div> | |
<div> | |
<compose | |
view.bind="nodeViewPath" | |
view-model.bind="nodeModelPath" | |
model.bind="node"> | |
</compose> | |
</div> | |
</div> | |
<div show.bind="node.showChildren" style="position:relative; left: 15px; min-height: 15px;" class="dropzone"> | |
<div repeat.for="node of node.children" class="draggable"> | |
<!-- | |
model.bind get placed on the element | |
that way we can go from HTMLElement to Aurelia ViewModel | |
http://aurelia.io/hub.html#/doc/article/aurelia/templating/latest/templating-basics/4 | |
--> | |
<node-renderer | |
node.bind="node" | |
model.bind="node" | |
id="${node.id}" | |
node-view-path.bind="nodeViewPath" | |
node-model-path.bind="nodeModelPath" | |
drake.bind="drake"> | |
</node-renderer> | |
</div> | |
</div> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {bindable} from 'aurelia-framework'; | |
export class NodeRenderer { | |
@bindable node; | |
@bindable nodeViewPath; | |
@bindable nodeModelPath; | |
@bindable drake; | |
constructor() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const NODE_EVENTS = { | |
ADD: 'tree-node-add', | |
REMOVE: 'tree-node-remove' | |
}; | |
export class TreeNode { | |
children = []; | |
data = null; | |
isRoot = false; | |
id = null; | |
parent = null; | |
index = 0; | |
showChildren = true; | |
constructor( data, isRoot ) { | |
this.data = data; | |
this.isRoot = isRoot; | |
this.id = this.getGuid(); | |
} | |
static get EVENTS() { | |
return NODE_EVENTS; | |
} | |
addChild( childNode ) { | |
this.addChildren( [childNode] ); | |
} | |
addChildren( children ) { | |
let length = this.children.length; | |
children.forEach( (child, index) => { | |
child.index = length + index; | |
child.parent = this; | |
this.children.push( child ); | |
}); | |
} | |
move( item, before, from, to ) { | |
let node = item; | |
let index = to.children.indexOf( before ); | |
let beforeString = ''; | |
try { | |
beforeString = before.data.name; | |
} catch( ex ) { | |
beforeString = 'the end'; | |
} | |
this.reIndexChildren( to ); | |
if( from !== to ) { | |
this.reIndexChildren( from ); | |
} | |
} | |
reIndexChildren( to, from ) { | |
let dropzone = this.getStageDropzoneElement( to ); | |
let elements = [].slice.call(dropzone.children); | |
elements.forEach( (element, index) => { | |
let child = this.getChildById( element.querySelector('node-renderer').id ); | |
if( child ) { | |
child.index = index; | |
} | |
}); | |
} | |
getChildById( id ) { | |
let root = this; | |
while( root.parent ) { | |
root = root.parent; | |
} | |
let child = this.getChildFromNode( root, id ); | |
if( child ) { | |
return child; | |
} else { | |
console.error('could not find ' + id); | |
} | |
} | |
getChildFromNode( node, id ) { | |
let all = [node]; | |
this.gatherChildren( node, all ); | |
let found = all.find( child => { | |
return child.id === id; | |
}); | |
return found; | |
} | |
gatherChildren( node, all ) { | |
if( !node.children || node.children.length === 0 ) return; | |
node.children.forEach( child => { | |
all.push( child ); | |
this.gatherChildren( child, all ); | |
}); | |
} | |
getStageDropzoneElement( node ) { | |
let nodeRenderer = document.getElementById( node.id ); | |
let dropzone = nodeRenderer.querySelector('.dropzone'); | |
return dropzone; | |
} | |
getGuid() { | |
/* | |
* http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript | |
*/ | |
let s4 = () => { | |
return Math.floor((1 + Math.random()) * 0x10000) | |
.toString(16) | |
.substring(1); | |
}; | |
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | |
s4() + '-' + s4() + s4() + s4(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<require from="node-renderer"></require> | |
<label> | |
Events | |
</label> | |
<div style="color:#fff; background-color:#222; padding:5px;max-height: 120px; height:120px; overflow-y:scroll;"> | |
<div repeat.for="event of loggedEvents"> | |
${ event } | |
</div> | |
</div> | |
<br> | |
<br> | |
<div> | |
<div> | |
<node-renderer | |
node.bind="node" | |
id="${node.id}" | |
node-view-path.bind="nodeViewPath" | |
node-model-path.bind="nodeModelPath" | |
drake.bind="drake"> | |
</node-renderer> | |
</div> | |
</div> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {customElement, bindable, inject, TaskQueue} from 'aurelia-framework'; | |
import {EventAggregator} from 'aurelia-event-aggregator'; | |
const EVENTS = { | |
ADD: 'tree-node-add', | |
REMOVE: 'tree-node-remove' | |
} | |
@customElement( 'tree-renderer' ) | |
@inject( EventAggregator, TaskQueue ) | |
export class TreeRenderer { | |
@bindable node; | |
@bindable nodeViewPath; | |
@bindable nodeModelPath; | |
constructor( events, taskQueue ) { | |
this.events = events; | |
this.taskQueue = taskQueue; | |
this.loggedEvents = []; | |
this.initDrag(); | |
} | |
attached() { | |
let addListener = this.events.subscribe( EVENTS.ADD, (event) => { | |
let beforeText = ''; | |
try { | |
beforeText = event.before.data.name; | |
} catch( ex ) { | |
beforeText = 'the end'; | |
} | |
this.logEvent(`${event.item.data.name} moved to ${event.to.data.name} before ${beforeText}`); | |
}); | |
let removeListener = this.events.subscribe( EVENTS.REMOVE, (event) => { | |
this.logEvent(`${event.item.data.name} removed from ${event.from.data.name}`); | |
}); | |
this.listeners = [ addListener, removeListener ]; | |
} | |
logEvent( text ) { | |
this.loggedEvents.push( text ); | |
console.log( text ); | |
} | |
detached() { | |
this.listeners.forEach( listener => { | |
listener.dispose(); | |
}); | |
} | |
initDrag() { | |
this.drake = dragula({ | |
isContainer: (el) => { | |
return el.classList.contains('dropzone'); | |
}, | |
moves: (el) => { | |
return el.classList.contains('draggable'); | |
} | |
}); | |
this.drake.on( 'drop', (el, target, source, sibling) => { | |
el = el.querySelector('node-renderer'); | |
let moved = el.model; | |
let newParent = this.getNewParent( el ); | |
let oldParent = this.getOldParent( el ); | |
let before = null; | |
if( sibling ) { | |
before = sibling.querySelector('node-renderer').model; | |
} | |
this.taskQueue.queueTask(() => { | |
this.node.move( moved, before, oldParent, newParent ); | |
let addEvent = { | |
item: moved, | |
to: newParent, | |
before: before | |
}; | |
let removeEvent = { | |
item: moved, | |
from: oldParent | |
}; | |
this.events.publish( EVENTS.ADD, addEvent ); | |
if( newParent !== oldParent ) { | |
this.events.publish( EVENTS.REMOVE, removeEvent ); | |
} | |
}); | |
}); | |
} | |
getOldParent( element ) { | |
return element.model.parent; | |
} | |
getNewParent( element ) { | |
let maxDistance = 20; | |
let el = element.parentElement; | |
for( let i=0; i<maxDistance; i++ ) { | |
if( el.tagName.toLowerCase() == 'node-renderer' ) { | |
break; | |
} | |
el = el.parentElement; | |
} | |
return el.model || this.node; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {TreeNode} from 'tree-node.js'; | |
export class Tree { | |
root = null; | |
constructor( rootNode ) { | |
this.root = rootNode; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment