Browse Source

+ v0.1.0

pull/10/head
RubaXa 11 years ago
commit
6ace554483
  1. 1
      .gitignore
  2. 31
      Gruntfile.js
  3. 46
      README.md
  4. 474
      Sortable.js
  5. 2
      Sortable.min.js
  6. 387
      index.html
  7. 22
      package.json

1
.gitignore vendored

@ -0,0 +1 @@
node_modules

31
Gruntfile.js

@ -0,0 +1,31 @@
'use strict';
module.exports = function (grunt){
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
version: {
src: '<%= pkg.exportName %>.js'
},
uglify: {
options: {
banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n'
},
dist: {
files: {
'<%= pkg.exportName %>.min.js': ['<%= pkg.exportName %>.js']
}
}
}
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-version');
grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task.
grunt.registerTask('default', ['version', 'uglify']);
};

46
README.md

@ -0,0 +1,46 @@
# Sortable
## Features
* Support touch devices
* Built using native HTML5 drag and drop API
* Simple API
* Lightweight, 2KB gzipped
* No jQuery
### Usage
```html
<ul id="items">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
```
```js
new Sortable(items);
```
### Options
```js
new Sortable(elem, {
group: "name",
handle: ".my-handle", // Restricts sort start click/touch to the specified element
draggable: ".item", // Specifies which items inside the element should be sortable
ghostClass: "sortable-ghost",
onAdd: function (evt){
var itemEl = ui.detail;
},
onUpdate: function (evt){
var itemEl = ui.detail; // the current dragged HTMLElement
},
onRemove: function (evt){
var itemEl = ui.detail;
}
});
```

474
Sortable.js

