Skip to content

Instantly share code, notes, and snippets.

@seungjoolee
Last active October 6, 2016 21:55
Show Gist options
  • Save seungjoolee/52bcb30305c0dcd08998cbfe9f21b545 to your computer and use it in GitHub Desktop.
Save seungjoolee/52bcb30305c0dcd08998cbfe9f21b545 to your computer and use it in GitHub Desktop.
drag drop
import Ember from 'ember';
import { computed } from 'ember';
import { extend } from '../utils/keys';
const DATA_TRANSFER_TYPE = 'text';
/**
* Hello! I'm a basic wrapper component for HTML5 drag and drop behavior
*/
export default Ember.Component.extend({
// passed in attributes
isDraggable: false, // use this flag to turn on/off dragging behavior
isDroppable: false, // use this flag to enable/disable this element as a drop target
data: '', // a string to pass along with the element
dragHandleSelector: null, // (optional) a CSS selector of the part of this element from
// where a drag can be initiated
dragEffectAllowed: 'all', // HTML5 drag property that tells the drop area which kinds of actions
// are supported by this draggable element when it is dropped
dropEffect: 'move', // HTML5 drop property that indicates what kind of action will occur
// when an element is dropped on this element
// this component maintains this state
isHovered: false, // can't use :hover because of a webkit but with :hover being overly persistent
// with drag and drop: http://stackoverflow.com/questions/17946886/hover-sticks-to-element-on-drag-and-drop
isDragging: false,
isDraggedOver: false,
dragTarget: null, // the target element of the drag (which part of the element the mouse is on)
$dragGhost: null, // the JQuery element that we are moving around with the drag
// we do this because HTML5 drag and drop doesn't do a good job with that
classNames: ['sq-drag-drop'],
classNameBindings: [
'isHovered:sq-drag-drop--hovered',
'isDraggable:sq-drag-drop--draggable',
'isDragging:sq-drag-drop--dragging',
'isDroppable:sq-drag-drop--droppable',
'isDraggedOver:sq-drag-drop--dragged-over'
],
attributeBindings: ['draggable'],
draggable: Ember.computed.readOnly('isDraggable'),
/* BEGIN REGULAR MOUSE EVENTS *******************/
mouseEnter() {
this.set('isHovered', true);
},
mouseLeave() {
this.set('isHovered', false);
},
mouseDown(evt) {
this.set('dragTarget', evt.target);
},
mouseUp() {
this.set('dragTarget', null);
},
/* BEGIN DRAGGABLE EVENTS *******************/
dragStart(evt) {
if (!this._shouldAllowDragStart()) {
return false;
}
const eventData = this._eventData(evt, {
dragData: this.get('data')
});
const $dragGhost = this._createDragGhost(eventData);
this.set('isDragging', true);
evt.dataTransfer.setData(DATA_TRANSFER_TYPE, this.get('data'));
evt.dataTransfer.effectAllowed = this.get('dragEffectAllowed');
evt.dataTransfer.setDragImage(
$dragGhost[0],
eventData.offsetX,
eventData.offsetY
);
this.sendAction('onDragStart', eventData);
return true;
},
drag(evt) {
const eventData = this._eventData(evt, {
dragData: this.get('data')
});
this.sendAction('onDrag', eventData);
},
dragEnd(evt) {
this._clearDragGhost();
this.set('isDragging', false);
this.sendAction('onDragEnd', this._eventData(evt, {
dragData: this.get('data')
}));
},
/* BEGIN DROP TARGET EVENTS *******************/
dragEnter(evt) {
this.set('isDraggedOver', true);
evt.dataTransfer.dropEffect = this.get('dropEffect');
this.sendAction('onDragEnter', this._eventData(evt, {
// unfortunately HTML5 dnd doesn't let you know what is being dragged over this
dropData: this.get('data')
}));
evt.preventDefault();
},
dragOver(evt) {
this.sendAction('onDragOver', this._eventData(evt, {
// unfortunately HTML5 dnd doesn't let you know what is being dragged over this
dropData: this.get('data')
}));
evt.preventDefault();
},
dragLeave(evt) {
this.set('isDraggedOver', false);
this.sendAction('onDragLeave', this._eventData(evt, {
// unfortunately HTML5 dnd doesn't let you know what is being dragged over this
dropData: this.get('data')
}));
},
drop(evt) {
this.set('isDraggedOver', false);
this.sendAction('onDrop', this._eventData(evt, {
dragData: evt.dataTransfer.getData(DATA_TRANSFER_TYPE),
dropData: this.get('data')
}));
evt.preventDefault();
},
/* BEGIN HELPERS *******************/
_shouldAllowDragStart() {
const dragHandleSelector = this.get('dragHandleSelector');
if (!dragHandleSelector) {
return true; // we don't care where the drag was initiated
}
// make sure the drag was initiated by the specified drag handle
const dragTarget = this.get('dragTarget');
return Ember.$(dragTarget).is(dragHandleSelector);
},
_createDragGhost() {
// notes: find a way to hide cloned div under empty spot
// dragging css not working
const $this = this.$('.sq-drag-drop__drop-content');
const $dragGhost = $this.clone()
// .addClass('sq-drag-drop--dragging') // todo: figure out why classNameBindings isn't doing this for us
.css({
// give the clone the right dimensions, and move it off screen
// we can't use display: none or anything because then HTML5 dnd doesn't
// draw the correct preview, so we use this hack to make it "invisible"
// See http://www.kryogenix.org/code/browser/custom-drag-image.html
position: 'absolute',
top: '0',
left: '0',
width: `${$this.width()}px`,
height: `${$this.height()}px`,
'z-index': -1
});
Ember.$('body').append($dragGhost);
this.set('$dragGhost', $dragGhost);
return $dragGhost;
},
_clearDragGhost() {
const $dragGhost = this.get('$dragGhost');
if ($dragGhost && $dragGhost.length > 0) {
$dragGhost.remove();
}
this.set('$dragGhost', null);
},
_eventData(evt, extraParams) {
const params = (evt && evt.originalEvent) || evt;
return extend(true, {}, {
// mouse position relative to page
pageX: params.pageX,
pageY: params.pageY,
// mouse position relative to target element
// (which is the dragged element for drag events,
// and the drop target element for drop events)
offsetX: params.offsetX,
offsetY: params.offsetY
}, extraParams);
}
});
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
actions: {
onWidgetDragStart() {
},
onWidgetDragEnd() {
console.log('drag end')
},
onWidgetDragOver() {
console.log('drag over')
},
}
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.a {
height: 200px;
width: 200px;
background-color: teal;
}
.l-pulse-fluid {
position: relative;
}
/**
.sq-drag-drop__drop-area {
height: 100%;
display: none;
}
.sq-drag-drop--dragging {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
.sq-drag-drop--dragging .a {
display: none;
}
.sq-drag-drop--dragging .sq-drag-drop__drop-area {
display: block;
border: dashed 2px $color-light;
}
**/
{{#sq-drag-drop class="l-pulse-fluid"
dragHandleSelector=".a"
isDraggable=true
isDroppable=true
data="a"
onDragStart="onWidgetDragStart"
onDragOver="onWidgetDragOver"
onDrop="onWidgetDrop"
onDragEnd="onWidgetDragEnd"}}
<div class="a">
hello
</div>
{{/sq-drag-drop}}
<div class="sq-drag-drop__drop-content">{{yield}}</div>
<div class="sq-drag-drop__drop-area"></div>
{
"version": "0.10.5",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.8.0",
"ember-data": "2.8.0",
"ember-template-compiler": "2.8.0",
"ember-testing": "2.8.0"
},
"addons": {}
}
import Ember from 'ember';
export function transformData(data, transform) {
switch (Ember.typeOf(data)) {
case 'array':
return data.map(item => transformData(item, transform));
case 'object': {
const out = {};
forOwnProperty(data, (key, value) => {
out[transform(key)] = transformData(value, transform);
});
return out;
}
default:
return data;
}
}
export function camelize(data) {
return transformData(data, key => key.camelize());
}
export function underscore(data) {
return transformData(data, key => {
if (key.match(/[A-Z].*\d+$/)) {
// Multiple words followed by a number
return key.replace(/([a-z])(\d+)/g, '$1_$2').underscore();
} else {
return key.underscore();
}
});
}
export function extend(deep, target, ...sources) {
if (deep !== true && deep !== false) {
return extend(false, deep, target, ...sources);
}
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
if (source) {
forOwnProperty(source, (key, value) => {
if (deep && Ember.typeOf(value) === 'object') {
target[key] = extend(true, {}, target[key], value);
} else if (deep && Ember.typeOf(value) === 'array') {
target[key] = value.map(item => extend(true, {}, { item }).item);
} else {
target[key] = value;
}
});
}
}
return target;
}
export function checker(...validKeys) {
if (validKeys.length === 1 && Ember.isArray(validKeys[0])) {
validKeys = validKeys[0];
}
return {
check(object) {
forOwnProperty(object, key => {
if (validKeys.indexOf(key) === -1) {
throw new Error(`unexpected key: ${key}`);
}
});
}
};
}
function forOwnProperty(object, iterator) {
for (const key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
iterator(key, object[key]);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment