/*
Ractive-decorators-sortable
===========================
Version 0.2.1.
This plugin adds a 'sortable' decorator to Ractive, which enables
elements that correspond to array members to be re-ordered using
the HTML5 drag and drop API. Doing so will update the order
of the array.
==========================
Troubleshooting: If you're using a module system in your app (AMD or
something more nodey) then you may need to change the paths below,
where it says `require( 'Ractive' )` or `define([ 'Ractive' ]...)`.
==========================
Usage: Include this file on your page below Ractive, e.g:
Or, if you're using a module loader, require this module:
// requiring the plugin will 'activate' it - no need to use
// the return value
require( 'Ractive-decorators-sortable' );
Then use the decorator like so:
{{#list}}
- {{.}}
{{/list}}
var ractive = new Ractive({
el: myContainer,
template: myTemplate,
data: { list: [ 'Firefox', 'Chrome', 'Internet Explorer', 'Opera', 'Safari', 'Maxthon' ] }
});
When the user drags the source element over a target element, the
target element will have a class name added to it. This allows you
to render the target differently (e.g. hide the text, add a dashed
border, whatever). By default this class name is 'droptarget'.
You can configure the class name like so:
Ractive.decorators.sortable.targetClass = 'aDifferentClassName';
PS for an entertaining rant about the drag and drop API, visit
http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
*/
var sortableDecorator = (function ( global, factory ) {
'use strict';
// Common JS (i.e. browserify) environment
if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) {
factory( require( 'Ractive' ) );
}
// AMD?
else if ( typeof define === 'function' && define.amd ) {
define([ 'Ractive' ], factory );
}
// browser global
else if ( global.Ractive ) {
factory( global.Ractive );
}
else {
throw new Error( 'Could not find Ractive! It must be loaded before the Ractive-decorators-sortable plugin' );
}
}( typeof window !== 'undefined' ? window : this, function ( Ractive ) {
'use strict';
var sortable,
ractive,
sourceKeypath,
sourceArray,
dragstartHandler,
dragenterHandler,
removeTargetClass,
preventDefault,
errorMessage;
sortable = function ( node ) {
node.draggable = true;
node.addEventListener( 'dragstart', dragstartHandler, false );
node.addEventListener( 'dragenter', dragenterHandler, false );
node.addEventListener( 'dragleave', removeTargetClass, false );
node.addEventListener( 'drop', removeTargetClass, false );
// necessary to prevent animation where ghost element returns
// to its (old) home
node.addEventListener( 'dragover', preventDefault, false );
return {
teardown: function () {
node.removeEventListener( 'dragstart', dragstartHandler, false );
node.removeEventListener( 'dragenter', dragenterHandler, false );
node.removeEventListener( 'dragleave', removeTargetClass, false );
node.removeEventListener( 'drop', removeTargetClass, false );
node.removeEventListener( 'dragover', preventDefault, false );
}
};
};
sortable.targetClass = 'droptarget';
errorMessage = 'The sortable decorator only works with elements that correspond to array members';
dragstartHandler = function ( event ) {
var context = Ractive.getContext(this);
sourceKeypath = context.resolve();
sourceArray = context.resolve('../');
if ( !Array.isArray(context.get('../')) ) {
throw new Error( errorMessage );
}
event.dataTransfer.setData( 'foo', true ); // enables dragging in FF. go figure
// keep a reference to the Ractive instance that 'owns' this data and this element
ractive = context.ractive;
};
dragenterHandler = function () {
var targetKeypath, targetArray, array, source, context;
context = Ractive.getContext(this);
// If we strayed into someone else's territory, abort
if ( context.ractive !== ractive ) {
return;
}
targetKeypath = context.resolve();
targetArray = context.resolve('../');
// if we're dealing with a different array, abort
if ( targetArray !== sourceArray ) {
return;
}
// if it's the same index, add droptarget class then abort
if ( targetKeypath === sourceKeypath ) {
this.classList.add( sortable.targetClass );
return;
}
// remove source from array
source = ractive.get(sourceKeypath);
array = Ractive.splitKeypath(sourceKeypath);
ractive.splice( targetArray, array[ array.length - 1 ], 1 );
// the target index is now the source index...
sourceKeypath = targetKeypath;
array = Ractive.splitKeypath(sourceKeypath);
// add source back to array in new location
ractive.splice( targetArray, array[ array.length - 1 ], 0, source );
};
removeTargetClass = function () {
this.classList.remove( sortable.targetClass );
};
preventDefault = function ( event ) { event.preventDefault(); };
Ractive.decorators.sortable = sortable;
return sortable;
}));
// Common JS (i.e. browserify) environment
if ( typeof module !== 'undefined' && module.exports) {
module.exports = sortableDecorator;
}