@ -0,0 +1,474 @@
/**!
* Sortable
* @author RubaXa <trash@rubaxa.org>
* @license MIT
*/
(function (factory){
"use strict";
if( typeof define === "function" && define.amd ){
define("Sortable", [], factory);
}
else {
window["Sortable"] = factory();
}
})(function (){
"use strict";
var
dragEl
, ghostEl
, rootEl
, nextEl
, lastEl
, lastCSS
, activeGroup
, tapEvt
, touchEvt
, expando = 'Sortable' + (new Date).getTime()
, win = window
, document = win.document
, parseInt = win.parseInt
, Event = win.CustomEvent
, noop = function (){}
, slice = [].slice
, touchDragOverListeners = []
;
/**
* @class Sortable
* @param {HTMLElement} el
* @param {Object} [options]
* @constructor
*/
function Sortable(el, options){
this.el = el; // root element
this.options = options = (options || {});
// Defaults
options.group = options.group || Math.random();
options.handle = options.handle || null;
options.draggable = options.draggable || el.children[0] && el.children[0].nodeName || 'li';
options.ghostClass = options.ghostClass || 'sortable-ghost';
options.onAdd = _bind(this, options.onAdd || noop);
options.onUpdate = _bind(this, options.onUpdate || noop);
options.onRemove = _bind(this, options.onRemove || noop);
el[expando] = options.group;
// Bind all prevate methods
for( var fn in this ){
if( fn.charAt(0) === '_' ){
this[fn] = _bind(this, this[fn]);
}
}
// Bind events
_on(el, 'add', options.onAdd);
_on(el, 'update', options.onUpdate);
_on(el, 'remove', options.onRemove);
_on(el, 'mousedown', this._onTapStart);
_on(el, 'touchstart', this._onTapStart);
_on(el, 'dragover', this._onDragOver);
_on(el, 'dragenter', this._onDragOver);
touchDragOverListeners.push(this._onDragOver);
}
Sortable.prototype = {
constructor: Sortable,
_applyEffects: function (){
_toggleClass(dragEl, this.options.ghostClass, true);
},
_onTapStart: function (evt/**TouchEvent*/){
var
touch = evt.touches && evt.touches[0]
, target = (touch || evt).target
, options = this.options
;
if( options.handle ){
target = _closest(target, options.handle, this.el);
}
target = _closest(target, options.draggable, this.el);
if( target && !dragEl ){
tapEvt = evt;
target.draggable = true;
// Disable "draggable"
_find(target, 'a', _disableDraggable);
_find(target, 'img', _disableDraggable);
if( touch ){
// Touch device support
tapEvt = {
target: target
, clientX: touch.clientX
, clientY: touch.clientY
};
this._onDragStart(tapEvt, true);
evt.preventDefault();
}
_on(this.el, 'dragstart', this._onDragStart);
_on(document, 'dragover', _globalDragOver);
try {
if( document.selection ){
document.selection.empty();
} else {
window.getSelection().removeAllRanges()
}
} catch (err){ }
}
},
_emulateDragOver: function (){
if( touchEvt ){
_css(ghostEl, 'display', 'none');
var
target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY)
, parent = target
, group = this.options.group
, i = touchDragOverListeners.length
;
do {
if( parent[expando] === group ){
while( i-- ){
touchDragOverListeners[i]({
clientX: touchEvt.clientX,
clientY: touchEvt.clientY,
target: target,
rootEl: parent
});
}
break;
}
}
while( parent = parent.parentNode );
_css(ghostEl, 'display', '');
}
},
_onTouchMove: function (evt){
if( tapEvt ){
var
touch = evt.touches[0]
, dx = touch.clientX - tapEvt.clientX
, dy = touch.clientY - tapEvt.clientY
;
touchEvt = touch;
_css(ghostEl, 'webkitTransform', 'translate3d('+dx+'px,'+dy+'px,0)');
}
},
_onDragStart: function (evt/**Event*/, isTouch){
var
target = evt.target
, dataTransfer = evt.dataTransfer
;
rootEl = this.el;
dragEl = target;
nextEl = target.nextSibling;
activeGroup = this.options.group;
if( isTouch ){
var rect = target.getBoundingClientRect(), css = _css(target);
ghostEl = target.cloneNode(true);
_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
_css(ghostEl, 'width', rect.right - rect.left);
_css(ghostEl, 'height', rect.bottom - rect.top);
_css(ghostEl, 'opacity', '0.8');
_css(ghostEl, 'position', 'fixed');
_css(ghostEl, 'zIndex', '100000');
target.parentNode.insertBefore(ghostEl, target);
// Bind touch events
_on(document, 'touchmove', this._onTouchMove);
_on(document, 'touchend', this._onDrop);
this._loopId = setInterval(this._emulateDragOver, 100);
}
else {
dataTransfer.effectAllowed = 'move';
dataTransfer.setData('Text', target.textContent);
_on(document, 'drop', this._onDrop);
}
setTimeout(this._applyEffects);
},
_onDragOver: function (evt){
if( activeGroup === this.options.group && (evt.rootEl === void 0 || evt.rootEl === this.el) ){
var
el = this.el
, target = _closest(evt.target, this.options.draggable, el)
;
if( el.children.length === 0 || target == null ){
el.appendChild(dragEl);
}
else if( target && (target !== dragEl) ){
if( lastEl !== target ){
lastEl = target;
lastCSS = _css(target)
}
var
rect = target.getBoundingClientRect()
, width = rect.right - rect.left
, height = rect.bottom - rect.top
, floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
, after = !floating && (evt.clientY - rect.top)/height > .5 || floating && (evt.clientX - rect.left)/width > .5
, nextSibling = target.nextSibling
;
if( after && !nextSibling ){
el.appendChild(dragEl);
} else {
target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
}
}
}
},
_onDrop: function (evt/**Event*/){
clearInterval(this._loopId);
// Unbind events
_off(document, 'drop', this._onDrop);
_off(document, 'dragover', _globalDragOver);
_off(this.el, 'dragstart', this._onDragStart);
_off(document, 'touchmove', this._onTouchMove);
_off(document, 'touchend', this._onDrop);
if( evt ){
evt.preventDefault();
if( dragEl ){
var opts = { bubbles: true, cancelable: true, detail: dragEl };
_toggleClass(dragEl, this.options.ghostClass, false);
if( !rootEl.contains(dragEl) ){
// Remove event
rootEl.dispatchEvent(new Event('remove', opts));
// Add event
dragEl.dispatchEvent(new Event('add', opts));
}
else if( dragEl.nextSibling !== nextEl ){
// Update event
dragEl.dispatchEvent(new Event('update', opts));
}
}
if( ghostEl ){
ghostEl.parentNode.removeChild(ghostEl);
}
// Set NULL
rootEl =
dragEl =
ghostEl =
nextEl =
tapEvt =
touchEvt =
lastEl =
lastCSS =
activeGroup = null;
}
},
destroy: function (){
var el = this.el, options = this.options;
_off(el, 'add', options.onAdd);
_off(el, 'update', options.onUpdate);
_off(el, 'remove', options.onRemove);
_off(el, 'mousedown', this._onTapStart);
_off(el, 'touchstart', this._onTapStart);
_off(el, 'dragover', this._onDragOver);
_off(el, 'dragenter', this._onDragOver);
touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);
this._onDrop();
this.el = null;
}
};
function _bind(ctx, fn){
var args = slice.call(arguments, 2);
return fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function (){
return fn.apply(ctx, args.concat(slice.call(arguments)));
};
}
function _closest(el, selector, ctx){
if( el && ctx ){
ctx = ctx || document;
selector = selector.split('.');
var
tag = selector.shift().toUpperCase()
, re = new RegExp('\\b('+selector.join('|')+')\\b', 'g')
;
do {
if(
(tag === '' || el.nodeName == tag)
&& (!selector.length || ((el.className+'').match(re) || []).length == selector.length)
){
return el;
}
}
while( el !== ctx && (el = el.parentNode) );
}
return null;
}
function _globalDragOver(evt){
evt.dataTransfer.dropEffect = 'move';
evt.preventDefault();
}
function _on(el, event, fn){
el.addEventListener(event, fn, false);
}
function _off(el, event, fn){
el.removeEventListener(event, fn, false);
}
function _toggleClass(el, name, state){
if( el ){
if( el.classList ){
el.classList[state ? 'add' : 'remove'](name);
}
else {
var className = (' '+el.className+' ').replace(' '+name+' ', '').replace(/\s+/g, ' ');
el.className = className + (state ? ' '+name : '')
}
}
}
function _css(el, prop, val){
if( el && el.style ){
if( val === void 0 ){
if( document.defaultView && document.defaultView.getComputedStyle ){
val = document.defaultView.getComputedStyle(el, '');
}
else if( el.currentStyle ){
val = el.currentStyle;
}
return prop === void 0 ? val : val[prop];
} else {
el.style[prop] = val + (typeof val === 'string' ? '' : 'px');
}
}
}
function _find(ctx, tagName, iterator){
if( ctx ){
var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
if( iterator ){
for( ; i < n; i++ ){
iterator(list[i], i);
}
}
return list;
}
return [];
}
function _disableDraggable(el){
return el.draggable = false;
}
// Export utils
Sortable.utils = {
on: _on,
off: _off,
css: _css,
find: _find,
bind: _bind,
closest: _closest,
toggleClass: _toggleClass
};
Sortable.version = '0.1.0';
// Export
return Sortable;
});

2
Sortable.min.js vendored

File diff suppressed because one or more lines are too long

387
index.html

@ -0,0 +1,387 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Sortable (No jQuery)</title>
<meta name="keywords" content="sortable, reorder, list, javascript, html5, drag and drop, dnd, rubaxa"/>
<meta name="description" content="Sortable - is a minimalist JavaScript library for modern browsers and touch devices (No jQuery)."/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
<style>
@import url(http://fonts.googleapis.com/css?family=Roboto:300);
html, body {
color: #333;
min-height: 100%;
font-size: 18px;
font-family: "Arial", Helvetica, Garuda, sans-serif;
}
h1, h2, h3 {
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
h1 a {
color: #333;
text-decoration: underline;
}
h1 + div {
opacity: 0.95;
margin-bottom: 50px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.sortable-ghost {
opacity: .2;
}
img { vertical-align: middle; }
#contacts {
border-radius: 3px;
box-shadow: 0 0 1px rgba(0,0,0,0.4);
}
#contacts li:first-child {
border-radius: 3px 3px 0 0;
}
#contacts li:last-child {
border-radius: 0 0 3px 3px;
}
#contacts li {
cursor: move;
padding: 10px;
background: rgba(255,255,255,.8);
border-bottom: 1px solid #ccc;
}
#contacts img {
margin-right: 5px;
}
#tags {
min-height: 100px;
box-shadow: 0 1px 1px rgba(0,0,0,0.3);
border-radius: 5px;
background-color: #fff;
}
#tags li {
cursor: move;
float: left;
margin: 10px;
padding: 6px 15px;
background: #B7D967;
border-radius: 3px;
box-shadow: 0 2px 0 #6d823d;
text-shadow: 0 1px 1px rgba(255,255,255,0.6);
}
#tags img {
display: none;
}
.tile {
width: 280px;
float: left;
padding: 2px;
box-shadow: 0 0 0 3px rgba(0,0,0,0.2);
background-color: #fff;
margin-right: 30px;
margin-bottom: 30px;
}
.tile__title {
cursor: pointer;
padding: 7px 0 10px;
text-align: center;
background-color: #E0E4F2;
}
.tile__list {
padding: 15px;
}
.tile__list img {
margin: 10px;
cursor: move;
}
.download {
color: #333;
margin-left: 10px;
text-decoration: none;
}
.download:hover {
text-decoration: none;
}
</style>
</head>
<body>
<a href="https://github.com/RubaXa/Sortable"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div class="container">
<h1>
<a href="https://github.com/RubaXa/Sortable/">Sortable</a>
<a class="download" title="Download" href="https://github.com/RubaXa/Sortable/archive/master.zip">&darr;</a>
</h1>
<div>&mdash; is a minimalist JavaScript library for modern browsers and touch devices (No jQuery).</div>
<div class="row">
<div class="col-md-6 col-sm-6">
<h3>List A</h3>
<ul id="contacts">
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-prn1/s32x32/41627_1611219478_5779_q.jpg"/>
<span>Catherine</span>
</li>
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash3/s32x32/370175_619386670_43244658_q.jpg"/>
<span>Polina</span>
</li>
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash3/s32x32/173108_654249662_698694459_q.jpg"/>
<span>Duke</span>
</li>
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-prn2/s32x32/372024_100000330534964_827799899_q.jpg"/>
<span>Adnrey</span>
</li>
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash1/s32x32/260934_100000460170867_1741520317_q.jpg"/>
<span>Maxim</span>
</li>
</ul>
</div>
<div class="col-md-6 col-sm-6">
<h3>List B</h3>
<ul id="tags">
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash2/s32x32/274297_100002088418977_1552918698_q.jpg"/>
<span>Ilya</span>
</li>
<li>
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash1/s32x32/370664_100003149104712_1050952731_q.jpg"/>
<span>Anna</span>
</li>
</ul>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<div class="container" id="multi">
<h2>Multi</h2>
<div class="tile">
<div class="tile__title">Group A</div>
<div class="tile__list">
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-prn1/c9.9.113.113/s100x100/59436_1391411357920_1388516_s.jpg"/><!--
--><img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash3/c13.8.104.104/s100x100/941190_10151608397684663_1532692251_s.jpg"/><!--
--><img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-frc3/c0.0.103.103/s100x100/382696_10150378364701671_1792621129_a.jpg"/><!--
--><img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash3/c44.10.116.116/s100x100/552948_430685950285752_1435082176_a.jpg"/>
</div>
</div>
<div class="tile">
<div class="tile__title">Group B</div>
<div class="tile__list">
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash3/c8.8.105.105/s100x100/558916_4874661741992_448469446_s.jpg"/><!--
--><img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-prn1/c29.9.117.117/s100x100/68347_385372304875713_1358705380_a.jpg"/><!--
--><img src="https://fbcdn-profile-a.akamaihd.net/hprofile-ak-ash2/c9.9.113.113/s100x100/424349_465457316812937_2106915541_s.jpg"/>
</div>
</div>
<div class="tile">
<div class="tile__title">Group C</div>
<div class="tile__list">
<img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-frc3/c12.12.156.156/s100x100/303317_320632284665935_15996162_a.jpg"/><!--
--><img src="//fbcdn-profile-a.akamaihd.net/hprofile-ak-ash2/c9.9.109.109/s100x100/484507_4207733265938_1693034881_s.jpg"/>
</div>
</div>
<div class="row"></div>
<p>&nbsp;</p>
</div>
<div class="container">
<h2>Code example</h2>
<pre class="javascript"><code>// Simple list
var list = document.getElementById("my-ui-list");
new Sortable(list); // That's all.
// Grouping
var foo = document.getElementById("foo");
new Sortable(foo, { group: "omega" });
var bar = document.getElementById("bar");
new Sortable(bar, { group: "omega" });
// Or
var container = document.getElementById("multi");
var sort = new Sortable(container, {
handle: ".tile__title", // Restricts sort start click/touch to the specified element
dragabble: ".tile", // Specifies which items inside the element should be sortable
onUpdate: function (evt/**Event*/){
var item = evt.detail; // the current dragged HTMLElement
}
});
// ..
sort.destroy();
</code></pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
<script src="Sortable.js"></script>
<script>
(function (){
var console = window.console;
if( !console.log ){
console.log = function (){
alert([].join.apply(arguments, ' '));
};
}
new Sortable(contacts, {
group: "contacts",
onAdd: function (evt){ console.log('onAdd.contacts:', evt.detail); },
onUpdate: function (evt){ console.log('onUpdate.contacts:', evt.detail); },
onRemove: function (evt){ console.log('onRemove.contacts:', evt.detail); }
});
new Sortable(tags, {
group: "contacts",
onAdd: function (evt){ console.log('onAdd.tags:', evt.detail); },
onUpdate: function (evt){ console.log('onUpdate.tags:', evt.detail); },
onRemove: function (evt){ console.log('onRemove.tags:', evt.detail); }
});
new Sortable(multi, {
draggable: '.tile',
handle: '.tile__title'
});
[].forEach.call(multi.getElementsByClassName('tile__list'), function (el){
new Sortable(el, { group: 'photo' });
});
})();
function setNoiseBackground(el, color, width, height, opacity){
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for( var i = 0; i < width; i++ ){
for( var j = 0; j < height; j++ ){
var val = Math.floor(Math.random() * 255);
context.fillStyle = "rgba(" + val + "," + val + "," + val + "," + opacity + ")";
context.fillRect(i, j, 1, 1);
}
}
el.style.background = "url(" + canvas.toDataURL("image/png") + "), "+color;
}
// Usage
setNoiseBackground(document.getElementsByTagName('body')[0], "linear-gradient(to bottom, #E1E2D1 0%, #5AABA5 100%)", 50, 50, 0.03);
</script>
<!-- highlight.js -->
<style>
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
.tomorrow-comment, pre .comment, pre .title {
color: #8e908c;
}
.tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo {
color: #c82829;
}
.tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant {
color: #f5871f;
}
.tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute {
color: #eab700;
}
.tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata {
color: #718c00;
}
.tomorrow-aqua, pre .css .hexcolor {
color: #3e999f;
}
.tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title {
color: #4271ae;
}
.tomorrow-purple, pre .keyword, pre .javascript .function {
color: #8959a8;
}
pre {
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
background-color: #fff;
}
pre code {
display: block;
color: #4d4d4c;
font-family: Menlo, Monaco, Consolas, monospace;
line-height: 1.5;
padding: 10px;
}
</style>
<script src="//yandex.st/highlightjs/7.5/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
</body>
</html>

22
package.json

@ -0,0 +1,22 @@
{
"name": "sortable",
"exportName": "Sortable",
"version": "0.1.0",
"devDependencies": {
"grunt": "*",
"grunt-version": "*",
"grunt-contrib-uglify": "*"
},
"description": "Sortable - is a minimalist JavaScript library for modern browsers and touch devices (No jQuery).",
"main": "Sortable.js",
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
"url": "git://github.com/rubaxa/Sortable.git"
},
"keywords": ["sortable", "reorder"],
"author": "Konstantin Lebedev <ibnRubaXa@gmail.com>",
"license": "MIT"
}
Loading…
Cancel
Save