1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

switch drag & drop lib

This commit is contained in:
Luke Pulverenti 2015-10-22 11:03:08 -04:00
parent 1f9c509d58
commit f7c130f1b5
105 changed files with 4100 additions and 4165 deletions

View file

@ -0,0 +1,43 @@
{
"name": "Sortable",
"main": [
"Sortable.js",
"ng-sortable.js",
"knockout-sortable.js",
"react-sortable-mixin.js"
],
"homepage": "http://rubaxa.github.io/Sortable/",
"authors": [
"RubaXa <ibnRubaXa@gmail.com>"
],
"description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.",
"keywords": [
"sortable",
"reorder",
"list",
"html5",
"drag",
"and",
"drop",
"dnd",
"web-components"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"tests"
],
"version": "1.4.2",
"_release": "1.4.2",
"_resolution": {
"type": "version",
"tag": "1.4.2",
"commit": "d86937c8bca017f948aad04eb02dcaddfbb60ffa"
},
"_source": "git://github.com/RubaXa/Sortable.git",
"_target": "~1.4.2",
"_originalSource": "Sortable",
"_direct": true
}

View file

@ -2,8 +2,7 @@
root = true root = true
[*] [*]
indent_style = space indent_style = tab
indent_size = 2
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true

View file

@ -0,0 +1,5 @@
node_modules
mock.png
.*.sw*
.build*
jquery.fn.*

View file

@ -0,0 +1,24 @@
{
"strict": true,
"newcap": false,
"node": true,
"expr": true,
"supernew": true,
"laxbreak": true,
"white": true,
"globals": {
"define": true,
"test": true,
"expect": true,
"module": true,
"asyncTest": true,
"start": true,
"ok": true,
"equal": true,
"notEqual": true,
"deepEqual": true,
"window": true,
"document": true,
"performance": true
}
}

View file

@ -0,0 +1,23 @@
# Contribution Guidelines
### Issue
1. Try [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch, perhaps the problem has been solved;
2. [Use the search](https://github.com/RubaXa/Sortable/search?q=problem), maybe already have an answer;
3. If not found, create example on [jsbin.com (draft)](http://jsbin.com/zunibaxada/1/edit?html,js,output) and describe the problem.
---
### Pull Request
1. Before PR run `grunt`;
2. Only into [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch.
### Setup
Pieced together from [gruntjs](http://gruntjs.com/getting-started)
1. Fork repo on [github](https://github.com)
2. Clone locally
3. from local repro ```npm install```
4. Install grunt-cli globally ```sudo -H npm install -g grunt-cli```

View file

@ -0,0 +1,103 @@
module.exports = function (grunt) {
'use strict';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
version: {
js: {
src: ['<%= pkg.exportName %>.js', '*.json']
},
cdn: {
options: {
prefix: '(cdnjs\\.cloudflare\\.com\\/ajax\\/libs\\/Sortable|cdn\\.jsdelivr\\.net\\/sortable)\\/',
replace: '[0-9\\.]+'
},
src: ['README.md']
}
},
jshint: {
all: ['*.js', '!*.min.js'],
options: {
jshintrc: true
}
},
uglify: {
options: {
banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n'
},
dist: {
files: {
'<%= pkg.exportName %>.min.js': ['<%= pkg.exportName %>.js']
}
},
jquery: {
files: {}
}
},
exec: {
'meteor-test': {
command: 'meteor/runtests.sh'
},
'meteor-publish': {
command: 'meteor/publish.sh'
}
},
jquery: {}
});
grunt.registerTask('jquery', function (exportName, uglify) {
if (exportName == 'min') {
exportName = null;
uglify = 'min';
}
if (!exportName) {
exportName = 'sortable';
}
var fs = require('fs'),
filename = 'jquery.fn.' + exportName + '.js';
grunt.log.oklns(filename);
fs.writeFileSync(
filename,
(fs.readFileSync('jquery.binding.js') + '')
.replace('$.fn.sortable', '$.fn.' + exportName)
.replace('/* CODE */',
(fs.readFileSync('Sortable.js') + '')
.replace(/^[\s\S]*?function[\s\S]*?(var[\s\S]+)\/\/\s+Export[\s\S]+/, '$1')
)
);
if (uglify) {
var opts = {};
opts['jquery.fn.' + exportName + '.min.js'] = filename;
grunt.config.set('uglify.jquery.files', opts);
grunt.task.run('uglify:jquery');
}
});
grunt.loadNpmTasks('grunt-version');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-exec');
// Meteor tasks
grunt.registerTask('meteor-test', 'exec:meteor-test');
grunt.registerTask('meteor-publish', 'exec:meteor-publish');
grunt.registerTask('meteor', ['meteor-test', 'meteor-publish']);
grunt.registerTask('tests', ['jshint']);
grunt.registerTask('default', ['tests', 'version', 'uglify:dist']);
};

View file

@ -0,0 +1,139 @@
<link rel="import" href="../polymer/polymer.html">
<script src="./Sortable.js"></script>
<dom-module id="sortable-js">
<template>
<content></content>
</template>
</dom-module>
<script>
Polymer({
is: "sortable-js",
properties: {
group : { type: String, value: () => Math.random(), observer: "groupChanged" },
sort : { type: Boolean, value: true, observer: "sortChanged" },
disabled : { type: Boolean, value: false, observer: "disabledChanged" },
store : { type: Object, value: null, observer: "storeChanged" },
handle : { type: String, value: null, observer: "handleChanged" },
scrollSensitivity : { type: Number, value: 30, observer: "scrollSensitivityChanged" },
scrollSpeed : { type: Number, value: 10, observer: "scrollSpeedChanged" },
ghostClass : { type: String, value: "sortable-ghost", observer: "ghostClassChanged" },
chosenClass : { type: String, value: "sortable-chosen", observer: "chosenClassChanged" },
ignore : { type: String, value: "a, img", observer: "ignoreChanged" },
filter : { type: Object, value: null, observer: "filterChanged" },
animation : { type: Number, value: 0, observer: "animationChanged" },
dropBubble : { type: Boolean, value: false, observer: "dropBubbleChanged" },
dragoverBubble : { type: Boolean, value: false, observer: "dragoverBubbleChanged" },
dataIdAttr : { type: String, value: "data-id", observer: "dataIdAttrChanged" },
delay : { type: Number, value: 0, observer: "delayChanged" },
forceFallback : { type: Boolean, value: false, observer: "forceFallbackChanged" },
fallbackClass : { type: String, value: "sortable-fallback", observer: "fallbackClassChanged" },
fallbackOnBody : { type: Boolean, value: false, observer: "fallbackOnBodyChanged" },
draggable : {},
scroll : {}
},
created() {
// override default DOM property behavior
Object.defineProperties(this, {
draggable: { get() { return this._draggable || this.getAttribute("draggable") || ">*"}, set(value) { this._draggable = value; this.draggableChanged(value)} },
scroll: { get() { return this._scroll || JSON.parse(this.getAttribute("scroll") || "true") }, set(value) { this._scroll = value; this.scrollChanged(value)} }
})
},
attached: function() {
// Given
// <sortable-js>
// <template is="dom-repeat" items={{data}}>
// <div>
// <template is="dom-if" if="true">
// <span>hello</span></template></div>
// After render, it becomes
// <sortable-js>
// <div>
// <span>hello</span>
// <template is="dom-if">
// <tempalte is="dom-repeat">
var templates = this.querySelectorAll("template[is='dom-repeat']")
var template = templates[templates.length-1]
var options = {}
Object.keys(this.properties).forEach(key => {
options[key] = this[key]
})
this.sortable = Sortable.create(this, Object.assign(options, {
onUpdate: e => {
if (template) {
template.splice("items", e.newIndex, 0, template.splice("items", e.oldIndex, 1)[0])
}
this.fire("update", e)
},
onAdd: e => {
if (template) {
var froms = e.from.querySelectorAll("template[is='dom-repeat']")
var from = froms[froms.length-1]
var item = from.items[e.oldIndex]
template.splice("items", e.newIndex, 0, item)
}
this.fire("add", e)
},
onRemove: e => {
if (template) {
template.splice("items", e.oldIndex, 1)[0]
}
this.fire("remove", e)
},
onStart: e => {
this.fire("start", e)
},
onEnd: e => {
this.fire("end", e)
},
onSort: e => {
this.fire("sort", e)
},
onFilter: e => {
this.fire("filter", e)
},
onMove: e => {
this.fire("move", e)
}
}))
},
detached: function() {
this.sortable.destroy()
},
groupChanged : function(value) { this.sortable && this.sortable.option("group", value) },
sortChanged : function(value) { this.sortable && this.sortable.option("sort", value) },
disabledChanged : function(value) { this.sortable && this.sortable.option("disabled", value) },
storeChanged : function(value) { this.sortable && this.sortable.option("store", value) },
handleChanged : function(value) { this.sortable && this.sortable.option("handle", value) },
scrollChanged : function(value) { this.sortable && this.sortable.option("scroll", value) },
scrollSensitivityChanged : function(value) { this.sortable && this.sortable.option("scrollSensitivity", value) },
scrollSpeedChanged : function(value) { this.sortable && this.sortable.option("scrollSpeed", value) },
draggableChanged : function(value) { this.sortable && this.sortable.option("draggable", value) },
ghostClassChanged : function(value) { this.sortable && this.sortable.option("ghostClass", value) },
chosenClassChanged : function(value) { this.sortable && this.sortable.option("chosenClass", value) },
ignoreChanged : function(value) { this.sortable && this.sortable.option("ignore", value) },
filterChanged : function(value) { this.sortable && this.sortable.option("filter", value) },
animationChanged : function(value) { this.sortable && this.sortable.option("animation", value) },
dropBubbleChanged : function(value) { this.sortable && this.sortable.option("dropBubble", value) },
dragoverBubbleChanged : function(value) { this.sortable && this.sortable.option("dragoverBubble", value) },
dataIdAttrChanged : function(value) { this.sortable && this.sortable.option("dataIdAttr", value) },
delayChanged : function(value) { this.sortable && this.sortable.option("delay", value) },
forceFallbackChanged : function(value) { this.sortable && this.sortable.option("forceFallback", value) },
fallbackClassChanged : function(value) { this.sortable && this.sortable.option("fallbackClass", value) },
fallbackOnBodyChanged : function(value) { this.sortable && this.sortable.option("fallbackOnBody", value) }
})
</script>

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,32 @@
{
"name": "Sortable",
"main": [
"Sortable.js",
"ng-sortable.js",
"knockout-sortable.js",
"react-sortable-mixin.js"
],
"homepage": "http://rubaxa.github.io/Sortable/",
"authors": [
"RubaXa <ibnRubaXa@gmail.com>"
],
"description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.",
"keywords": [
"sortable",
"reorder",
"list",
"html5",
"drag",
"and",
"drop",
"dnd",
"web-components"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"tests"
]
}

View file

@ -0,0 +1,32 @@
{
"name": "Sortable",
"main": "Sortable.js",
"version": "1.4.2",
"homepage": "http://rubaxa.github.io/Sortable/",
"repo": "RubaXa/Sortable",
"authors": [
"RubaXa <ibnRubaXa@gmail.com>"
],
"description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.",
"keywords": [
"sortable",
"reorder",
"list",
"html5",
"drag",
"and",
"drop",
"dnd"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"tests"
],
"scripts": [
"Sortable.js"
]
}

View file

@ -0,0 +1,344 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta property="og:image" content="/st/og-image.png"/>
<title>Sortable. No jQuery.</title>
<meta name="keywords" content="sortable, reorder, list, javascript, html5, drag and drop, dnd, animation, groups, angular, ng-sortable, react, mixin, effects, rubaxa"/>
<meta name="description" content="Sortable - is a minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports Meteor, AngularJS, React and any CSS library, e.g. Bootstrap."/>
<meta name="viewport" content="width=device-width, initial-scale=0.5"/>
<link href="//rubaxa.github.io/Ply/ply.css" rel="stylesheet" type="text/css"/>
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css"/>
<link href="st/app.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<a href="https://github.com/RubaXa/Sortable"><img style="position: fixed; top: 0; right: 0; border: 0; z-index: 10000;" src="//s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
<div class="container">
<div style="padding: 80px 150px 0; height: 160px;">
<a class="logo" href="https://github.com/RubaXa/Sortable"><img src="st/logo.png"/></a>
<h1 data-force="40" data-force-y="2.5">The JavaScript library for modern browsers and touch devices. No&nbsp;jQuery.</h1>
</div>
</div>
<!-- Connected lists -->
<div class="container" style="height: 520px">
<div data-force="30" class="layer block" style="left: 14.5%; top: 0; width: 37%">
<div class="layer title">List A</div>
<ul id="foo" class="block__list block__list_words">
<li>бегемот</li>
<li>корм</li>
<li>антон</li>
<li>сало</li>
<li>железосталь</li>
<li>валик</li>
<li>кровать</li>
<li>краб</li>
</ul>
</div>
<div data-force="18" class="layer block" style="left: 58%; top: 143px; width: 40%;">
<div class="layer title">List B</div>
<ul id="bar" class="block__list block__list_tags">
<li>казнить</li>
<li>,</li>
<li>нельзя</li>
<li>помиловать</li>
</ul>
</div>
</div>
<!-- Multi connected lists -->
<a name="m"></a>
<div class="container">
<div id="multi" style="margin-left: 30px">
<div><div data-force="5" class="layer title title_xl">Multi</div></div>
<div class="layer tile" data-force="30">
<div class="tile__name">Group A</div>
<div class="tile__list">
<img src="st/face-01.jpg"/><!--
--><img src="st/face-02.jpg"/><!--
--><img src="st/face-03.jpg"/><!--
--><img src="st/face-04.jpg"/>
</div>
</div>
<div class="layer tile" data-force="25">
<div class="tile__name">Group B</div>
<div class="tile__list">
<img src="st/face-05.jpg"/><!--
--><img src="st/face-06.jpg"/><!--
--><img src="st/face-07.jpg"/>
</div>
</div>
<div class="layer tile" data-force="20">
<div class="tile__name">Group C</div>
<div class="tile__list">
<img src="st/face-08.jpg"/><!--
--><img src="st/face-09.jpg"/>
</div>
</div>
</div>
</div>
<!-- Editable list -->
<a name="e"></a>
<div class="container" style="margin-top: 100px">
<div id="filter" style="margin-left: 30px">
<div><div data-force="5" class="layer title title_xl">Editable list</div></div>
<div style="margin-top: -8px; margin-left: 10px" class="block__list block__list_words">
<ul id="editable">
<li>Оля<i class="js-remove"></i></li>
<li>Владимир<i class="js-remove"></i></li>
<li>Алина<i class="js-remove"></i></li>
</ul>
<button id="addUser">Add</button>
</div>
</div>
</div>
<!-- Advanced connected lists -->
<a name="ag"></a>
<div class="container" style="margin-top: 100px;">
<div id="advanced" style="margin-left: 30px;">
<div><div data-force="5" class="layer title title_xl">Advanced groups</div></div>
<div style="width: 25%; float: left; margin-top: 15px; margin-left: 10px" class="block__list block__list_words">
<div class="block__list-title">pull & put</div>
<ul id="advanced-1">
<li>Meat</li>
<li>Potato</li>
<li>Tea</li>
</ul>
</div>
<div style="width: 25%; float: left; margin-top: 15px; margin-left: 10px" class="block__list block__list_words">
<div class="block__list-title">only pull (clone) no&nbsp;reordering</div>
<ul id="advanced-2">
<li>Sex</li>
<li>Drugs</li>
<li>Rock'n'roll</li>
</ul>
</div>
<div style="width: 25%; float: left; margin-top: 15px; margin-left: 10px" class="block__list block__list_words">
<div class="block__list-title">only put</div>
<ul id="advanced-3">
<li>Money</li>
<li>Force</li>
<li>Agility</li>
</ul>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- 'handle' option -->
<a name="h"></a>
<div class="container" style="margin-top: 100px;">
<div id="handle" style="margin-left: 30px;">
<div><div data-force="5" class="layer title title_xl">Drag handle and selectable text</div></div>
<div style="width: 30%; margin-left: 10px" class="block__list_words">
<ul id="handle-1">
<li><span class="drag-handle">&#9776;</span>Select text freely</li>
<li><span class="drag-handle">&#9776;</span>Drag my handle</li>
<li><span class="drag-handle">&#9776;</span>Best of both worlds</li>
</ul>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- Angular -->
<a name="ng"></a>
<div id="todos" ng-app="todoApp" class="container" style="margin-top: 100px">
<div style="margin-left: 30px">
<div><div data-force="5" class="layer title title_xl">AngularJS / ng-sortable</div></div>
<div style="width: 30%; margin-top: -8px; margin-left: 10px; float: left;" class="block__list block__list_words">
<div ng-controller="TodoController">
<span style="padding-left: 20px">{{remaining()}} of {{todos.length}} remaining</span>
[ <a href="" ng-click="archive()">archive</a> ]
<ul ng-sortable="{ group: 'todo', animation: 150 }" class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
<form ng-submit="addTodo()" style="padding-left: 20px">
<input type="text" ng-model="todoText" size="30"
placeholder="add new todo here">
</form>
</div>
</div>
<div style="width: 30%; margin-top: -8px; margin-left: 10px; float: left;" class="block__list block__list_words">
<div ng-controller="TodoControllerNext">
<span style="padding-left: 20px">{{remaining()}} of {{todos.length}} remaining</span>
<ul ng-sortable="sortableConfig" class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
</div>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- Code example -->
<a name="c"></a>
<div class="container" style="margin-top: 100px">
<div style="margin-left: 30px">
<div><div class="layer title title_xl">Code example</div></div>
<pre data-force="100" class="layer javascript" style="margin-top: -8px; margin-left: 10px; width: 90%"><code>// Simple list
var list = document.getElementById("my-ui-list");
Sortable.create(list); // That's all.
// Grouping
var foo = document.getElementById("foo");
Sortable.create(foo, { group: "omega" });
var bar = document.getElementById("bar");
Sortable.create(bar, { group: "omega" });
// Or
var container = document.getElementById("multi");
var sort = Sortable.create(container, {
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
handle: ".tile__title", // Restricts sort start click/touch to the specified element
draggable: ".tile", // Specifies which items inside the element should be sortable
onUpdate: function (evt/**Event*/){
var item = evt.item; // the current dragged HTMLElement
}
});
// ..
sort.destroy();
// Editable list
var editableList = Sortable.create(editable, {
filter: '.js-remove',
onFilter: function (evt) {
var el = editableList.closest(evt.item); // get dragged item
el && el.parentNode.removeChild(el);
}
});
</code></pre>
</div>
<div class="container" style="margin: 100px 0;">
<div style="margin-left: 30px">
<div><div class="layer title title_xl">See also</div></div>
<div id="rubaxa-repos" data-force="100" class="layer" style="margin-top: -8px; margin-left: 10px; width: 90%; background-color: #fff;">Loading&hellip;</div>
<script src="//rubaxa.github.io/repos.js"></script>
</div>
</div>
</div>
<script src="Sortable.js"></script>
<script src="//rubaxa.github.io/Ply/Ply.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="ng-sortable.js"></script>
<script src="st/app.js"></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;
background-color: #fff;
}
pre code {
display: block;
color: #4d4d4c;
font-size: 15px;
font-family: Menlo, Monaco, Consolas, monospace;
line-height: 1.5;
padding: 30px;
}
</style>
<script src="//yandex.st/highlightjs/7.5/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-16483888-3', 'rubaxa.github.io');
ga('send', 'pageview');
</script>
</body>
</html>

View file

@ -0,0 +1,61 @@
/**
* jQuery plugin for Sortable
* @author RubaXa <trash@rubaxa.org>
* @license MIT
*/
(function (factory) {
"use strict";
if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
}
else {
/* jshint sub:true */
factory(jQuery);
}
})(function ($) {
"use strict";
/* CODE */
/**
* jQuery plugin for Sortable
* @param {Object|String} options
* @param {..*} [args]
* @returns {jQuery|*}
*/
$.fn.sortable = function (options) {
var retVal,
args = arguments;
this.each(function () {
var $el = $(this),
sortable = $el.data('sortable');
if (!sortable && (options instanceof Object || !options)) {
sortable = new Sortable(this, options);
$el.data('sortable', sortable);
}
if (sortable) {
if (options === 'widget') {
return sortable;
}
else if (options === 'destroy') {
sortable.destroy();
$el.removeData('sortable');
}
else if (typeof sortable[options] === 'function') {
retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
}
else if (options in sortable.options) {
retVal = sortable.option.apply(sortable, args);
}
}
});
return (retVal === void 0) ? this : retVal;
};
});

View file

@ -0,0 +1,182 @@
(function (factory) {
"use strict";
if (typeof define === "function" && define.amd) {
// AMD anonymous module
define(["knockout"], factory);
} else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// CommonJS module
var ko = require("knockout");
factory(ko);
} else {
// No module loader (plain <script> tag) - put directly in global namespace
factory(window.ko);
}
})(function (ko) {
"use strict";
var init = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) {
var options = buildOptions(valueAccessor, sortableOptions);
//It's seems that we cannot update the eventhandlers after we've created the sortable, so define them in init instead of update
['onStart', 'onEnd', 'onRemove', 'onAdd', 'onUpdate', 'onSort', 'onFilter'].forEach(function (e) {
if (options[e] || eventHandlers[e])
options[e] = function (eventType, parentVM, parentBindings, handler, e) {
var itemVM = ko.dataFor(e.item),
//All of the bindings on the parent element
bindings = ko.utils.peekObservable(parentBindings()),
//The binding options for the draggable/sortable binding of the parent element
bindingHandlerBinding = bindings.sortable || bindings.draggable,
//The collection that we should modify
collection = bindingHandlerBinding.collection || bindingHandlerBinding.foreach;
if (handler)
handler(e, itemVM, parentVM, collection, bindings);
if (eventHandlers[eventType])
eventHandlers[eventType](e, itemVM, parentVM, collection, bindings);
}.bind(undefined, e, viewModel, allBindings, options[e]);
});
var sortableElement = Sortable.create(element, options);
//Destroy the sortable if knockout disposes the element it's connected to
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
sortableElement.destroy();
});
return ko.bindingHandlers.template.init(element, valueAccessor);
},
update = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) {
//There seems to be some problems with updating the options of a sortable
//Tested to change eventhandlers and the group options without any luck
return ko.bindingHandlers.template.update(element, valueAccessor, allBindings, viewModel, bindingContext);
},
eventHandlers = (function (handlers) {
var moveOperations = [],
tryMoveOperation = function (e, itemVM, parentVM, collection, parentBindings) {
//A move operation is the combination of a add and remove event, this is to make sure that we have both the target and origin collections
var currentOperation = { event: e, itemVM: itemVM, parentVM: parentVM, collection: collection, parentBindings: parentBindings },
existingOperation = moveOperations.filter(function (op) {
return op.itemVM === currentOperation.itemVM;
})[0];
if (!existingOperation) {
moveOperations.push(currentOperation);
}
else {
//We're finishing the operation and already have a handle on the operation item meaning that it's safe to remove it
moveOperations.splice(moveOperations.indexOf(existingOperation), 1);
var removeOperation = currentOperation.event.type === 'remove' ? currentOperation : existingOperation,
addOperation = currentOperation.event.type === 'add' ? currentOperation : existingOperation;
moveItem(itemVM, removeOperation.collection, addOperation.collection, addOperation.event.clone, addOperation.event);
}
},
//Moves an item from the to (collection to from (collection), these can be references to the same collection which means it's a sort,
//clone indicates if we should move or copy the item into the new collection
moveItem = function (itemVM, from, to, clone, e) {
//Unwrapping this allows us to manipulate the actual array
var fromArray = from(),
//It's not certain that the items actual index is the same as the index reported by sortable due to filtering etc.
originalIndex = fromArray.indexOf(itemVM),
newIndex = e.newIndex;
if (e.item.previousElementSibling)
{
newIndex = fromArray.indexOf(ko.dataFor(e.item.previousElementSibling));
if (originalIndex > newIndex)
newIndex = newIndex + 1;
}
//Remove sortables "unbound" element
e.item.parentNode.removeChild(e.item);
//This splice is necessary for both clone and move/sort
//In sort/move since it shouldn't be at this index/in this array anymore
//In clone since we have to work around knockouts valuHasMutated when manipulating arrays and avoid a "unbound" item added by sortable
fromArray.splice(originalIndex, 1);
//Update the array, this will also remove sortables "unbound" clone
from.valueHasMutated();
if (clone && from !== to) {
//Readd the item
fromArray.splice(originalIndex, 0, itemVM);
//Force knockout to update
from.valueHasMutated();
}
//Insert the item on its new position
to().splice(newIndex, 0, itemVM);
//Make sure to tell knockout that we've modified the actual array.
to.valueHasMutated();
};
handlers.onRemove = tryMoveOperation;
handlers.onAdd = tryMoveOperation;
handlers.onUpdate = function (e, itemVM, parentVM, collection, parentBindings) {
//This will be performed as a sort since the to/from collections reference the same collection and clone is set to false
moveItem(itemVM, collection, collection, false, e);
};
return handlers;
})({}),
//bindingOptions are the options set in the "data-bind" attribute in the ui.
//options are custom options, for instance draggable/sortable specific options
buildOptions = function (bindingOptions, options) {
//deep clone/copy of properties from the "from" argument onto the "into" argument and returns the modified "into"
var merge = function (into, from) {
for (var prop in from) {
if (Object.prototype.toString.call(from[prop]) === '[object Object]') {
if (Object.prototype.toString.call(into[prop]) !== '[object Object]') {
into[prop] = {};
}
into[prop] = merge(into[prop], from[prop]);
}
else
into[prop] = from[prop];
}
return into;
},
//unwrap the supplied options
unwrappedOptions = ko.utils.peekObservable(bindingOptions()).options || {};
//Make sure that we don't modify the provided settings object
options = merge({}, options);
//group is handled differently since we should both allow to change a draggable to a sortable (and vice versa),
//but still be able to set a name on a draggable without it becoming a drop target.
if (unwrappedOptions.group && Object.prototype.toString.call(unwrappedOptions.group) !== '[object Object]') {
//group property is a name string declaration, convert to object.
unwrappedOptions.group = { name: unwrappedOptions.group };
}
return merge(options, unwrappedOptions);
};
ko.bindingHandlers.draggable = {
sortableOptions: {
group: { pull: 'clone', put: false },
sort: false
},
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions);
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions);
}
};
ko.bindingHandlers.sortable = {
sortableOptions: {
group: { pull: true, put: true }
},
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions);
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions);
}
};
});

View file

@ -0,0 +1,34 @@
base64@1.0.3
binary-heap@1.0.3
blaze@2.1.2
blaze-tools@1.0.3
callback-hook@1.0.3
check@1.0.5
dburles:mongo-collection-instances@0.3.4
ddp@1.1.0
deps@1.0.7
ejson@1.0.6
geojson-utils@1.0.3
html-tools@1.0.4
htmljs@1.0.4
id-map@1.0.3
jquery@1.11.3_2
json@1.0.3
lai:collection-extensions@0.1.4
local-test:rubaxa:sortable@1.2.1
logging@1.0.7
meteor@1.1.6
minifiers@1.1.5
minimongo@1.0.8
mongo@1.1.0
observe-sequence@1.0.6
ordered-dict@1.0.3
random@1.0.3
reactive-var@1.0.5
retry@1.0.3
rubaxa:sortable@1.2.1
spacebars-compiler@1.0.6
templating@1.1.1
tinytest@1.0.5
tracker@1.0.7
underscore@1.0.3

View file

@ -0,0 +1,8 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2

View file

@ -0,0 +1 @@
local

View file

@ -0,0 +1,7 @@
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
ir0jg2douy3yo5mehw

View file

@ -0,0 +1,10 @@
# Meteor packages used by this project, one per line.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-platform
autopublish
insecure
rubaxa:sortable
fezvrasta:bootstrap-material-design

View file

@ -0,0 +1,2 @@
browser
server

View file

@ -0,0 +1 @@
METEOR@1.1.0.3

View file

@ -0,0 +1,53 @@
autopublish@1.0.3
autoupdate@1.2.1
base64@1.0.3
binary-heap@1.0.3
blaze@2.1.2
blaze-tools@1.0.3
boilerplate-generator@1.0.3
callback-hook@1.0.3
check@1.0.5
dburles:mongo-collection-instances@0.3.4
ddp@1.1.0
deps@1.0.7
ejson@1.0.6
fastclick@1.0.3
fezvrasta:bootstrap-material-design@0.3.0
geojson-utils@1.0.3
html-tools@1.0.4
htmljs@1.0.4
http@1.1.0
id-map@1.0.3
insecure@1.0.3
jquery@1.11.3_2
json@1.0.3
lai:collection-extensions@0.1.4
launch-screen@1.0.2
livedata@1.0.13
logging@1.0.7
meteor@1.1.6
meteor-platform@1.2.2
minifiers@1.1.5
minimongo@1.0.8
mobile-status-bar@1.0.3
mongo@1.1.0
observe-sequence@1.0.6
ordered-dict@1.0.3
random@1.0.3
reactive-dict@1.1.0
reactive-var@1.0.5
reload@1.1.3
retry@1.0.3
routepolicy@1.0.5
rubaxa:sortable@1.2.1
session@1.1.0
spacebars@1.0.6
spacebars-compiler@1.0.6
templating@1.1.1
tracker@1.0.7
twbs:bootstrap@3.3.5
ui@1.0.6
underscore@1.0.3
url@1.0.4
webapp@1.2.0
webapp-hashing@1.0.3

View file

@ -0,0 +1,57 @@
.glyphicon {
vertical-align: baseline;
font-size: 80%;
margin-right: 0.5em;
}
[class^="mdi-"], [class*=" mdi-"] {
vertical-align: baseline;
font-size: 90%;
margin-right: 0.4em;
}
.list-pair {
display: flex; /* use the flexbox model */
flex-direction: row;
}
.sortable {
/* font-size: 2em;*/
}
.sortable.source {
/*background: #9FA8DA;*/
flex: 0 0 auto;
margin-right: 1em;
cursor: move;
cursor: -webkit-grabbing;
}
.sortable.target {
/*background: #3F51B5;*/
flex: 1 1 auto;
margin-left: 1em;
}
.target .well {
}
.sortable-handle {
cursor: move;
cursor: -webkit-grabbing;
}
.sortable-handle.pull-right {
margin-top: 0.3em;
}
.sortable-ghost {
opacity: 0.6;
}
/* show the remove button on hover */
.removable .close {
display: none;
}
.removable:hover .close {
display: block;
}

View file

@ -0,0 +1,94 @@
<head>
<title>Reactive RubaXa:Sortable for Meteor</title>
</head>
<body>
{{> navbar}}
<div class="container">
<div class="page-header">
<h1>RubaXa:Sortable - reactive reorderable lists for Meteor</h1>
<h2>Drag attribute types from the left to define an object type on the right</h2>
<h3>Drag the <i class="sortable-handle mdi-action-view-headline"></i> handle to reorder elements</h3>
</div>
{{> typeDefinition}}
</div>
</body>
<template name="typeDefinition">
<div class="row">
<div class="list-pair col-sm-12">
<div class="sortable source list-group" id="types">
{{#sortable items=types options=typesOptions}}
<div class="list-group-item well well-sm">
{{{icon}}} {{name}}
</div>
{{/sortable}}
</div>
<div class="sortable target" id="object">
{{#sortable items=attributes animation="100" handle=".sortable-handle" ghostClass="sortable-ghost" options=attributesOptions}}
{{> sortableItemTarget}}
{{/sortable}}
</div>
</div>
</div>
</template>
<template name="sortableItemTarget">
<div data-id="{{order}}" class="sortable-item removable well well-sm">
{{{icon}}}
<i class="sortable-handle mdi-action-view-headline pull-right"></i>
<span class="name">{{name}}</span>
<span class="badge">{{order}}</span>
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">&times;</span><span class="sr-only">Close</span>
</button>
</div>
</template>
<template name="navbar">
<div class="navbar navbar-inverse">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-inverse-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="https://atmospherejs.com/rubaxa/sortable">RubaXa:Sortable</a>
</div>
<div class="navbar-collapse collapse navbar-inverse-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="http://rubaxa-sortable.meteor.com">Meteor Demo</a></li>
<li><a href="https://rubaxa.github.io/Sortable/" target="_blank">Sortable standalone demo</a></li>
<li class="dropdown">
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">Source <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">GitHub</li>
<li><a href="https://github.com/RubaXa/Sortable/tree/dev/meteor/example" target="_blank">This demo</a></li>
<li><a href="https://github.com/RubaXa/Sortable/tree/dev/meteor" target="_blank">Meteor integration</a></li>
<li><a href="https://github.com/RubaXa/Sortable" target="_blank">rubaxa/sortable</a></li>
<li class="divider"></li>
<li><a href="https://atmospherejs.com/rubaxa/sortable">Star this package on Atmosphere!</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">Resources <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="http://www.meteorpedia.com/read/Packaging_existing_Libraries">Packaging 3rd party libraries for Meteor</a></li>
<li><a href="https://twitter.com/dandv">Author: @dandv</a></li>
</ul>
</li>
</ul>
</div>
</div>
</template>

View file

@ -0,0 +1,101 @@
// Define an object type by dragging together attributes
Template.typeDefinition.helpers({
types: function () {
return Types.find({}, { sort: { order: 1 } });
},
typesOptions: {
sortField: 'order', // defaults to 'order' anyway
group: {
name: 'typeDefinition',
pull: 'clone',
put: false
},
sort: false // don't allow reordering the types, just the attributes below
},
attributes: function () {
return Attributes.find({}, {
sort: { order: 1 },
transform: function (doc) {
doc.icon = Types.findOne({name: doc.type}).icon;
return doc;
}
});
},
attributesOptions: {
group: {
name: 'typeDefinition',
put: true
},
onAdd: function (event) {
delete event.data._id; // Generate a new id when inserting in the Attributes collection. Otherwise, if we add the same type twice, we'll get an error that the ids are not unique.
delete event.data.icon;
event.data.type = event.data.name;
event.data.name = 'Rename me (double click)'
},
// event handler for reordering attributes
onSort: function (event) {
console.log('Item %s went from #%d to #%d',
event.data.name, event.oldIndex, event.newIndex
);
}
}
});
Template.sortableItemTarget.events({
'dblclick .name': function (event, template) {
// Make the name editable. We should use an existing component, but it's
// in a sorry state - https://github.com/arillo/meteor-x-editable/issues/1
var name = template.$('.name');
var input = template.$('input');
if (input.length) { // jQuery never returns null - http://stackoverflow.com/questions/920236/how-can-i-detect-if-a-selector-returns-null
input.show();
} else {
input = $('<input class="form-control" type="text" placeholder="' + this.name + '" style="display: inline">');
name.after(input);
}
name.hide();
input.focus();
},
'blur input[type=text]': function (event, template) {
// commit the change to the name, if any
var input = template.$('input');
input.hide();
template.$('.name').show();
// TODO - what is the collection here? We'll hard-code for now.
// https://github.com/meteor/meteor/issues/3303
if (this.name !== input.val() && this.name !== '')
Attributes.update(this._id, {$set: {name: input.val()}});
},
'keydown input[type=text]': function (event, template) {
if (event.which === 27) {
// ESC - discard edits and keep existing value
template.$('input').val(this.name);
event.preventDefault();
event.target.blur();
} else if (event.which === 13) {
// ENTER
event.preventDefault();
event.target.blur();
}
}
});
// you can add events to all Sortable template instances
Template.sortable.events({
'click .close': function (event, template) {
// `this` is the data context set by the enclosing block helper (#each, here)
template.collection.remove(this._id);
// custom code, working on a specific collection
if (Attributes.find().count() === 0) {
Meteor.setTimeout(function () {
Attributes.insert({
name: 'Not nice to delete the entire list! Add some attributes instead.',
type: 'String',
order: 0
})
}, 1000);
}
}
});

View file

@ -0,0 +1,2 @@
Types = new Mongo.Collection('types');
Attributes = new Mongo.Collection('attributes');

View file

@ -0,0 +1,7 @@
@echo off
REM Sanity check: make sure we're in the directory of the script
set DIR=%~dp0
cd %DIR%
set PACKAGE_DIRS=..\..\
meteor run %*

View file

@ -0,0 +1,5 @@
# sanity check: make sure we're in the root directory of the example
cd "$( dirname "$0" )"
# let Meteor find the local package
PACKAGE_DIRS=../../ meteor run "$@"

View file

@ -0,0 +1,75 @@
Meteor.startup(function () {
if (Types.find().count() === 0) {
[
{
name: 'String',
icon: '<span class="glyphicon glyphicon-tag" aria-hidden="true"></span>'
},
{
name: 'Text, multi-line',
icon: '<i class="mdi-communication-message" aria-hidden="true"></i>'
},
{
name: 'Category',
icon: '<span class="glyphicon glyphicon-list" aria-hidden="true"></span>'
},
{
name: 'Number',
icon: '<i class="mdi-image-looks-one" aria-hidden="true"></i>'
},
{
name: 'Date',
icon: '<span class="glyphicon glyphicon-calendar" aria-hidden="true"></span>'
},
{
name: 'Hyperlink',
icon: '<span class="glyphicon glyphicon-link" aria-hidden="true"></span>'
},
{
name: 'Image',
icon: '<span class="glyphicon glyphicon-picture" aria-hidden="true"></span>'
},
{
name: 'Progress',
icon: '<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>'
},
{
name: 'Duration',
icon: '<span class="glyphicon glyphicon-time" aria-hidden="true"></span>'
},
{
name: 'Map address',
icon: '<i class="mdi-maps-place" aria-hidden="true"></i>'
},
{
name: 'Relationship',
icon: '<span class="glyphicon glyphicon-flash" aria-hidden="true"></span>'
}
].forEach(function (type, i) {
Types.insert({
name: type.name,
icon: type.icon,
order: i
});
}
);
console.log('Initialized attribute types.');
}
if (Attributes.find().count() === 0) {
[
{ name: 'Name', type: 'String' },
{ name: 'Created at', type: 'Date' },
{ name: 'Link', type: 'Hyperlink' },
{ name: 'Owner', type: 'Relationship' }
].forEach(function (attribute, i) {
Attributes.insert({
name: attribute.name,
type: attribute.type,
order: i
});
}
);
console.log('Created sample object type.');
}
});

View file

@ -0,0 +1,3 @@
'use strict';
Sortable.collections = ['attributes'];

View file

@ -0,0 +1,16 @@
'use strict';
Meteor.methods({
/**
* Update the sortField of documents with given ids in a collection, incrementing it by incDec
* @param {String} collectionName - name of the collection to update
* @param {String[]} ids - array of document ids
* @param {String} orderField - the name of the order field, usually "order"
* @param {Number} incDec - pass 1 or -1
*/
'rubaxa:sortable/collection-update': function (collectionName, ids, sortField, incDec) {
var selector = {_id: {$in: ids}}, modifier = {$inc: {}};
modifier.$inc[sortField] = incDec;
Mongo.Collection.get(collectionName).update(selector, modifier, {multi: true});
}
});

View file

@ -0,0 +1,31 @@
'use strict';
Sortable = {};
Sortable.collections = []; // array of collection names that the client is allowed to reorder
Meteor.methods({
/**
* Update the sortField of documents with given ids in a collection, incrementing it by incDec
* @param {String} collectionName - name of the collection to update
* @param {String[]} ids - array of document ids
* @param {String} orderField - the name of the order field, usually "order"
* @param {Number} incDec - pass 1 or -1
*/
'rubaxa:sortable/collection-update': function (collectionName, ids, sortField, incDec) {
check(collectionName, String);
// don't allow the client to modify just any collection
if (!Sortable || !Array.isArray(Sortable.collections)) {
throw new Meteor.Error(500, 'Please define Sortable.collections');
}
if (Sortable.collections.indexOf(collectionName) === -1) {
throw new Meteor.Error(403, 'Collection <' + collectionName + '> is not Sortable. Please add it to Sortable.collections in server code.');
}
check(ids, [String]);
check(sortField, String);
check(incDec, Number);
var selector = {_id: {$in: ids}}, modifier = {$inc: {}};
modifier.$inc[sortField] = incDec;
Mongo.Collection.get(collectionName).update(selector, modifier, {multi: true});
}
});

View file

@ -0,0 +1,85 @@
// Package metadata file for Meteor.js
'use strict';
var packageName = 'rubaxa:sortable'; // https://atmospherejs.com/rubaxa/sortable
var gitHubPath = 'RubaXa/Sortable'; // https://github.com/RubaXa/Sortable
var npmPackageName = 'sortablejs'; // https://www.npmjs.com/package/sortablejs - optional but recommended; used as fallback if GitHub fails
/* All of the below is just to get the version number of the 3rd party library.
* First we'll try to read it from package.json. This works when publishing or testing the package
* but not when running an example app that uses a local copy of the package because the current
* directory will be that of the app, and it won't have package.json. Finding the path of a file is hard:
* http://stackoverflow.com/questions/27435797/how-do-i-obtain-the-path-of-a-file-in-a-meteor-package
* Therefore, we'll fall back to GitHub (which is more frequently updated), and then to NPMJS.
* We also don't have the HTTP package at this stage, and if we use Package.* in the request() callback,
* it will error that it must be run in a Fiber. So we'll use Node futures.
*/
var request = Npm.require('request');
var Future = Npm.require('fibers/future');
var fut = new Future;
var version;
if (!version) try {
var packageJson = JSON.parse(Npm.require('fs').readFileSync('../package.json'));
version = packageJson.version;
} catch (e) {
// if the file was not found, fall back to GitHub
console.warn('Could not find ../package.json to read version number from; trying GitHub...');
var url = 'https://api.github.com/repos/' + gitHubPath + '/tags';
request.get({
url: url,
headers: {
'User-Agent': 'request' // GitHub requires it
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var versions = JSON.parse(body).map(function (version) {
return version['name'].replace(/^\D+/, '') // trim leading non-digits from e.g. "v4.3.0"
}).sort();
fut.return(versions[versions.length -1]);
} else {
// GitHub API rate limit reached? Fall back to npmjs.
console.warn('GitHub request to', url, 'failed:\n ', response && response.statusCode, response && response.body, error || '', '\nTrying NPMJS...');
url = 'http://registry.npmjs.org/' + npmPackageName + '/latest';
request.get(url, function (error, response, body) {
if (!error && response.statusCode === 200)
fut.return(JSON.parse(body).version);
else
fut.throw('Could not get version information from ' + url + ' either (incorrect package name?):\n' + (response && response.statusCode || '') + (response && response.body || '') + (error || ''));
});
}
});
version = fut.wait();
}
// Now that we finally have an accurate version number...
Package.describe({
name: packageName,
summary: 'Sortable: reactive minimalist reorderable drag-and-drop lists on modern browsers and touch devices',
version: version,
git: 'https://github.com/RubaXa/Sortable.git',
documentation: 'README.md'
});
Package.onUse(function (api) {
api.versionsFrom(['METEOR@0.9.0', 'METEOR@1.0']);
api.use('templating', 'client');
api.use('dburles:mongo-collection-instances@0.3.4'); // to watch collections getting created
api.export('Sortable'); // exported on the server too, as a global to hold the array of sortable collections (for security)
api.addFiles([
'../Sortable.js',
'template.html', // the HTML comes first, so reactivize.js can refer to the template in it
'reactivize.js'
], 'client');
api.addFiles('methods-client.js', 'client');
api.addFiles('methods-server.js', 'server');
});
Package.onTest(function (api) {
api.use(packageName, 'client');
api.use('tinytest', 'client');
api.addFiles('test.js', 'client');
});

View file

@ -0,0 +1,26 @@
#!/bin/bash
# Publish package to Meteor's repository, Atmospherejs.com
# Make sure Meteor is installed, per https://www.meteor.com/install.
# The curl'ed script is totally safe; takes 2 minutes to read its source and check.
type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; }
# sanity check: make sure we're in the directory of the script
cd "$( dirname "$0" )"
# publish package, creating it if it's the first time we're publishing
PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2)
echo "Publishing $PACKAGE_NAME..."
# Attempt to re-publish the package - the most common operation once the initial release has
# been made. If the package name was changed (rare), you'll have to pass the --create flag.
meteor publish "$@"; EXIT_CODE=$?
if (( $EXIT_CODE == 0 )); then
echo "Thanks for releasing a new version. You can see it at"
echo "https://atmospherejs.com/${PACKAGE_NAME/://}"
else
echo "We have an error. Please post it at https://github.com/RubaXa/Sortable/issues"
fi
exit $EXIT_CODE

View file

@ -0,0 +1,201 @@
/*
Make a Sortable reactive by binding it to a Mongo.Collection.
Calls `rubaxa:sortable/collection-update` on the server to update the sortField of affected records.
TODO:
* supply consecutive values if the `order` field doesn't have any
* .get(DOMElement) - return the Sortable object of a DOMElement
* create a new _id automatically onAdd if the event.from list had pull: 'clone'
* support arrays
* sparse arrays
* tests
* drop onto existing empty lists
* insert back into lists emptied by dropping
* performance on dragging into long list at the beginning
* handle failures on Collection operations, e.g. add callback to .insert
* when adding elements, update ranks just for the half closer to the start/end of the list
* revisit http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible
* reproduce the insidious bug where the list isn't always sorted (fiddle with dragging #1 over #2, then back, then #N before #1)
*/
'use strict';
Template.sortable.created = function () {
var templateInstance = this;
// `this` is a template instance that can store properties of our choice - http://docs.meteor.com/#/full/template_inst
if (templateInstance.setupDone) return; // paranoid: only run setup once
// this.data is the data context - http://docs.meteor.com/#/full/template_data
// normalize all options into templateInstance.options, and remove them from .data
templateInstance.options = templateInstance.data.options || {};
Object.keys(templateInstance.data).forEach(function (key) {
if (key === 'options' || key === 'items') return;
templateInstance.options[key] = templateInstance.data[key];
delete templateInstance.data[key];
});
templateInstance.options.sortField = templateInstance.options.sortField || 'order';
// We can get the collection via the .collection property of the cursor, but changes made that way
// will NOT be sent to the server - https://github.com/meteor/meteor/issues/3271#issuecomment-66656257
// Thus we need to use dburles:mongo-collection-instances to get a *real* collection
if (templateInstance.data.items && templateInstance.data.items.collection) {
// cursor passed via items=; its .collection works client-only and has a .name property
templateInstance.collectionName = templateInstance.data.items.collection.name;
templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName);
} else if (templateInstance.data.items) {
// collection passed via items=; does NOT have a .name property, but _name
templateInstance.collection = templateInstance.data.items;
templateInstance.collectionName = templateInstance.collection._name;
} else if (templateInstance.data.collection) {
// cursor passed directly
templateInstance.collectionName = templateInstance.data.collection.name;
templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName);
} else {
templateInstance.collection = templateInstance.data; // collection passed directly
templateInstance.collectionName = templateInstance.collection._name;
}
// TODO if (Array.isArray(templateInstance.collection))
// What if user filters some of the items in the cursor, instead of ordering the entire collection?
// Use case: reorder by preference movies of a given genre, a filter within all movies.
// A: Modify all intervening items **that are on the client**, to preserve the overall order
// TODO: update *all* orders via a server method that takes not ids, but start & end elements - mild security risk
delete templateInstance.data.options;
/**
* When an element was moved, adjust its orders and possibly the order of
* other elements, so as to maintain a consistent and correct order.
*
* There are three approaches to this:
* 1) Using arbitrary precision arithmetic and setting only the order of the moved
* element to the average of the orders of the elements around it -
* http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible
* The downside is that the order field in the DB will increase by one byte every
* time an element is reordered.
* 2) Adjust the orders of the intervening items. This keeps the orders sane (integers)
* but is slower because we have to modify multiple documents.
* TODO: we may be able to update fewer records by only altering the
* order of the records between the newIndex/oldIndex and the start/end of the list.
* 3) Use regular precision arithmetic, but when the difference between the orders of the
* moved item and the one before/after it falls below a certain threshold, adjust
* the order of that other item, and cascade doing so up or down the list.
* This will keep the `order` field constant in size, and will only occasionally
* require updating the `order` of other records.
*
* For now, we use approach #2.
*
* @param {String} itemId - the _id of the item that was moved
* @param {Number} orderPrevItem - the order of the item before it, or null
* @param {Number} orderNextItem - the order of the item after it, or null
*/
templateInstance.adjustOrders = function adjustOrders(itemId, orderPrevItem, orderNextItem) {
var orderField = templateInstance.options.sortField;
var selector = templateInstance.options.selector || {}, modifier = {$set: {}};
var ids = [];
var startOrder = templateInstance.collection.findOne(itemId)[orderField];
if (orderPrevItem !== null) {
// Element has a previous sibling, therefore it was moved down in the list.
// Decrease the order of intervening elements.
selector[orderField] = {$lte: orderPrevItem, $gt: startOrder};
ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id');
Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, -1);
// Set the order of the dropped element to the order of its predecessor, whose order was decreased
modifier.$set[orderField] = orderPrevItem;
} else {
// element moved up the list, increase order of intervening elements
selector[orderField] = {$gte: orderNextItem, $lt: startOrder};
ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id');
Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, 1);
// Set the order of the dropped element to the order of its successor, whose order was increased
modifier.$set[orderField] = orderNextItem;
}
templateInstance.collection.update(itemId, modifier);
};
templateInstance.setupDone = true;
};
Template.sortable.rendered = function () {
var templateInstance = this;
var orderField = templateInstance.options.sortField;
// sorting was changed within the list
var optionsOnUpdate = templateInstance.options.onUpdate;
templateInstance.options.onUpdate = function sortableUpdate(/**Event*/event) {
var itemEl = event.item; // dragged HTMLElement
event.data = Blaze.getData(itemEl);
if (event.newIndex < event.oldIndex) {
// Element moved up in the list. The dropped element has a next sibling for sure.
var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField];
templateInstance.adjustOrders(event.data._id, null, orderNextItem);
} else if (event.newIndex > event.oldIndex) {
// Element moved down in the list. The dropped element has a previous sibling for sure.
var orderPrevItem = Blaze.getData(itemEl.previousElementSibling)[orderField];
templateInstance.adjustOrders(event.data._id, orderPrevItem, null);
} else {
// do nothing - drag and drop in the same location
}
if (optionsOnUpdate) optionsOnUpdate(event);
};
// element was added from another list
var optionsOnAdd = templateInstance.options.onAdd;
templateInstance.options.onAdd = function sortableAdd(/**Event*/event) {
var itemEl = event.item; // dragged HTMLElement
event.data = Blaze.getData(itemEl);
// let the user decorate the object with additional properties before insertion
if (optionsOnAdd) optionsOnAdd(event);
// Insert the new element at the end of the list and move it where it was dropped.
// We could insert it at the beginning, but that would lead to negative orders.
var sortSpecifier = {}; sortSpecifier[orderField] = -1;
event.data.order = templateInstance.collection.findOne({}, { sort: sortSpecifier, limit: 1 }).order + 1;
// TODO: this can obviously be optimized by setting the order directly as the arithmetic average, with the caveats described above
var newElementId = templateInstance.collection.insert(event.data);
event.data._id = newElementId;
if (itemEl.nextElementSibling) {
var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField];
templateInstance.adjustOrders(newElementId, null, orderNextItem);
} else {
// do nothing - inserted after the last element
}
// remove the dropped HTMLElement from the list because we have inserted it in the collection, which will update the template
itemEl.parentElement.removeChild(itemEl);
};
// element was removed by dragging into another list
var optionsOnRemove = templateInstance.options.onRemove;
templateInstance.options.onRemove = function sortableRemove(/**Event*/event) {
var itemEl = event.item; // dragged HTMLElement
event.data = Blaze.getData(itemEl);
// don't remove from the collection if group.pull is clone or false
if (typeof templateInstance.options.group === 'undefined'
|| typeof templateInstance.options.group.pull === 'undefined'
|| templateInstance.options.group.pull === true
) templateInstance.collection.remove(event.data._id);
if (optionsOnRemove) optionsOnRemove(event);
};
// just compute the `data` context
['onStart', 'onEnd', 'onSort', 'onFilter'].forEach(function (eventHandler) {
if (templateInstance.options[eventHandler]) {
var userEventHandler = templateInstance.options[eventHandler];
templateInstance.options[eventHandler] = function (/**Event*/event) {
var itemEl = event.item; // dragged HTMLElement
event.data = Blaze.getData(itemEl);
userEventHandler(event);
};
}
});
templateInstance.sortable = Sortable.create(templateInstance.firstNode.parentElement, templateInstance.options);
// TODO make the object accessible, e.g. via Sortable.getSortableById() or some such
};
Template.sortable.destroyed = function () {
if(this.sortable) this.sortable.destroy();
};

View file

@ -0,0 +1,8 @@
@echo off
REM Test Meteor package before publishing to Atmospherejs.com
REM Sanity check: make sure we're in the directory of the script
set DIR=%~dp0
cd %DIR%
meteor test-packages ./ %*

View file

@ -0,0 +1,35 @@
#!/bin/sh
# Test Meteor package before publishing to Atmospherejs.com
# Make sure Meteor is installed, per https://www.meteor.com/install.
# The curl'ed script is totally safe; takes 2 minutes to read its source and check.
type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; }
# sanity check: make sure we're in the directory of the script
cd "$( dirname "$0" )"
# delete the temporary files even if Ctrl+C is pressed
int_trap() {
printf "\nTests interrupted. Cleaning up...\n\n"
}
trap int_trap INT
EXIT_CODE=0
PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2)
echo "### Testing $PACKAGE_NAME..."
# provide an invalid MONGO_URL so Meteor doesn't bog us down with an empty Mongo database
if [ $# -gt 0 ]; then
# interpret any parameter to mean we want an interactive test
MONGO_URL=mongodb:// meteor test-packages ./
else
# automated/CI test with phantomjs
./node_modules/.bin/spacejam --mongo-url mongodb:// test-packages ./
EXIT_CODE=$(( $EXIT_CODE + $? ))
fi
exit $EXIT_CODE

View file

@ -0,0 +1,5 @@
<template name="sortable">
{{#each items}}
{{> Template.contentBlock this}}
{{/each}}
</template>

View file

@ -0,0 +1,9 @@
'use strict';
Tinytest.add('Sortable.is', function (test) {
var items = document.createElement('ul');
items.innerHTML = '<li data-id="one">item 1</li><li data-id="two">item 2</li><li data-id="three">item 3</li>';
var sortable = new Sortable(items);
test.instanceOf(sortable, Sortable, 'Instantiation OK');
test.length(sortable.toArray(), 3, 'Three elements');
});

View file

@ -0,0 +1,191 @@
/**
* @author RubaXa <trash@rubaxa.org>
* @licence MIT
*/
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['angular', './Sortable'], factory);
}
else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
require('angular');
factory(angular, require('./Sortable'));
module.exports = 'ng-sortable';
}
else if (window.angular && window.Sortable) {
factory(angular, Sortable);
}
})(function (angular, Sortable) {
'use strict';
/**
* @typedef {Object} ngSortEvent
* @property {*} model List item
* @property {Object|Array} models List of items
* @property {number} oldIndex before sort
* @property {number} newIndex after sort
*/
var expando = 'Sortable:ng-sortable';
angular.module('ng-sortable', [])
.constant('ngSortableVersion', '0.4.0')
.constant('ngSortableConfig', {})
.directive('ngSortable', ['$parse', 'ngSortableConfig', function ($parse, ngSortableConfig) {
var removed,
nextSibling,
getSourceFactory = function getSourceFactory(el, scope) {
var ngRepeat = [].filter.call(el.childNodes, function (node) {
return (
(node.nodeType === 8) &&
(node.nodeValue.indexOf('ngRepeat:') !== -1)
);
})[0];
if (!ngRepeat) {
// Without ng-repeat
return function () {
return null;
};
}
// tests: http://jsbin.com/kosubutilo/1/edit?js,output
ngRepeat = ngRepeat.nodeValue.match(/ngRepeat:\s*(?:\(.*?,\s*)?([^\s)]+)[\s)]+in\s+([^\s|]+)/);
var itemsExpr = $parse(ngRepeat[2]);
return function () {
return itemsExpr(scope.$parent) || [];
};
};
// Export
return {
restrict: 'AC',
scope: { ngSortable: "=?" },
link: function (scope, $el) {
var el = $el[0],
options = angular.extend(scope.ngSortable || {}, ngSortableConfig),
watchers = [],
getSource = getSourceFactory(el, scope),
sortable
;
el[expando] = getSource;
function _emitEvent(/**Event*/evt, /*Mixed*/item) {
var name = 'on' + evt.type.charAt(0).toUpperCase() + evt.type.substr(1);
var source = getSource();
/* jshint expr:true */
options[name] && options[name]({
model: item || source[evt.newIndex],
models: source,
oldIndex: evt.oldIndex,
newIndex: evt.newIndex
});
}
function _sync(/**Event*/evt) {
var items = getSource();
if (!items) {
// Without ng-repeat
return;
}
var oldIndex = evt.oldIndex,
newIndex = evt.newIndex;
if (el !== evt.from) {
var prevItems = evt.from[expando]();
removed = prevItems[oldIndex];
if (evt.clone) {
removed = angular.copy(removed);
prevItems.splice(Sortable.utils.index(evt.clone), 0, prevItems.splice(oldIndex, 1)[0]);
evt.from.removeChild(evt.clone);
}
else {
prevItems.splice(oldIndex, 1);
}
items.splice(newIndex, 0, removed);
evt.from.insertBefore(evt.item, nextSibling); // revert element
}
else {
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]);
}
scope.$apply();
}
sortable = Sortable.create(el, Object.keys(options).reduce(function (opts, name) {
opts[name] = opts[name] || options[name];
return opts;
}, {
onStart: function (/**Event*/evt) {
nextSibling = evt.item.nextSibling;
_emitEvent(evt);
scope.$apply();
},
onEnd: function (/**Event*/evt) {
_emitEvent(evt, removed);
scope.$apply();
},
onAdd: function (/**Event*/evt) {
_sync(evt);
_emitEvent(evt, removed);
scope.$apply();
},
onUpdate: function (/**Event*/evt) {
_sync(evt);
_emitEvent(evt);
},
onRemove: function (/**Event*/evt) {
_emitEvent(evt, removed);
},
onSort: function (/**Event*/evt) {
_emitEvent(evt);
}
}));
$el.on('$destroy', function () {
angular.forEach(watchers, function (/** Function */unwatch) {
unwatch();
});
sortable.destroy();
el[expando] = null;
el = null;
watchers = null;
sortable = null;
nextSibling = null;
});
angular.forEach([
'sort', 'disabled', 'draggable', 'handle', 'animation', 'group', 'ghostClass', 'filter',
'onStart', 'onEnd', 'onAdd', 'onUpdate', 'onRemove', 'onSort'
], function (name) {
watchers.push(scope.$watch('ngSortable.' + name, function (value) {
if (value !== void 0) {
options[name] = value;
if (!/^on[A-Z]/.test(name)) {
sortable.option(name, value);
}
}
}));
});
}
};
}]);
});

View file

@ -0,0 +1,41 @@
{
"name": "sortablejs",
"exportName": "Sortable",
"version": "1.4.2",
"devDependencies": {
"grunt": "*",
"grunt-version": "*",
"grunt-exec": "*",
"grunt-contrib-jshint": "0.9.2",
"grunt-contrib-uglify": "*",
"spacejam": "*"
},
"description": "Minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports AngularJS and any CSS library, e.g. Bootstrap.",
"main": "Sortable.js",
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
"url": "git://github.com/rubaxa/Sortable.git"
},
"keywords": [
"sortable",
"reorder",
"drag",
"meteor",
"angular",
"ng-sortable",
"react",
"mixin"
],
"author": "Konstantin Lebedev <ibnRubaXa@gmail.com>",
"license": "MIT",
"spm": {
"main": "Sortable.js",
"ignore": [
"meteor",
"st"
]
}
}

View file

@ -0,0 +1,165 @@
/**
* @author RubaXa <trash@rubaxa.org>
* @licence MIT
*/
(function (factory) {
'use strict';
if (typeof module != 'undefined' && typeof module.exports != 'undefined') {
module.exports = factory(require('./Sortable'));
}
else if (typeof define === 'function' && define.amd) {
define(['./Sortable'], factory);
}
else {
/* jshint sub:true */
window['SortableMixin'] = factory(Sortable);
}
})(function (/** Sortable */Sortable) {
'use strict';
var _nextSibling;
var _activeComponent;
var _defaultOptions = {
ref: 'list',
model: 'items',
animation: 100,
onStart: 'handleStart',
onEnd: 'handleEnd',
onAdd: 'handleAdd',
onUpdate: 'handleUpdate',
onRemove: 'handleRemove',
onSort: 'handleSort',
onFilter: 'handleFilter',
onMove: 'handleMove'
};
function _getModelName(component) {
return component.sortableOptions && component.sortableOptions.model || _defaultOptions.model;
}
function _getModelItems(component) {
var name = _getModelName(component),
items = component.state && component.state[name] || component.props[name];
return items.slice();
}
function _extend(dst, src) {
for (var key in src) {
if (src.hasOwnProperty(key)) {
dst[key] = src[key];
}
}
return dst;
}
/**
* Simple and easy mixin-wrapper for rubaxa/Sortable library, in order to
* make reorderable drag-and-drop lists on modern browsers and touch devices.
*
* @mixin
*/
var SortableMixin = {
sortableMixinVersion: '0.1.1',
/**
* @type {Sortable}
* @private
*/
_sortableInstance: null,
componentDidMount: function () {
var DOMNode, options = _extend(_extend({}, _defaultOptions), this.sortableOptions || {}),
copyOptions = _extend({}, options),
emitEvent = function (/** string */type, /** Event */evt) {
var method = this[options[type]];
method && method.call(this, evt, this._sortableInstance);
}.bind(this);
// Bind callbacks so that "this" refers to the component
'onStart onEnd onAdd onSort onUpdate onRemove onFilter onMove'.split(' ').forEach(function (/** string */name) {
copyOptions[name] = function (evt) {
if (name === 'onStart') {
_nextSibling = evt.item.nextElementSibling;
_activeComponent = this;
}
else if (name === 'onAdd' || name === 'onUpdate') {
evt.from.insertBefore(evt.item, _nextSibling);
var newState = {},
remoteState = {},
oldIndex = evt.oldIndex,
newIndex = evt.newIndex,
items = _getModelItems(this),
remoteItems,
item;
if (name === 'onAdd') {
remoteItems = _getModelItems(_activeComponent);
item = remoteItems.splice(oldIndex, 1)[0];
items.splice(newIndex, 0, item);
remoteState[_getModelName(_activeComponent)] = remoteItems;
}
else {
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]);
}
newState[_getModelName(this)] = items;
if (copyOptions.stateHandler) {
this[copyOptions.stateHandler](newState);
} else {
this.setState(newState);
}
(this !== _activeComponent) && _activeComponent.setState(remoteState);
}
setTimeout(function () {
emitEvent(name, evt);
}, 0);
}.bind(this);
}, this);
DOMNode = this.getDOMNode() ? (this.refs[options.ref] || this).getDOMNode() : this.refs[options.ref] || this;
/** @namespace this.refs — http://facebook.github.io/react/docs/more-about-refs.html */
this._sortableInstance = Sortable.create(DOMNode, copyOptions);
},
componentWillReceiveProps: function (nextProps) {
var newState = {},
modelName = _getModelName(this),
items = nextProps[modelName];
if (items) {
newState[modelName] = items;
this.setState(newState);
}
},
componentWillUnmount: function () {
this._sortableInstance.destroy();
this._sortableInstance = null;
}
};
// Export
return SortableMixin;
});

View file

@ -0,0 +1,239 @@
html {
background-image: -webkit-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%);
background-image: -ms-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%);
background-image: linear-gradient(to bottom, #F4E2C9 20%, #F4D7C9 100%);
}
html, body {
margin: 0;
padding: 0;
position: relative;
color: #464637;
min-height: 100%;
font-size: 20px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
h1 {
color: #FF3F00;
font-size: 20px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
text-align: center;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.container {
width: 80%;
margin: auto;
min-width: 1100px;
max-width: 1300px;
position: relative;
}
@media (min-width: 750px) and (max-width: 970px){
.container {
width: 100%;
min-width: 750px;
}
}
.sortable-ghost {
opacity: .2;
}
img {
border: 0;
vertical-align: middle;
}
.logo {
top: 55px;
left: 30px;
position: absolute;
}
.title {
color: #fff;
padding: 3px 10px;
display: inline-block;
position: relative;
background-color: #FF7373;
z-index: 1000;
}
.title_xl {
padding: 3px 15px;
font-size: 40px;
}
.tile {
width: 22%;
min-width: 245px;
color: #FF7270;
padding: 10px 30px;
text-align: center;
margin-top: 15px;
margin-left: 5px;
margin-right: 30px;
background-color: #fff;
display: inline-block;
vertical-align: top;
}
.tile__name {
cursor: move;
padding-bottom: 10px;
border-bottom: 1px solid #FF7373;
}
.tile__list {
margin-top: 10px;
}
.tile__list:last-child {
margin-right: 0;
min-height: 80px;
}
.tile__list img {
cursor: move;
margin: 10px;
border-radius: 100%;
}
.block {
opacity: 1;
position: absolute;
}
.block__list {
padding: 20px 0;
max-width: 360px;
margin-top: -8px;
margin-left: 5px;
background-color: #fff;
}
.block__list-title {
margin: -20px 0 0;
padding: 10px;
text-align: center;
background: #5F9EDF;
}
.block__list li { cursor: move; }
.block__list_words li {
background-color: #fff;
padding: 10px 40px;
}
.block__list_words .sortable-ghost {
opacity: 0.4;
background-color: #F4E2C9;
}
.block__list_words li:first-letter {
text-transform: uppercase;
}
.block__list_tags {
padding-left: 30px;
}
.block__list_tags:after {
clear: both;
content: '';
display: block;
}
.block__list_tags li {
color: #fff;
float: left;
margin: 8px 20px 10px 0;
padding: 5px 10px;
min-width: 10px;
background-color: #5F9EDF;
text-align: center;
}
.block__list_tags li:first-child:first-letter {
text-transform: uppercase;
}
#editable {}
#editable li {
position: relative;
}
#editable i {
-webkit-transition: opacity .2s;
transition: opacity .2s;
opacity: 0;
display: block;
cursor: pointer;
color: #c00;
top: 10px;
right: 40px;
position: absolute;
font-style: normal;
}
#editable li:hover i {
opacity: 1;
}
#filter {}
#filter button {
color: #fff;
width: 100%;
border: none;
outline: 0;
opacity: .5;
margin: 10px 0 0;
transition: opacity .1s ease;
cursor: pointer;
background: #5F9EDF;
padding: 10px 0;
font-size: 20px;
}
#filter button:hover {
opacity: 1;
}
#filter .block__list {
padding-bottom: 0;
}
.drag-handle {
margin-right: 10px;
font: bold 20px Sans-Serif;
color: #5F9EDF;
display: inline-block;
cursor: move;
cursor: -webkit-grabbing; /* overrides 'move' */
}
#todos input {
padding: 5px;
font-size: 14px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
#nested ul li {
background-color: rgba(0,0,0,.05);
}

View file

@ -0,0 +1,226 @@
(function () {
'use strict';
var byId = function (id) { return document.getElementById(id); },
loadScripts = function (desc, callback) {
var deps = [], key, idx = 0;
for (key in desc) {
deps.push(key);
}
(function _next() {
var pid,
name = deps[idx],
script = document.createElement('script');
script.type = 'text/javascript';
script.src = desc[deps[idx]];
pid = setInterval(function () {
if (window[name]) {
clearTimeout(pid);
deps[idx++] = window[name];
if (deps[idx]) {
_next();
} else {
callback.apply(null, deps);
}
}
}, 30);
document.getElementsByTagName('head')[0].appendChild(script);
})()
},
console = window.console;
if (!console.log) {
console.log = function () {
alert([].join.apply(arguments, ' '));
};
}
Sortable.create(byId('foo'), {
group: "words",
animation: 150,
store: {
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group);
return order ? order.split('|') : [];
},
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group, order.join('|'));
}
},
onAdd: function (evt){ console.log('onAdd.foo:', [evt.item, evt.from]); },
onUpdate: function (evt){ console.log('onUpdate.foo:', [evt.item, evt.from]); },
onRemove: function (evt){ console.log('onRemove.foo:', [evt.item, evt.from]); },
onStart:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);},
onSort:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);},
onEnd: function(evt){ console.log('onEnd.foo:', [evt.item, evt.from]);}
});
Sortable.create(byId('bar'), {
group: "words",
animation: 150,
onAdd: function (evt){ console.log('onAdd.bar:', evt.item); },
onUpdate: function (evt){ console.log('onUpdate.bar:', evt.item); },
onRemove: function (evt){ console.log('onRemove.bar:', evt.item); },
onStart:function(evt){ console.log('onStart.foo:', evt.item);},
onEnd: function(evt){ console.log('onEnd.foo:', evt.item);}
});
// Multi groups
Sortable.create(byId('multi'), {
animation: 150,
draggable: '.tile',
handle: '.tile__name'
});
[].forEach.call(byId('multi').getElementsByClassName('tile__list'), function (el){
Sortable.create(el, {
group: 'photo',
animation: 150
});
});
// Editable list
var editableList = Sortable.create(byId('editable'), {
animation: 150,
filter: '.js-remove',
onFilter: function (evt) {
evt.item.parentNode.removeChild(evt.item);
}
});
byId('addUser').onclick = function () {
Ply.dialog('prompt', {
title: 'Add',
form: { name: 'name' }
}).done(function (ui) {
var el = document.createElement('li');
el.innerHTML = ui.data.name + '<i class="js-remove">✖</i>';
editableList.el.appendChild(el);
});
};
// Advanced groups
[{
name: 'advanced',
pull: true,
put: true
},
{
name: 'advanced',
pull: 'clone',
put: false
}, {
name: 'advanced',
pull: false,
put: true
}].forEach(function (groupOpts, i) {
Sortable.create(byId('advanced-' + (i + 1)), {
sort: (i != 1),
group: groupOpts,
animation: 150
});
});
// 'handle' option
Sortable.create(byId('handle-1'), {
handle: '.drag-handle',
animation: 150
});
// Angular example
angular.module('todoApp', ['ng-sortable'])
.constant('ngSortableConfig', {onEnd: function() {
console.log('default onEnd()');
}})
.controller('TodoController', ['$scope', function ($scope) {
$scope.todos = [
{text: 'learn angular', done: true},
{text: 'build an angular app', done: false}
];
$scope.addTodo = function () {
$scope.todos.push({text: $scope.todoText, done: false});
$scope.todoText = '';
};
$scope.remaining = function () {
var count = 0;
angular.forEach($scope.todos, function (todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function () {
var oldTodos = $scope.todos;
$scope.todos = [];
angular.forEach(oldTodos, function (todo) {
if (!todo.done) $scope.todos.push(todo);
});
};
}])
.controller('TodoControllerNext', ['$scope', function ($scope) {
$scope.todos = [
{text: 'learn Sortable', done: true},
{text: 'use ng-sortable', done: false},
{text: 'Enjoy', done: false}
];
$scope.remaining = function () {
var count = 0;
angular.forEach($scope.todos, function (todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.sortableConfig = { group: 'todo', animation: 150 };
'Start End Add Update Remove Sort'.split(' ').forEach(function (name) {
$scope.sortableConfig['on' + name] = console.log.bind(console, name);
});
}]);
})();
// Background
document.addEventListener("DOMContentLoaded", function () {
function setNoiseBackground(el, 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") + ")";
}
setNoiseBackground(document.getElementsByTagName('body')[0], 50, 50, 0.02);
}, false);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- List with handle -->
<div id="listWithHandle" class="list-group">
<div class="list-group-item">
<span class="badge">14</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
Drag me by the handle
</div>
<div class="list-group-item">
<span class="badge">2</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
You can also select text
</div>
<div class="list-group-item">
<span class="badge">1</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
Best of both worlds!
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IFrame playground</title>
</head>
<body>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- Latest Sortable -->
<script src="../../Sortable.js"></script>
<!-- Simple List -->
<div id="simpleList" class="list-group">
<div class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></div>
<div class="list-group-item">It works with Bootstrap...</div>
<div class="list-group-item">...out of the box.</div>
<div class="list-group-item">It has support for touch devices.</div>
<div class="list-group-item">Just drag some elements around.</div>
</div>
<script>
(function () {
Sortable.create(simpleList, {group: 'shared'});
var iframe = document.createElement('iframe');
iframe.src = 'frame.html';
iframe.width = '100%';
iframe.onload = function () {
var doc = iframe.contentDocument,
list = doc.getElementById('listWithHandle');
Sortable.create(list, {group: 'shared'});
};
document.body.appendChild(iframe);
})();
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,25 +0,0 @@
{
"name": "dragula.js",
"version": "3.5.1",
"description": "Drag and drop so simple it hurts",
"main": [
"dist/dragula.js",
"dist/dragula.css"
],
"ignore": [],
"homepage": "https://github.com/bevacqua/dragula",
"authors": [
"Nicolas Bevacqua <nicolasbevacqua@gmail.com>"
],
"license": "MIT",
"_release": "3.5.1",
"_resolution": {
"type": "version",
"tag": "v3.5.1",
"commit": "bbd33f7f871d2f672bebf70bfc054134a48cf0e9"
},
"_source": "git://github.com/bevacqua/dragula.git",
"_target": "~3.5.1",
"_originalSource": "dragula",
"_direct": true
}

View file

@ -1,4 +0,0 @@
node_modules
npm-debug.log
.DS_Store
Thumbs.db

View file

@ -1,4 +0,0 @@
node_modules
bower_components
dist
example

View file

@ -1,23 +0,0 @@
{
"curly": true,
"eqeqeq": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"sub": true,
"undef": true,
"unused": true,
"trailing": true,
"boss": true,
"eqnull": true,
"strict": true,
"immed": true,
"expr": true,
"latedef": "nofunc",
"quotmark": "single",
"validthis": true,
"indent": 2,
"node": true,
"browser": true
}

View file

@ -1,6 +0,0 @@
language: node_js
node_js:
- 'iojs'
- '4'
before_script:
- export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start

View file

@ -1,15 +0,0 @@
{
"name": "dragula.js",
"version": "3.5.1",
"description": "Drag and drop so simple it hurts",
"main": [
"dist/dragula.js",
"dist/dragula.css"
],
"ignore": [],
"homepage": "https://github.com/bevacqua/dragula",
"authors": [
"Nicolas Bevacqua <nicolasbevacqua@gmail.com>"
],
"license": "MIT"
}

View file

@ -1,228 +0,0 @@
# 3.5.1 Which Hunt
- Fixed a bug when determining the mouse button being pressed
- Fixed a bug when determining the element behind the mouse cursor when `ignoreInputTextSelection` was enabled
# 3.5.0 Input Fanatic
- Added a feature where users are able to select text ranges with their mouse in inputs within a dragula container
# 3.4.1 Input Accomodation
- Fixed a bug where text in inputs inside containers assigned to `dragula` couldn't be selected
# 3.4.0 Event Sourcing
- Events for `cancel`, `remove`, and `shadow` now all provide a `source` parameter in the third position
# 3.3.2 Captain Obvious
- Fixed a bug where `out` would be emitted with an `undefined` container
# 3.3.1 Significant Other
- Fixed a fringe bug [(#207)](https://github.com/bevacqua/dragula/pull/207) where the click handler wouldn't work
- Fixed a bug where `drop` events would sometimes not receive the current sibling
# 3.3.0 Brotherhood
- The `options.moves` callback now receives a fourth parameter, the `sibling` found after `el`
- The `drop` event now receives a fourth parameter, the `sibling` found after `el`
# 3.2.0 Sortable Sauce
- You can now use `options.copySortSource` to enable sorting in `copy`-source containers
# 3.1.0 Copy Paste
- You can now set `options.copy` to a method. It'll be invoked once per drag to ask whether the element being dragged should be treated as a copy or not
- Fixed a bug where starting a drag programatically while an element was being dragged resulted in an exception
# 3.0.7 Crossroads
- Fixed a bug in Webpack builds by updating `crossvent` to `1.5.3`
# 3.0.5 Mouse Rat Rock Band
- Fixed a bug where `mousedown` would be prevented and focusing draggable inputs wouldn't be possible
# 3.0.4 IE is the old IE
- Fixed a bug in IE8 by updating `crossvent` to `1.5.2`
# 3.0.3 Forest Fire
- Fixed a bug in Firefox where dragging links and images would result in issues
# 3.0.2 Clickhood Rainforest
- Fixed a _historical_ bug, where click on anchors would be ignored within `dragula` containers in mobile
- Fixed a bug where events wouldn't be gracefully removed if `drake` were destroyed during a drag event
- Now emits `dragend` after `out` to preserve consistency _(because `drag` is emitted before `over`)_
- Fixed another old bug where attempting to remove elements using `removeOnSpill` on mobile would fail
# 3.0.1 Carjacking
- Fixed a bug in mobile, caused by `3.0.0`, where scrolling would be impossible
- Fixed a bug where dragging would cause text selection in IE8
# 3.0.0 Guilty Conscience
- Removed `addContainer` method, which was previously deprecated
- Removed `removeContainer` method, which was previously deprecated
- Removed `delay` option in favor of using `mousemove`
- Drag events now start on the first occurrence of a `mousemove` event
- If `mousemove` never fires, then the `drag` machinery won't start, either
- Changed default value for `invalid`, now always returns `false` by default
- Added `mirrorContainer` option to determine where the mirror gets appended to _(defaults to `document.body`)_
# 2.1.2 Shady Sibling
- Fixed a bug where `shadow` would trigger multiple times while dragging an element over the same spot
# 2.1.1 Classy Drake
- Fixed a bug where adding and removing classes might've caused issues on elements that had foreign CSS classes
- Added an argument to `cloned` event that specifies the kind of clone. Possible values include `mirror` and `copy` at the moment
# 2.1.0 Over and Out
- Added `over` event that fires whenever an element is dragged over a container _(or whenever a drag event starts)_
- Added `out` event that fires whenever an element is dragged out of a container _(or whenever a drag event ends)_
# 2.0.7 Mayhem
- Fixed a bug caused in `2.0.6` where anything would be regarded as a `drake` container
# 2.0.6 Coruscant
- Fixed a bug where `isContainer` would be called with a `el=null` in some situations
# 2.0.5 Cross Ventilation
- Bumped `crossvent@1.5.0`
# 2.0.4 Transit Overload
- Set `gu-transit` after a drag event has fully started
# 2.0.3 Mice Trap
- Fixed a bug where using `.cancel` would throw an exception
# 2.0.2 Aural Emission
- Replaced `contra.emitter` with `contra@1.9.1/emitter`
# 2.0.1 Copycat
- Fixed a bug where dragging a copy back to origin after hovering over another container would still result in a copy being made if you never spilled the item
# 2.0.0 Containerization
- Deprecated `addContainer` method
- Deprecated `removeContainer` method
- Exposed `dragula.containers` collection
- Introduced dynamic `isContainer` method
- Can now omit `containers` argument to `dragula(containers, options)`
- Can now pass `containers` as an option
# 1.7.0 Clickety Click
- Differentiate between drag and click using `delay` option
- Ability to specify which event targets are `invalid` drag triggers
# 1.6.1 Shadow Drake
- Improved shadow positioning when `revertOnSpill` is `true`
# 1.6.0 Lonely Clown Clone
- Added `'cloned'` event when a DOM element is cloned
# 1.5.1 Touchypants
- Fixed an issue where dragula didn't understand where an element was being dropped
# 1.5.0 Drag Racing
- Introduced drag handles so that elements could only be dragged from a handle element
# 1.4.2 Container Camp
- Fixed a bug where `addContainer` and `removeContainer` wouldn't update the list of available containers
- Fixed a bug where `document.body` would be accessed before it was available if the scripts were loaded in the `<head>`
# 1.4.1 Blood Prince
- Fixed an issue where manually started drag events wouldn't know if position changed when an item was dropped in the source container
- Added minor styling to `gu-mirror`, to visually identify that a drag is in progress
# 1.4.0 Top Fuel
- Added a `dragend` event that's always fired
- Added a `dragging` property to API
- Introduced manual `start` API method
- Introduced `addContainer` and `removeContainer` dynamic API
# 1.3.0 Terror
Introduced an `.end` instance API method that gracefully ends the drag event using the last known valid drop target.
# 1.2.4 Brother in Arms
- The `accepts` option now takes a fourth argument, `sibling`, giving us a hint of the precise position the item would be dropped in
# 1.2.3 Breeding Pool
- Fixed a bug in cross browser behavior that caused the hover effect to ignore scrolling
- Fixed a bug where touch events weren't working in obscure versions of IE
# 1.2.2 Originality Accepted
- Improved `accepts` mechanism so that it always accepts the original starting point
# 1.2.1 Firehose
- Fixed a bug introduced in `1.2.0`
- Fixed a bug where cancelling with `revert` enabled wouldn't respect sort order
# 1.2.0 Firefly
- Introduced `moves` option, used to determine if an item is draggable
- Added a `source` parameter for the `drop` event
- Cancelling a drag event when `revertOnSpill` is `true` will now move the element to its original position in the source element instead of appending it
- Fixed a bug where _"cancellations"_ that ended up leaving the dragged element in the source container but changed sort order would trigger a `cancel` event instead of `drop`
- Fixed a bug where _"drops"_ that ended up leaving the element in the exact same place it was dragged from would end up triggering a `drop` event instead of `cancel`
- Added touch event support
# 1.1.4 Fog Creek
- Added `'shadow'` event to enable easy updates to shadow element as it's moved
# 1.1.3 Drag Queen
- Fixed a bug where `dragula` wouldn't make a copy if the element was dropped outside of a target container
- If a dragged element gets removed for an instance that has `copy` set to `true`, a `cancel` event is raised instead
# 1.1.2 Eavesdropping
- Fixed a bug where _"cancellations"_ that ended up leaving the dragged element somewhere other than the source container wouldn't trigger a `drop` event
# 1.1.1 Slipping Jimmy
- Fixed a bug where the movable shadow wouldn't update properly if the element was hovered over the last position of a container
# 1.1.0 Age of Shadows
- Added a movable shadow that gives visual feedback as to where a dragged item would be dropped
- Added an option to remove dragged elements when they are dropped outside of sanctioned containers
- Added an option to revert dragged elements back to their source container when they are dropped outside of sanctioned containers
# 1.0.1 Consuelo
- Removed `console.log` statement
# 1.0.0 IPO
- Initial Public Release

View file

@ -1,33 +0,0 @@
'use strict';
var cache = {};
var start = '(?:^|\\s)';
var end = '(?:\\s|$)';
function lookupClass (className) {
var cached = cache[className];
if (cached) {
cached.lastIndex = 0;
} else {
cache[className] = cached = new RegExp(start + className + end, 'g');
}
return cached;
}
function addClass (el, className) {
var current = el.className;
if (!current.length) {
el.className = className;
} else if (!lookupClass(className).test(current)) {
el.className += ' ' + className;
}
}
function rmClass (el, className) {
el.className = el.className.replace(lookupClass(className), ' ').trim();
}
module.exports = {
add: addClass,
rm: rmClass
};

View file

@ -1,60 +0,0 @@
# Contributing
Hey there! Glad you want to chime in. Here's what you need to know.
### Support Requests
There's now a dedicated support channel in Slack. Visit the `dragula` [demo page][2] to get an invite. Support requests won't be handled through the repository anymore.
If you have a question, make sure it wasn't [already answered][1]. If it wasn't, please refer to the Slack chat. To get an invite, use the badge in the [demo page][2].
> Our goal is to provide answers to the most frequently asked questions somewhere in the documentation.
### Bugs
Bug reports are tricky. Please provide as much context as possible, and if you want to start working on a fix, we'll be forever grateful! Please try and test around for a bit to make sure you're dealing with a bug and not an issue in your implementation.
If possible, provide a demo where the bug is isolated and turned into its smallest possible representation. That would help a lot!
Thanks for reporting bugs, we'd be lost without you.
### Feature Requests
We're still considering feature requests. Note that we might not implement the feature you want, or exactly how you want it. The goal here is to keep making `dragula` awesome while not making it too bloated.
We also dislike overly specific features and favor more abstract ones you the consumer can build other features upon.
# Development
Development flows are based on `npm run` scripts.
### Build
To compile a standalone browserify module, use the following command. A minified version will also be produced. The compiled bundles are placed inside `dist`. Since **these are autogenerated**, please don't include them in your pull requests.
```shell
npm run build
```
You can also run the build continuously, _to faciliate development_, with this command.
```shell
npm start
```
### Test
Run the command below to execute all tests in a DevTools window through Electron. Note that the DevTools will get reloaded whenever your test files change, making tests a breeze!
```shell
npm run test-watch
```
To run tests a single time, simply run the following command. This is used in CI testing.
```shell
npm test
```
[1]: https://github.com/bevacqua/dragula/issues?q=label%3Asupport
[2]: http://bevacqua.github.io/dragula/

View file

@ -1,22 +0,0 @@
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.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: 0.2;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
filter: alpha(opacity=20);
}

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
.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)}

File diff suppressed because one or more lines are too long

View file

@ -1,585 +0,0 @@
'use strict';
var emitter = require('contra/emitter');
var crossvent = require('crossvent');
var classes = require('./classes');
function dragula (initialContainers, options) {
var len = arguments.length;
if (len === 1 && Array.isArray(initialContainers) === false) {
options = initialContainers;
initialContainers = [];
}
var body = document.body;
var documentElement = document.documentElement;
var _mirror; // mirror image
var _source; // source container
var _item; // item being dragged
var _offsetX; // reference x
var _offsetY; // reference y
var _moveX; // reference move x
var _moveY; // reference move y
var _initialSibling; // reference sibling when grabbed
var _currentSibling; // reference sibling now
var _copy; // item used for copying
var _renderTimer; // timer for setTimeout renderMirrorImage
var _lastDropTarget = null; // last container item was over
var _grabbed; // holds mousedown context until first mousemove
var o = options || {};
if (o.moves === void 0) { o.moves = always; }
if (o.accepts === void 0) { o.accepts = always; }
if (o.invalid === void 0) { o.invalid = invalidTarget; }
if (o.containers === void 0) { o.containers = initialContainers || []; }
if (o.isContainer === void 0) { o.isContainer = never; }
if (o.copy === void 0) { o.copy = false; }
if (o.copySortSource === void 0) { o.copySortSource = false; }
if (o.revertOnSpill === void 0) { o.revertOnSpill = false; }
if (o.removeOnSpill === void 0) { o.removeOnSpill = false; }
if (o.direction === void 0) { o.direction = 'vertical'; }
if (o.ignoreInputTextSelection === void 0) { o.ignoreInputTextSelection = true; }
if (o.mirrorContainer === void 0) { o.mirrorContainer = body; }
var drake = emitter({
containers: o.containers,
start: manualStart,
end: end,
cancel: cancel,
remove: remove,
destroy: destroy,
dragging: false
});
if (o.removeOnSpill === true) {
drake.on('over', spillOver).on('out', spillOut);
}
events();
return drake;
function isContainer (el) {
return drake.containers.indexOf(el) !== -1 || o.isContainer(el);
}
function events (remove) {
var op = remove ? 'remove' : 'add';
touchy(documentElement, op, 'mousedown', grab);
touchy(documentElement, op, 'mouseup', release);
}
function eventualMovements (remove) {
var op = remove ? 'remove' : 'add';
touchy(documentElement, op, 'mousemove', startBecauseMouseMoved);
}
function movements (remove) {
var op = remove ? 'remove' : 'add';
touchy(documentElement, op, 'selectstart', preventGrabbed); // IE8
touchy(documentElement, op, 'click', preventGrabbed);
}
function destroy () {
events(true);
release({});
}
function preventGrabbed (e) {
if (_grabbed) {
e.preventDefault();
}
}
function grab (e) {
_moveX = e.clientX;
_moveY = e.clientY;
var ignore = whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
if (ignore) {
return; // we only care about honest-to-god left clicks and touch events
}
var item = e.target;
var context = canStart(item);
if (!context) {
return;
}
_grabbed = context;
eventualMovements();
if (e.type === 'mousedown') {
if (isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208
item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176
} else {
e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155
}
}
}
function startBecauseMouseMoved (e) {
if (!_grabbed) {
return;
}
if (whichMouseButton(e) === 0) {
release({});
return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope
}
if (e.clientX === _moveX && e.clientY === _moveY) {
return;
}
if (o.ignoreInputTextSelection) {
var clientX = getCoord('clientX', e);
var clientY = getCoord('clientY', e);
var elementBehindCursor = document.elementFromPoint(clientX, clientY);
if (isInput(elementBehindCursor)) {
return;
}
}
var grabbed = _grabbed; // call to end() unsets _grabbed
eventualMovements(true);
movements();
end();
start(grabbed);
var offset = getOffset(_item);
_offsetX = getCoord('pageX', e) - offset.left;
_offsetY = getCoord('pageY', e) - offset.top;
classes.add(_copy || _item, 'gu-transit');
renderMirrorImage();
drag(e);
}
function canStart (item) {
if (drake.dragging && _mirror) {
return;
}
if (isContainer(item)) {
return; // don't drag container itself
}
var handle = item;
while (item.parentElement && isContainer(item.parentElement) === false) {
if (o.invalid(item, handle)) {
return;
}
item = item.parentElement; // drag target should be a top element
if (!item) {
return;
}
}
var source = item.parentElement;
if (!source) {
return;
}
if (o.invalid(item, handle)) {
return;
}
var movable = o.moves(item, source, handle, nextEl(item));
if (!movable) {
return;
}
return {
item: item,
source: source
};
}
function manualStart (item) {
var context = canStart(item);
if (context) {
start(context);
}
}
function start (context) {
if (isCopy(context.item, context.source)) {
_copy = context.item.cloneNode(true);
drake.emit('cloned', _copy, context.item, 'copy');
}
_source = context.source;
_item = context.item;
_initialSibling = _currentSibling = nextEl(context.item);
drake.dragging = true;
drake.emit('drag', _item, _source);
}
function invalidTarget () {
return false;
}
function end () {
if (!drake.dragging) {
return;
}
var item = _copy || _item;
drop(item, item.parentElement);
}
function ungrab () {
_grabbed = false;
eventualMovements(true);
movements(true);
}
function release (e) {
ungrab();
if (!drake.dragging) {
return;
}
var item = _copy || _item;
var clientX = getCoord('clientX', e);
var clientY = getCoord('clientY', e);
var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
if (dropTarget && ((_copy && o.copySortSource) || (!_copy || dropTarget !== _source))) {
drop(item, dropTarget);
} else if (o.removeOnSpill) {
remove();
} else {
cancel();
}
}
function drop (item, target) {
var parent = item.parentElement;
if (_copy && o.copySortSource && target === _source) {
parent.removeChild(_item);
}
if (isInitialPlacement(target)) {
drake.emit('cancel', item, _source, _source);
} else {
drake.emit('drop', item, target, _source, _currentSibling);
}
cleanup();
}
function remove () {
if (!drake.dragging) {
return;
}
var item = _copy || _item;
var parent = item.parentElement;
if (parent) {
parent.removeChild(item);
}
drake.emit(_copy ? 'cancel' : 'remove', item, parent, _source);
cleanup();
}
function cancel (revert) {
if (!drake.dragging) {
return;
}
var reverts = arguments.length > 0 ? revert : o.revertOnSpill;
var item = _copy || _item;
var parent = item.parentElement;
if (parent === _source && _copy) {
parent.removeChild(_copy);
}
var initial = isInitialPlacement(parent);
if (initial === false && !_copy && reverts) {
_source.insertBefore(item, _initialSibling);
}
if (initial || reverts) {
drake.emit('cancel', item, _source, _source);
} else {
drake.emit('drop', item, parent, _source, _currentSibling);
}
cleanup();
}
function cleanup () {
var item = _copy || _item;
ungrab();
removeMirrorImage();
if (item) {
classes.rm(item, 'gu-transit');
}
if (_renderTimer) {
clearTimeout(_renderTimer);
}
drake.dragging = false;
if (_lastDropTarget) {
drake.emit('out', item, _lastDropTarget, _source);
}
drake.emit('dragend', item);
_source = _item = _copy = _initialSibling = _currentSibling = _renderTimer = _lastDropTarget = null;
}
function isInitialPlacement (target, s) {
var sibling;
if (s !== void 0) {
sibling = s;
} else if (_mirror) {
sibling = _currentSibling;
} else {
sibling = nextEl(_copy || _item);
}
return target === _source && sibling === _initialSibling;
}
function findDropTarget (elementBehindCursor, clientX, clientY) {
var target = elementBehindCursor;
while (target && !accepted()) {
target = target.parentElement;
}
return target;
function accepted () {
var droppable = isContainer(target);
if (droppable === false) {
return false;
}
var immediate = getImmediateChild(target, elementBehindCursor);
var reference = getReference(target, immediate, clientX, clientY);
var initial = isInitialPlacement(target, reference);
if (initial) {
return true; // should always be able to drop it right back where it was
}
return o.accepts(_item, target, _source, reference);
}
}
function drag (e) {
if (!_mirror) {
return;
}
e.preventDefault();
var clientX = getCoord('clientX', e);
var clientY = getCoord('clientY', e);
var x = clientX - _offsetX;
var y = clientY - _offsetY;
_mirror.style.left = x + 'px';
_mirror.style.top = y + 'px';
var item = _copy || _item;
var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
var changed = dropTarget !== null && dropTarget !== _lastDropTarget;
if (changed || dropTarget === null) {
out();
_lastDropTarget = dropTarget;
over();
}
if (dropTarget === _source && _copy && !o.copySortSource) {
if (item.parentElement) {
item.parentElement.removeChild(item);
}
return;
}
var reference;
var immediate = getImmediateChild(dropTarget, elementBehindCursor);
if (immediate !== null) {
reference = getReference(dropTarget, immediate, clientX, clientY);
} else if (o.revertOnSpill === true && !_copy) {
reference = _initialSibling;
dropTarget = _source;
} else {
if (_copy && item.parentElement) {
item.parentElement.removeChild(item);
}
return;
}
if (
reference === null ||
reference !== item &&
reference !== nextEl(item) &&
reference !== _currentSibling
) {
_currentSibling = reference;
dropTarget.insertBefore(item, reference);
drake.emit('shadow', item, dropTarget, _source);
}
function moved (type) { drake.emit(type, item, _lastDropTarget, _source); }
function over () { if (changed) { moved('over'); } }
function out () { if (_lastDropTarget) { moved('out'); } }
}
function spillOver (el) {
classes.rm(el, 'gu-hide');
}
function spillOut (el) {
if (drake.dragging) { classes.add(el, 'gu-hide'); }
}
function renderMirrorImage () {
if (_mirror) {
return;
}
var rect = _item.getBoundingClientRect();
_mirror = _item.cloneNode(true);
_mirror.style.width = getRectWidth(rect) + 'px';
_mirror.style.height = getRectHeight(rect) + 'px';
classes.rm(_mirror, 'gu-transit');
classes.add(_mirror, 'gu-mirror');
o.mirrorContainer.appendChild(_mirror);
touchy(documentElement, 'add', 'mousemove', drag);
classes.add(o.mirrorContainer, 'gu-unselectable');
drake.emit('cloned', _mirror, _item, 'mirror');
}
function removeMirrorImage () {
if (_mirror) {
classes.rm(o.mirrorContainer, 'gu-unselectable');
touchy(documentElement, 'remove', 'mousemove', drag);
_mirror.parentElement.removeChild(_mirror);
_mirror = null;
}
}
function getImmediateChild (dropTarget, target) {
var immediate = target;
while (immediate !== dropTarget && immediate.parentElement !== dropTarget) {
immediate = immediate.parentElement;
}
if (immediate === documentElement) {
return null;
}
return immediate;
}
function getReference (dropTarget, target, x, y) {
var horizontal = o.direction === 'horizontal';
var reference = target !== dropTarget ? inside() : outside();
return reference;
function outside () { // slower, but able to figure out any position
var len = dropTarget.children.length;
var i;
var el;
var rect;
for (i = 0; i < len; i++) {
el = dropTarget.children[i];
rect = el.getBoundingClientRect();
if (horizontal && rect.left > x) { return el; }
if (!horizontal && rect.top > y) { return el; }
}
return null;
}
function inside () { // faster, but only available if dropped inside a child element
var rect = target.getBoundingClientRect();
if (horizontal) {
return resolve(x > rect.left + getRectWidth(rect) / 2);
}
return resolve(y > rect.top + getRectHeight(rect) / 2);
}
function resolve (after) {
return after ? nextEl(target) : target;
}
}
function isCopy (item, container) {
return typeof o.copy === 'boolean' ? o.copy : o.copy(item, container);
}
}
function touchy (el, op, type, fn) {
var touch = {
mouseup: 'touchend',
mousedown: 'touchstart',
mousemove: 'touchmove'
};
var microsoft = {
mouseup: 'MSPointerUp',
mousedown: 'MSPointerDown',
mousemove: 'MSPointerMove'
};
if (global.navigator.msPointerEnabled) {
crossvent[op](el, microsoft[type], fn);
}
crossvent[op](el, touch[type], fn);
crossvent[op](el, type, fn);
}
function whichMouseButton (e) {
if (e.buttons !== void 0) { return e.buttons; }
if (e.which !== void 0) { return e.which; }
var button = e.button;
if (button !== void 0) { // see https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/event.js#L573-L575
return button & 1 ? 1 : button & 2 ? 3 : (button & 4 ? 2 : 0);
}
}
function getOffset (el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + getScroll('scrollLeft', 'pageXOffset'),
top: rect.top + getScroll('scrollTop', 'pageYOffset')
};
}
function getScroll (scrollProp, offsetProp) {
if (typeof global[offsetProp] !== 'undefined') {
return global[offsetProp];
}
var documentElement = document.documentElement;
if (documentElement.clientHeight) {
return documentElement[scrollProp];
}
var body = document.body;
return body[scrollProp];
}
function getElementBehindPoint (point, x, y) {
var p = point || {};
var state = p.className;
var el;
p.className += ' gu-hide';
el = document.elementFromPoint(x, y);
p.className = state;
return el;
}
function never () { return false; }
function always () { return true; }
function getRectWidth (rect) { return rect.width || (rect.right - rect.left); }
function getRectHeight (rect) { return rect.height || (rect.bottom - rect.top); }
function isInput (el) { return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA'; }
function nextEl (el) {
return el.nextElementSibling || manually();
function manually () {
var sibling = el;
do {
sibling = sibling.nextSibling;
} while (sibling && sibling.nodeType !== 1);
return sibling;
}
}
function getEventHost (e) {
// on touchend event, we have to use `e.changedTouches`
// see http://stackoverflow.com/questions/7192563/touchend-event-properties
// see https://github.com/bevacqua/dragula/issues/34
if (e.targetTouches && e.targetTouches.length) {
return e.targetTouches[0];
}
if (e.changedTouches && e.changedTouches.length) {
return e.changedTouches[0];
}
return e;
}
function getCoord (coord, e) {
var host = getEventHost(e);
var missMap = {
pageX: 'clientX', // IE8
pageY: 'clientY' // IE8
};
if (coord in missMap && !(coord in host) && missMap[coord] in host) {
coord = missMap[coord];
}
return host[coord];
}
module.exports = dragula;

View file

@ -1,14 +0,0 @@
.gu-mirror
position fixed !important
margin 0 !important
z-index 9999 !important
opacity 0.8
.gu-hide
display none !important
.gu-unselectable
user-select none !important
.gu-transit
opacity 0.2

View file

@ -1,194 +0,0 @@
body {
background-color: #942A57;
margin: 0 auto;
max-width: 760px;
}
html, body {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*, *:before, *:after {
-webkit-box-sizing: inherit;
-moz-box-sizing: inherit;
box-sizing: inherit;
}
body, input, button {
font-family: Georgia, Helvetica;
font-size: 17px;
color: #ecf0f1;
}
h1 {
text-align: center;
background-color: #AC5C7E;
margin-top: 20px;
margin-bottom: 0;
padding: 10px;
}
h3 {
background-color: rgba(255, 255, 255, 0.2);
border-bottom: 5px solid #A13462;
text-align: center;
padding: 10px;
}
h3 div {
margin-bottom: 10px;
}
.tagline {
margin-top: 0;
}
.tagline-text {
vertical-align: middle;
}
.__slackin {
float: right;
margin-left: 10px;
vertical-align: middle;
}
.promo {
margin-bottom: 0;
font-style: italic;
padding: 10px;
background-color: #ff4020;
border-bottom: 5px solid #c00;
}
a {
font-weight: bold;
}
a,
a:hover {
color: #ecf0f1;
}
pre {
white-space: pre-wrap;
}
pre code {
color: #fff;
font-size: 14px;
line-height: 1.3;
}
label {
display: block;
margin-bottom: 15px;
}
sub {
display: block;
text-align: right;
margin-top: -10px;
font-size: 11px;
font-style: italic;
}
ul {
margin: 0;
padding: 0;
}
.parent {
background-color: rgba(255, 255, 255, 0.2);
margin: 50px 0;
padding: 20px;
}
input {
border: none;
outline: none;
background-color: #ecf0f1;
padding: 10px;
color: #942A57;
border: 0;
margin: 5px 0;
display: block;
width: 100%;
}
button {
background-color: #ecf0f1;
color: #942A57;
border: 0;
padding: 18px 12px;
margin-left: 6px;
cursor: pointer;
outline: none;
}
button:hover {
background-color: #e74c3c;
color: #ecf0f1;
}
.gh-fork {
position: fixed;
top: 0;
right: 0;
border: 0;
}
/* dragula-specific example page styles */
.wrapper {
display: table;
}
.container {
display: table-cell;
background-color: rgba(255, 255, 255, 0.2);
width: 50%;
}
.container:nth-child(odd) {
background-color: rgba(0, 0, 0, 0.2);
}
/*
* note that styling gu-mirror directly is a bad practice because it's too generic.
* you're better off giving the draggable elements a unique class and styling that directly!
*/
.container > div,
.gu-mirror {
margin: 10px;
padding: 10px;
background-color: rgba(0, 0, 0, 0.2);
transition: opacity 0.4s ease-in-out;
}
.container > div {
cursor: move;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
}
.gu-mirror {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
.container .ex-moved {
background-color: #e74c3c;
}
.container.ex-over {
background-color: rgba(255, 255, 255, 0.3);
}
#left-lovehandles > div,
#right-lovehandles > div {
cursor: initial;
}
.handle {
padding: 0 5px;
margin-right: 5px;
background-color: rgba(0, 0, 0, 0.4);
cursor: move;
}
.image-thing {
margin: 20px 0;
display: block;
text-align: center;
}

View file

@ -1,56 +0,0 @@
'use strict';
var crossvent = require('crossvent');
var sortable = $('sortable');
dragula([$('left-defaults'), $('right-defaults')]);
dragula([$('left-copy'), $('right-copy')], { copy: true });
dragula([$('left-events'), $('right-events')])
.on('drag', function (el) {
el.className = el.className.replace('ex-moved', '');
})
.on('drop', function (el) {
el.className += ' ex-moved';
})
.on('over', function (el, container) {
container.className += ' ex-over';
})
.on('out', function (el, container) {
container.className = container.className.replace('ex-over', '');
});
dragula([$('left-rollbacks'), $('right-rollbacks')], { revertOnSpill: true });
dragula([$('left-lovehandles'), $('right-lovehandles')], {
moves: function (el, container, handle) {
return handle.className === 'handle';
}
});
dragula([$('left-rm-spill'), $('right-rm-spill')], { removeOnSpill: true });
dragula([$('left-copy-1tomany'), $('right-copy-1tomany')], {
copy: function (el, source) {
return source === $('left-copy-1tomany');
},
accepts: function (el, target) {
return target !== $('left-copy-1tomany');
}
});
dragula([sortable]);
crossvent.add(sortable, 'click', clickHandler);
function clickHandler (e) {
var target = e.target;
if (target === sortable) {
return;
}
target.innerHTML += ' [click!]';
setTimeout(function () {
target.innerHTML = target.innerHTML.replace(/ \[click!\]/g, '');
}, 500);
}
function $ (id) {
return document.getElementById(id);
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,205 +0,0 @@
<!doctype html>
<meta charset='utf-8'>
<link rel="shortcut icon" href="favicon.ico">
<link href='dist/dragula.css' rel='stylesheet' type='text/css' />
<link href='example/example.css' rel='stylesheet' type='text/css' />
<title>dragula</title>
<h1><a href='https://github.com/bevacqua/dragula'><img src='resources/logo.svg' onerror='this.src="resources/logo.png"' alt='dragula'/></a></h1>
<h3 class='tagline'><span class='tagline-text'>Drag and drop so simple it hurts</span><script async defer src='https://dragula-slackin.herokuapp.com/slackin.js'></script></h3>
<a href='https://github.com/bevacqua/dragula'>
<img class='gh-fork' src='https://camo.githubusercontent.com/52760788cde945287fbb584134c4cbc2bc36f904/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f77686974655f6666666666662e706e67' alt='Fork me on GitHub' data-canonical-src='https://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
</a>
<div class='examples'>
<div class='parent'>
<label for='hy'>Move stuff between these two containers. Note how the stuff gets inserted near the mouse pointer? Great stuff.</label>
<div class='wrapper'>
<div id='left-defaults' class='container'>
<div>You can move these elements between these two containers</div>
<div>Moving them anywhere else isn't quite possible</div>
<div>Anything can be moved around. That includes images, <a href='https://github.com/bevacqua/dragula'>links</a>, or any other nested elements.
<div class='image-thing'><img src='resources/icon.svg' onerror='this.src="resources/icon.png"' alt='dragula'/></div><sub>(You can still click on links, as usual!)</sub>
</div>
</div>
<div id='right-defaults' class='container'>
<div>There's also the possibility of moving elements around in the same container, changing their position</div>
<div>This is the default use case. You only need to specify the containers you want to use</div>
<div>More interactive use cases lie ahead</div>
<div>Moving <code>&lt;input/&gt;</code> elements works just fine. You can still focus them, too. <input placeholder='See?' /></div>
<div>Make sure to check out the <a href='https://github.com/bevacqua/dragula#readme'>documentation on GitHub!</a></div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(left), document.getElementById(right)]);
</code>
</pre>
</div>
<div class='parent'>
<label for='hy'>There are plenty of events along the lifetime of a drag event. Check out <a href='https://github.com/bevacqua/dragula#drakeon-events'>all of them</a> in the docs!</label>
<div class='wrapper'>
<div id='left-events' class='container'>
<div>As soon as you start dragging an element, a <code>drag</code> event is fired</div>
<div>Whenever an element is cloned because <code>copy: true</code>, a <code>cloned</code> event fires</div>
<div>The <code>shadow</code> event fires whenever the placeholder showing where an element would be dropped is moved to a different container or position</div>
<div>A <code>drop</code> event is fired whenever an element is dropped anywhere other than its origin <em>(where it was initially dragged from)</em></div>
</div>
<div id='right-events' class='container'>
<div>If the element gets removed from the DOM as a result of dropping outside of any containers, a <code>remove</code> event gets fired</div>
<div>A <code>cancel</code> event is fired when an element would be dropped onto an invalid target, but retains its original placement instead</div>
<div>The <code>over</code> event fires when you drag something over a container, and <code>out</code> fires when you drag it away from the container</div>
<div>Lastly, a <code>dragend</code> event is fired whenever a drag operation ends, regardless of whether it ends in a cancellation, removal, or drop</div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(left), document.getElementById(right)])
.on('drag', function (el) {
el.className = el.className.replace('ex-moved', '');
}).on('drop', function (el) {
el.className += ' ex-moved';
}).on('over', function (el, container) {
container.className += ' ex-over';
}).on('out', function (el, container) {
container.className = container.className.replace('ex-over', '');
});
</code>
</pre>
</div>
<div class='parent'>
<label for='hy'>Need to be able to quickly delete stuff when it spills out of the chosen containers? Note how you can easily sort the items in any containers by just dragging and dropping.</label>
<div class='wrapper'>
<div id='left-rm-spill' class='container'>
<div>Anxious Cab Driver</div>
<div>Thriving Venture</div>
<div>Such <a href='http://ponyfoo.com'>a good blog</a></div>
<div>Calm Clam</div>
</div>
<div id='right-rm-spill' class='container'>
<div>Banana Boat</div>
<div>Orange Juice</div>
<div>Cuban Cigar</div>
<div>Terrible Comedian</div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(single)], {
removeOnSpill: true
});
</code>
</pre>
</div>
<div class='parent'>
<label for='hy'>By default, dropping an element outside of any known containers will keep the element in the last place it went over. You can make elements go back to origin if they're dropped outside of known containers, too.</label>
<div class='wrapper'>
<div id='left-rollbacks' class='container'>
<div>Moving items between containers works as usual</div>
<div>If you try to drop an item outside of any containers, though, it'll retain its original position</div>
<div>When that happens, a <code>cancel</code> event will be raised</div>
</div>
<div id='right-rollbacks' class='container'>
<div>Note that the dragged element will go back to the place you originally dragged it from, even if you move it over other containers</div>
<div>This is useful if you want to ensure drop events only happen when the user intends for them to happen explicitly, avoiding surprises</div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(left), document.getElementById(right)], {
revertOnSpill: true
});
</code>
</pre>
</div>
<div class='parent'>
<label for='hy'>Copying stuff is common too, so we made it easy for you.</label>
<div class='wrapper'>
<div id='left-copy' class='container'>
<div>When elements are copyable, they can't be sorted in their origin container</div>
<div>Copying prevents original elements from being dragged. A copy gets created and <em>that</em> gets dragged instead</div>
<div>Whenever that happens, a <code>cloned</code> event is raised</div>
</div>
<div id='right-copy' class='container'>
<div>Note that the clones get destroyed if they're not dropped into another container</div>
<div>You'll be dragging a copy, so when they're dropped into another container you'll see the duplication.</div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(left), document.getElementById(right)], {
copy: true
});
</code>
</pre>
</div>
<div class='parent'>
<label for='hy'>Copying stuff from only one of the containers and sorting on the other one? No problem!</label>
<div class='wrapper'>
<div id='left-copy-1tomany' class='container'>
<div>When elements are copyable, they can't be sorted in their origin container</div>
<div>Copying prevents original elements from being dragged. A copy gets created and <em>that</em> gets dragged instead</div>
<div>Whenever that happens, a <code>cloned</code> event is raised</div>
<div>Note that the clones get destroyed if they're not dropped into another container</div>
<div>You'll be dragging a copy, so when they're dropped into another container you'll see the duplication.</div>
</div>
<div id='right-copy-1tomany' class='container'>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(left), document.getElementById(right)], {
copy: function (el, source) {
return source === left
},
accepts: function (el, target) {
return target !== left
}
});
</code>
</pre>
</div>
<div class='parent'>
<label for='hy'>Drag handles float your cruise?</label>
<div class='wrapper'>
<div id='left-lovehandles' class='container'>
<div><span class='handle'>+</span>Move me, but you can use the plus sign to drag me around.</div>
<div><span class='handle'>+</span>Note that <code>handle</code> element in the <code>moves</code> handler is just the original event target.</div>
</div>
<div id='right-lovehandles' class='container'>
<div><span class='handle'>+</span>This might also be useful if you want multiple children of an element to be able to trigger a drag event.</div>
<div><span class='handle'>+</span>You can also use the <code>moves</code> option to determine whether an element can be dragged at all from a container, <em>drag handle or not</em>.</div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(left), document.getElementById(right)], {
moves: function (el, container, handle) {
return handle.className === 'handle';
}
});
</code>
</pre>
<div>There are a few similar mechanisms to determine whether an element can be dragged from a certain container <a href='https://github.com/bevacqua/dragula#optionsmoves'>(<code>moves</code>)</a>, whether an element can be dropped into a certain container at a certain position <a href='https://github.com/bevacqua/dragula#optionsaccepts'>(<code>accepts</code>)</a>, and whether an element is able to originate a drag event <a href='https://github.com/bevacqua/dragula#optionsinvalid'>(<code>invalid</code>)</a>.</div>
</div>
<div class='parent'>
<label><strong>Click or Drag!</strong> Fires a click when the mouse button is released before a <code>mousemove</code> event, otherwise a drag event is fired. No extra configuration is necessary.</label>
<div class='wrapper'>
<div id='sortable' class='container'>
<div>Clicking on these elements triggers a regular <code>click</code> event you can listen to.</div>
<div>Try dragging or clicking on this element.</div>
<div>Note how you can click normally?</div>
<div>Drags don't trigger click events.</div>
<div>Clicks don't end up in a drag, either.</div>
<div>This is useful if you have elements that can be both clicked or dragged.</div>
</div>
</div>
<pre>
<code>
dragula([document.getElementById(container)]);
</code>
</pre>
</div>
</div>
<h3 class='promo'>Who couldn't love a pun that good? &mdash; <a href='http://thenextweb.com/dd/2015/07/20/less-of-a-drag-maaaaaaaan'>The Next Web</a></h3>
<h3>Get it on GitHub! <a href='https://github.com/bevacqua/dragula'>bevacqua/dragula</a></h3>
<script src='dist/dragula.js'></script>
<script src='example/example.min.js'></script>

View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright © 2015 Nicolas Bevacqua
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,72 +0,0 @@
{
"name": "dragula",
"version": "3.5.1",
"description": "Drag and drop so simple it hurts",
"main": "dragula.js",
"scripts": {
"build": "npm run scripts && npm run styles",
"deploy": "npm run build && npm run deployment && npm run sync",
"deploy-plugins": "npm run deploy-react ; npm run deploy-angular ; cd ../dragula",
"deploy-react": "cd ../react-dragula ; npm i -S dragula@* ; git commit package.json -m 'bumped dragula version'; npm run deploy",
"deploy-angular": "cd ../angular-dragula ; npm i -S dragula@* ; git commit package.json -m 'bumped dragula version'; npm run deploy",
"deployment": "git add dist && npm version ${BUMP:-\"patch\"} --no-git-tag-version && git add package.json && git commit -am \"Autogenerated pre-deployment commit\" && bower version ${BUMP:-\"patch\"} && git reset HEAD~2 && git add . && git commit -am \"Release $(cat package.json | jq -r .version)\" && git push --tags && npm publish && git push",
"scripts": "jshint . && browserify -s dragula -do dist/dragula.js dragula.js && uglifyjs -m -c -o dist/dragula.min.js dist/dragula.js",
"start": "watchify -dvo example/example.min.js example/example.js & watchify -dvs dragula -o dist/dragula.js dragula.js & stylus -w dragula.styl --import node_modules/nib -o dist",
"styles": "stylus dragula.styl --import node_modules/nib -o dist && cleancss dist/dragula.css -o dist/dragula.min.css",
"sync": "git checkout gh-pages ; git merge master ; git push ; git checkout master",
"lint": "jshint . --reporter node_modules/jshint-stylish/index.js",
"codestyle": "xo",
"test": "npm run lint && npm run codestyle && browserify test/*.js | testron",
"test-watch": "hihat test/*.js -p tap-dev-tool"
},
"xo": {
"env": {
"browser": true,
"node": true
},
"space": true,
"ignores": [
"dist/**"
],
"rules": {
"consistent-return": 0,
"no-void": 0,
"no-inline-comments": 0,
"object-curly-spacing": 0,
"space-before-function-paren": 0,
"no-undef": 0,
"brace-style": 0
}
},
"repository": {
"type": "git",
"url": "https://github.com/bevacqua/dragula.git"
},
"author": "Nicolas Bevacqua <nicolasbevacqua@gmail.com> (http://bevacqua.io/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/bevacqua/dragula/issues"
},
"homepage": "https://github.com/bevacqua/dragula",
"dependencies": {
"contra": "1.9.1",
"crossvent": "1.5.4"
},
"devDependencies": {
"bower": "1.5.2",
"browserify": "11.0.0",
"clean-css": "3.3.6",
"electron-prebuilt": "0.30.1",
"hihat": "1.2.1",
"jshint": "2.8.0",
"jshint-stylish": "2.0.1",
"nib": "1.1.0",
"stylus": "0.52.0",
"tap-dev-tool": "1.3.0",
"tape": "4.0.1",
"testron": "1.2.0",
"uglify-js": "2.4.24",
"watchify": "3.3.0",
"xo": "0.7.1"
}
}

View file

@ -1,314 +0,0 @@
[![logo.png][3]][2]
[![Travis CI][5]][4] [![Slack Status][17]][18] [![Flattr][6]][7] [![Patreon][19]][20]
> Drag and drop so simple it hurts
Browser support includes every sane browser and **IE7+**. <sub>_(Granted you polyfill the functional `Array` methods in ES5)_</sub>
Framework support includes vanilla JavaScript, Angular, and React.
- Official [Angular bridge][8] for `dragula` [_(demo)_][10]
- Official [React bridge][9] for `dragula` [_(demo)_][11]
# Demo
[![demo.png][1]][2]
Try out the [demo][2]!
# Inspiration
Have you ever wanted a drag and drop library that just works? That doesn't just depend on bloated frameworks, that has great support? That actually understands where to place the elements when they are dropped? That doesn't need you to do a zillion things to get it to work? Well, so did I!
# Features
- Super easy to set up
- No bloated dependencies
- **Figures out sort order** on its own
- A shadow where the item would be dropped offers **visual feedback**
- Touch events!
- Seamlessly handles clicks *without any configuration*
# Install
You can get it on npm.
```shell
npm install dragula --save
```
Or bower, too.
```shell
bower install dragula --save
```
If you're not using either package manager, you can use `dragula` by downloading the [files in the `dist` folder][15]. We **strongly suggest** using `npm`, though.
##### Including the CSS!
There's [a few CSS styles][16] you need to incorporate in order for `dragula` to work as expected.
You can add them by including [`dist/dragula.css`][12] or [`dist/dragula.min.css`][13] in your document. If you're using Stylus, you can include the styles using the directive below.
```styl
@import 'node_modules/dragula/dragula'
```
# Usage
Dragula provides the easiest possible API to make drag and drop a breeze in your applications.
## `dragula(containers?, options?)`
By default, `dragula` will allow the user to drag an element in any of the `containers` and drop it in any other container in the list. If the element is dropped anywhere that's not one of the `containers`, the event will be gracefully cancelled according to the `revertOnSpill` and `removeOnSpill` options.
Note that dragging is only triggered on left clicks, and only if no meta keys are pressed.
The example below allows the user to drag elements from `left` into `right`, and from `right` into `left`.
```js
dragula([document.querySelector('#left'), document.querySelector('#right')]);
```
You can also provide an `options` object. Here's an **overview of the default values**.
```js
dragula(containers, {
isContainer: function (el) {
return false; // only elements in drake.containers will be taken into account
},
moves: function (el, source, handle, sibling) {
return true; // elements are always draggable by default
},
accepts: function (el, target, source, sibling) {
return true; // elements can be dropped in any of the `containers` by default
},
invalid: function (el, target) {
return false; // don't prevent any drags from initiating by default
},
direction: 'vertical', // Y axis is considered when determining where an element would be dropped
copy: false, // elements are moved by default, not copied
copySortSource: false, // elements are moved by default, not copied
revertOnSpill: false, // spilling will put the element back where it was dragged from, if this is true
removeOnSpill: false, // spilling will `.remove` the element, if this is true
mirrorContainer: document.body, // set the element that gets mirror elements appended
ignoreInputTextSelection: true // allows users to select input text, see details below
});
```
You can omit the `containers` argument and add containers dynamically later on.
```js
var drake = dragula({
copy: true
});
drake.containers.push(container);
```
You can also set the `containers` from the `options` object.
```js
var drake = dragula({ containers: containers });
```
And you could also not set any arguments, which defaults to a drake without containers and with the default options.
```js
var drake = dragula();
```
The options are detailed below.
#### `options.containers`
Setting this option is effectively the same as passing the containers in the first argument to `dragula(containers, options)`.
#### `options.isContainer`
Besides the containers that you pass to `dragula`, or the containers you dynamically `push` or `unshift` from [drake.containers](#drakecontainers), you can also use this method to specify any sort of logic that defines what is a container for this particular `drake` instance.
The example below dynamically treats all DOM elements with a CSS class of `dragula-container` as dragula containers for this `drake`.
```js
var drake = dragula({
isContainer: function (el) {
return el.classList.contains('dragula-container');
}
});
```
#### `options.moves`
You can define a `moves` method which will be invoked with `(el, source, handle, sibling)` whenever an element is clicked. If this method returns `false`, a drag event won't begin, and the event won't be prevented either. The `handle` element will be the original click target, which comes in handy to test if that element is an expected _"drag handle"_.
#### `options.accepts`
You can set `accepts` to a method with the following signature: `(el, target, source, sibling)`. It'll be called to make sure that an element `el`, that came from container `source`, can be dropped on container `target` before a `sibling` element. The `sibling` can be `null`, which would mean that the element would be placed as the last element in the container. Note that if `options.copy` is set to `true`, `el` will be set to the copy, instead of the originally dragged element.
Also note that **the position where a drag starts is always going to be a valid place where to drop the element**, even if `accepts` returned `false` for all cases.
#### `options.copy`
If `copy` is set to `true` _(or a method that returns `true`)_, items will be copied rather than moved. This implies the following differences:
Event | Move | Copy
----------|------------------------------------------|---------------------------------------------
`drag` | Element will be concealed from `source` | Nothing happens
`drop` | Element will be moved into `target` | Element will be cloned into `target`
`remove` | Element will be removed from DOM | Nothing happens
`cancel` | Element will stay in `source` | Nothing happens
If a method is passed, it'll be called whenever an element starts being dragged in order to decide whether it should follow `copy` behavior or not. Consider the following example.
```js
copy: function (el, source) {
return el.className === 'you-may-copy-us';
}
```
#### `options.copySortSource`
If `copy` is set to `true` _(or a method that returns `true`)_ and `copySortSource` is `true` as well, users will be able to sort elements in `copy`-source containers.
```js
copy: true,
copySortSource: true
```
#### `options.revertOnSpill`
By default, spilling an element outside of any containers will move the element back to the _drop position previewed by the feedback shadow_. Setting `revertOnSpill` to `true` will ensure elements dropped outside of any approved containers are moved back to the source element where the drag event began, rather than stay at the _drop position previewed by the feedback shadow_.
#### `options.removeOnSpill`
By default, spilling an element outside of any containers will move the element back to the _drop position previewed by the feedback shadow_. Setting `removeOnSpill` to `true` will ensure elements dropped outside of any approved containers are removed from the DOM. Note that `remove` events won't fire if `copy` is set to `true`.
#### `options.direction`
When an element is dropped onto a container, it'll be placed near the point where the mouse was released. If the `direction` is `'vertical'`, the default value, the Y axis will be considered. Otherwise, if the `direction` is `'horizontal'`, the X axis will be considered.
#### `options.invalid`
You can provide an `invalid` method with a `(el, target)` signature. This method should return `true` for elements that shouldn't trigger a drag. Here's the default implementation, which doesn't prevent any drags.
```js
function invalidTarget (el, target) {
return false;
}
```
Note that `invalid` will be invoked on the DOM element that was clicked and every parent up to immediate children of a `drake` container.
As an example, you could set `invalid` to return `false` whenever the clicked element _(or any of its parents)_ is an anchor tag.
```js
invalid: function (el) {
return el.tagName === 'A';
}
```
#### `options.mirrorContainer`
The DOM element where the mirror element displayed while dragging will be appended to. Defaults to `document.body`.
#### `options.ignoreInputTextSelection`
When this option is enabled, if the user clicks on an input element the drag won't start until their mouse pointer exits the input. This translates into the user being able to select text in inputs contained inside draggable elements, and still drag the element by moving their mouse outside of the input -- so you get the best of both worlds.
This option is enabled by default. Turn it off by setting it to `false`. If its disabled your users won't be able to select text in inputs within `dragula` containers with their mouse.
## API
The `dragula` method returns a tiny object with a concise API. We'll refer to the API returned by `dragula` as `drake`.
#### `drake.containers`
This property contains the collection of containers that was passed to `dragula` when building this `drake` instance. You can `push` more containers and `splice` old containers at will.
#### `drake.dragging`
This property will be `true` whenever an element is being dragged.
#### `drake.start(item)`
Enter drag mode **without a shadow**. This method is most useful when providing complementary keyboard shortcuts to an existing drag and drop solution. Even though a shadow won't be created at first, the user will get one as soon as they click on `item` and start dragging it around. Note that if they click and drag something else, `.end` will be called before picking up the new item.
#### `drake.end()`
Gracefully end the drag event as if using **the last position marked by the preview shadow** as the drop target. The proper `cancel` or `drop` event will be fired, depending on whether the item was dropped back where it was originally lifted from _(which is essentially a no-op that's treated as a `cancel` event)_.
#### `drake.cancel(revert)`
If an element managed by `drake` is currently being dragged, this method will gracefully cancel the drag action. You can also pass in `revert` at the method invocation level, effectively producing the same result as if `revertOnSpill` was `true`.
Note that **a _"cancellation"_ will result in a `cancel` event** only in the following scenarios.
- `revertOnSpill` is `true`
- Drop target _(as previewed by the feedback shadow)_ is the source container **and** the item is dropped in the same position where it was originally dragged from
#### `drake.remove()`
If an element managed by `drake` is currently being dragged, this method will gracefully remove it from the DOM.
#### `drake.on` _(Events)_
The `drake` is an event emitter. The following events can be tracked using `drake.on(type, listener)`:
Event Name | Listener Arguments | Event Description
-----------|----------------------------------|-------------------------------------------------------------------------------------
`drag` | `el, source` | `el` was lifted from `source`
`dragend` | `el` | Dragging event for `el` ended with either `cancel`, `remove`, or `drop`
`drop` | `el, target, source, sibling` | `el` was dropped into `target` before a `sibling` element, and originally came from `source`
`cancel` | `el, container, source` | `el` was being dragged but it got nowhere and went back into `container`, its last stable parent; `el` originally came from `source`
`remove` | `el, container, source` | `el` was being dragged but it got nowhere and it was removed from the DOM. Its last stable parent was `container`, and originally came from `source`
`shadow` | `el, container, source` | `el`, _the visual aid shadow_, was moved into `container`. May trigger many times as the position of `el` changes, even within the same `container`; `el` originally came from `source`
`over` | `el, container, source` | `el` is over `container`, and originally came from `source`
`out` | `el, container, source` | `el` was dragged out of `container` or dropped, and originally came from `source`
`cloned` | `clone, original, type` | DOM element `original` was cloned as `clone`, of `type` _(`'mirror'` or `'copy'`)_. Fired for mirror images and when `copy: true`
#### `drake.destroy()`
Removes all drag and drop events used by `dragula` to manage drag and drop between the `containers`. If `.destroy` is called while an element is being dragged, the drag will be effectively cancelled.
## CSS
Dragula uses only four CSS classes. Their purpose is quickly explained below, but you can check [`dist/dragula.css`][12] to see the corresponding CSS rules.
- `gu-unselectable` is added to the `mirrorContainer` element when dragging. You can use it to style the `mirrorContainer` while something is being dragged.
- `gu-transit` is added to the source element when its mirror image is dragged. It just adds opacity to it.
- `gu-mirror` is added to the mirror image. It handles fixed positioning and `z-index` _(and removes any prior margins on the element)_. Note that the mirror image is appended to the `mirrorContainer`, not to its initial container. Keep that in mind when styling your elements with nested rules, like `.list .item { padding: 10px; }`.
- `gu-hide` is a helper class to apply `display: none` to an element.
# Contributing
See [contributing.markdown][14] for details.
# Support
There's now a dedicated support channel in Slack. Visit the `dragula` [demo page][2] to get an invite. Support requests won't be handled through the repository anymore.
# License
MIT
[1]: https://github.com/bevacqua/dragula/blob/master/resources/demo.png
[2]: http://bevacqua.github.io/dragula/
[3]: https://github.com/bevacqua/dragula/blob/master/resources/logo.png
[4]: https://travis-ci.org/bevacqua/dragula
[5]: https://travis-ci.org/bevacqua/dragula.svg
[6]: http://api.flattr.com/button/flattr-badge-large.png
[7]: http://flattr.com/thing/4127996/bevacquadragula-on-GitHub
[8]: https://github.com/bevacqua/angular-dragula
[9]: https://github.com/bevacqua/react-dragula
[10]: http://bevacqua.github.io/angular-dragula/
[11]: http://bevacqua.github.io/react-dragula/
[12]: https://github.com/bevacqua/dragula/blob/master/dist/dragula.css
[13]: https://github.com/bevacqua/dragula/blob/master/dist/dragula.min.css
[14]: https://github.com/bevacqua/dragula/blob/master/contributing.markdown
[15]: https://github.com/bevacqua/dragula/blob/master/dist
[16]: #css
[17]: https://dragula-slackin.herokuapp.com/badge.svg
[18]: https://bevacqua.github.io/dragula/
[19]: https://rawgit.com/bevacqua/dragula/master/resources/patreon.svg
[20]: https://patreon.com/bevacqua

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1 +0,0 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="200" height="220" viewBox="0 0 200 220"><path fill="#961914" stroke="#52100F" stroke-width="4" stroke-linejoin="round" stroke-miterlimit="10" d="M45.09 158.663c-2.652 40.673-7.848 34.5-7.797 43.87.05 9.37 8.36 9.02 8.36 9.02s8.315.26 8.265-9.11c-.064-11.833-5.916-5.496-8.827-43.78z"/><path fill="#221C3B" d="M161.298 129.555c-.584-.182-32.122-10.11-68.73-26.774l28.113-10.01c.815-.29 1.35-1.07 1.33-1.934-.02-.864-.596-1.617-1.423-1.865-.852-.255-85.436-26.222-107.022-77.88-.366-.877-1.3-1.374-2.23-1.19-.934.183-1.608.997-1.615 1.948-.15 21.842 9.604 43.05 28.337 61.876C25.045 65.42 13.063 56.408 3.41 46.828c-.66-.655-1.68-.77-2.466-.278-.787.49-1.134 1.456-.84 2.335 15.855 47.184 75.654 67.73 78.192 68.582.083.028.168.05.254.067l81.773 15.895c.128.024.256.036.383.036.9 0 1.71-.61 1.938-1.512.258-1.03-.332-2.083-1.346-2.4z"/><path fill="#952A58" d="M20.208 33.882C41.114 63.294 88.69 88.366 88.69 88.366l-5.67 11.54S28.464 66.843 20.207 33.883z"/><path fill="#221C3B" d="M176.817 96.7C169.673 82.777 156 73.51 136.173 69.153 81.2 57.076 66.583 46.878 38.43.955c-.473-.77-1.4-1.128-2.268-.874-.866.254-1.456 1.057-1.438 1.96.192 9.995 4.417 22.933 9.31 37.913 11.585 35.473 26.002 79.622-.626 117.455-.03.04-.057.083-.083.127-.157.258-.25.543-.28.832v.008c-.008.095-.01.19-.007.283v.008c.018.38.145.757.378 1.08.71.985 1.82 2.365 3.222 4.11 6.98 8.69 23.325 29.04 20.575 41.56-.212.967.316 1.944 1.242 2.297.233.09.474.13.71.13.707 0 1.383-.374 1.746-1.02 8.72-15.55 27.214-18.688 46.793-22.01 25.645-4.35 54.71-9.282 62.648-45.41 3.605-16.406 2.383-31.173-3.533-42.703zM80.36 118.05c9.852 2.812 31.804 9.922 32.124 24.504-15.71 4.128-28.223 7.467-41.418 19.84-3.02-1.093-11.167-3.906-18.468-5.194 14.872-8.625 23.905-24.973 27.76-39.15z"/><path fill="#952A58" d="M47.667 26.33c21.25 33.75 42.806 41.29 80.186 49.5 36.266 7.97 48.95 32.165 42.857 59.904-11.654 53.044-78.047 28.823-100.376 56.015-9.748-24.576-25.298-33.18-25.298-33.18s18.36 6.14 24.6 13.206c2.698 3.055 3.915 6.678 3.948 7.22 1.417 1-.43-5.054.34-5.968 21.34-25.367 55.016-14.355 60.676-40.12 7.2-32.77-34.33-29.798-59.6-42.625-5.08 31.65-10.715 48.995-29.964 68.287 21.16-21.657 22.518-60.586 14.408-99.74 21.733 28.916 53.058 34.035 69.974 37.75-37.017-11.142-66.5-29.685-81.75-70.25z"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.4 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="-2,0,65,65" color="#ff5900"><path d="M8 31.3C8 20.1 17.1 9.6 29.1 8.1c8.6-1 15.1 2.3 19.6 6.6 4.2 4 6.7 9.1 7.3 15.1 0.5 6-0.8 11.2-4.3 16.1-3.5 4.8-10.4 10.1-19 10.1l-11.7 0 0-23.1c0.1-5.1 1.8-9.5 8-11.6 5.4-1.6 11.7 1.4 13.6 7.1 2 6.1-0.9 10.2-4.3 12.7-3.4 2.5-8.7 2.5-12.2 0.1l0 7.9c2.3 1.1 5.2 1.4 7.3 1.3 7.6-1.1 13.5-5.4 16-11.9 2.6-6.9 0.8-14.9-4.6-19.9-6.5-5.3-13.6-6.6-21.2-2.9-5.3 2.7-9 8.2-9.9 14.2l0 26.1-5.6 0z" fill="#ff5900"/></svg>

Before

Width:  |  Height:  |  Size: 520 B

View file

@ -1,108 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('cancel does not throw when not dragging', function (t) {
t.test('a single time', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.cancel();
}, 'dragula ignores a single call to drake.cancel');
st.end();
});
t.test('multiple times', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.cancel();
drake.cancel();
drake.cancel();
drake.cancel();
}, 'dragula ignores multiple calls to drake.cancel');
st.end();
});
t.end();
});
test('when dragging and cancel gets called, nothing happens', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.start(item);
drake.cancel();
t.equal(div.children.length, 1, 'nothing happens');
t.equal(drake.dragging, false, 'drake has stopped dragging');
t.end();
});
test('when dragging and cancel gets called, cancel event is emitted', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.start(item);
drake.on('cancel', cancel);
drake.on('dragend', dragend);
drake.cancel();
t.plan(3);
t.end();
function dragend () {
t.pass('dragend got called');
}
function cancel (target, container) {
t.equal(target, item, 'cancel was invoked with item');
t.equal(container, div, 'cancel was invoked with container');
}
});
test('when dragging a copy and cancel gets called, default does not revert', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div, div2]);
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.start(item);
div2.appendChild(item);
drake.on('drop', drop);
drake.on('dragend', dragend);
drake.cancel();
t.plan(4);
t.end();
function dragend () {
t.pass('dragend got called');
}
function drop (target, parent, source) {
t.equal(target, item, 'drop was invoked with item');
t.equal(parent, div2, 'drop was invoked with final container');
t.equal(source, div, 'drop was invoked with source container');
}
});
test('when dragging a copy and cancel gets called, revert is executed', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div, div2]);
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.start(item);
div2.appendChild(item);
drake.on('cancel', cancel);
drake.on('dragend', dragend);
drake.cancel(true);
t.plan(3);
t.end();
function dragend () {
t.pass('dragend got called');
}
function cancel (target, container) {
t.equal(target, item, 'cancel was invoked with item');
t.equal(container, div, 'cancel was invoked with container');
}
});

View file

@ -1,65 +0,0 @@
'use strict';
var test = require('tape');
var classes = require('../classes');
test('classes exports the expected api', function (t) {
t.equal(typeof classes.add, 'function', 'classes.add is a method');
t.equal(typeof classes.rm, 'function', 'classes.rm is a method');
t.end();
});
test('classes can add a class', function (t) {
var el = document.createElement('div');
classes.add(el, 'gu-foo');
t.equal(el.className, 'gu-foo', 'setting a class works');
t.end();
});
test('classes can add a class to an element that already has classes', function (t) {
var el = document.createElement('div');
el.className = 'bar';
classes.add(el, 'gu-foo');
t.equal(el.className, 'bar gu-foo', 'appending a class works');
t.end();
});
test('classes.add is a no-op if class already is in element', function (t) {
var el = document.createElement('div');
el.className = 'gu-foo';
classes.add(el, 'gu-foo');
t.equal(el.className, 'gu-foo', 'no-op as expected');
t.end();
});
test('classes can remove a class', function (t) {
var el = document.createElement('div');
el.className = 'gu-foo';
classes.rm(el, 'gu-foo');
t.equal(el.className, '', 'removing a class works');
t.end();
});
test('classes can remove a class from a list on the right', function (t) {
var el = document.createElement('div');
el.className = 'bar gu-foo';
classes.rm(el, 'gu-foo');
t.equal(el.className, 'bar', 'removing a class from the list works to the right');
t.end();
});
test('classes can remove a class from a list on the left', function (t) {
var el = document.createElement('div');
el.className = 'gu-foo bar';
classes.rm(el, 'gu-foo');
t.equal(el.className, 'bar', 'removing a class from the list works to the left');
t.end();
});
test('classes can remove a class from a list on the middle', function (t) {
var el = document.createElement('div');
el.className = 'foo gu-foo bar';
classes.rm(el, 'gu-foo');
t.equal(el.className, 'foo bar', 'removing a class from the list works to the middle');
t.end();
});

View file

@ -1,38 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('drake defaults to no containers', function (t) {
var drake = dragula();
t.ok(Array.isArray(drake.containers), 'drake.containers is an array');
t.equal(drake.containers.length, 0, 'drake.containers is empty');
t.end();
});
test('drake reads containers from array argument', function (t) {
var el = document.createElement('div');
var containers = [el];
var drake = dragula(containers);
t.equal(drake.containers, containers, 'drake.containers matches input');
t.equal(drake.containers.length, 1, 'drake.containers has one item');
t.end();
});
test('drake reads containers from array in options', function (t) {
var el = document.createElement('div');
var containers = [el];
var drake = dragula({ containers: containers });
t.equal(drake.containers, containers, 'drake.containers matches input');
t.equal(drake.containers.length, 1, 'drake.containers has one item');
t.end();
});
test('containers in options take precedent', function (t) {
var el = document.createElement('div');
var containers = [el];
var drake = dragula([], { containers: containers });
t.equal(drake.containers, containers, 'drake.containers matches input');
t.equal(drake.containers.length, 1, 'drake.containers has one item');
t.end();
});

View file

@ -1,19 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('drake has sensible default options', function (t) {
var options = {};
dragula(options);
t.equal(typeof options.moves, 'function', 'options.moves defaults to a method');
t.equal(typeof options.accepts, 'function', 'options.accepts defaults to a method');
t.equal(typeof options.invalid, 'function', 'options.invalid defaults to a method');
t.equal(typeof options.isContainer, 'function', 'options.isContainer defaults to a method');
t.equal(options.copy, false, 'options.copy defaults to false');
t.equal(options.revertOnSpill, false, 'options.revertOnSpill defaults to false');
t.equal(options.removeOnSpill, false, 'options.removeOnSpill defaults to false');
t.equal(options.direction, 'vertical', 'options.direction defaults to \'vertical\'');
t.equal(options.mirrorContainer, document.body, 'options.mirrorContainer defaults to an document.body');
t.end();
});

View file

@ -1,103 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('destroy does not throw when not dragging, destroyed, or whatever', function (t) {
t.test('a single time', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.destroy();
}, 'dragula bites into a single call to drake.destroy');
st.end();
});
t.test('multiple times', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.destroy();
drake.destroy();
drake.destroy();
drake.destroy();
}, 'dragula bites into multiple calls to drake.destroy');
st.end();
});
t.end();
});
test('when dragging and destroy gets called, nothing happens', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.start(item);
drake.destroy();
t.equal(div.children.length, 1, 'nothing happens');
t.equal(drake.dragging, false, 'drake has stopped dragging');
t.end();
});
test('when dragging and destroy gets called, dragend event is emitted gracefully', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.start(item);
drake.on('dragend', dragend);
drake.destroy();
t.plan(1);
t.end();
function dragend () {
t.pass('dragend got called');
}
});
test('when dragging a copy and destroy gets called, default does not revert', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div, div2]);
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.start(item);
div2.appendChild(item);
drake.on('drop', drop);
drake.on('dragend', dragend);
drake.destroy();
t.plan(4);
t.end();
function dragend () {
t.pass('dragend got called');
}
function drop (target, parent, source) {
t.equal(target, item, 'drop was invoked with item');
t.equal(parent, div2, 'drop was invoked with final container');
t.equal(source, div, 'drop was invoked with source container');
}
});
test('when dragging a copy and destroy gets called, revert is executed', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div, div2], { revertOnSpill: true });
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.start(item);
div2.appendChild(item);
drake.on('cancel', cancel);
drake.on('dragend', dragend);
drake.destroy();
t.plan(3);
t.end();
function dragend () {
t.pass('dragend got called');
}
function cancel (target, container) {
t.equal(target, item, 'cancel was invoked with item');
t.equal(container, div, 'cancel was invoked with container');
}
});

View file

@ -1,256 +0,0 @@
'use strict';
var test = require('tape');
var events = require('./lib/events');
var dragula = require('..');
test('drag event gets emitted when clicking an item', function (t) {
testCase('works for left clicks', { which: 0 });
testCase('works for wheel clicks', { which: 1 });
testCase('works when clicking buttons by default', { which: 0 }, { tag: 'button', passes: true });
testCase('works when clicking anchors by default', { which: 0 }, { tag: 'a', passes: true });
testCase('fails for right clicks', { which: 2 }, { passes: false });
testCase('fails for meta-clicks', { which: 0, metaKey: true }, { passes: false });
testCase('fails for ctrl-clicks', { which: 0, ctrlKey: true }, { passes: false });
testCase('fails when clicking containers', { which: 0 }, { containerClick: true, passes: false });
testCase('fails whenever invalid returns true', { which: 0 }, { passes: false, dragulaOpts: { invalid: always } });
testCase('fails whenever moves returns false', { which: 0 }, { passes: false, dragulaOpts: { moves: never } });
t.end();
function testCase (desc, eventOptions, options) {
t.test(desc, function subtest (st) {
var o = options || {};
var div = document.createElement('div');
var item = document.createElement(o.tag || 'div');
var passes = o.passes !== false;
var drake = dragula([div], o.dragulaOpts);
div.appendChild(item);
document.body.appendChild(div);
drake.on('drag', drag);
events.raise(o.containerClick ? div : item, 'mousedown', eventOptions);
events.raise(o.containerClick ? div : item, 'mousemove');
st.plan(passes ? 4 : 1);
st.equal(drake.dragging, passes, desc + ': final state is drake is ' + (passes ? '' : 'not ') + 'dragging');
st.end();
function drag (target, container) {
st[passes ? 'pass' : 'fail'](desc + ': drag event was emitted synchronously');
st.equal(target, item, desc + ': first argument is selected item');
st.equal(container, div, desc + ': second argument is container');
}
});
}
});
test('when already dragging, mousedown/mousemove ends (cancels) previous drag', function (t) {
var div = document.createElement('div');
var item1 = document.createElement('div');
var item2 = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item1);
div.appendChild(item2);
document.body.appendChild(div);
drake.start(item1);
drake.on('dragend', end);
drake.on('cancel', cancel);
drake.on('drag', drag);
events.raise(item2, 'mousedown', { which: 0 });
events.raise(item2, 'mousemove', { which: 0 });
t.plan(7);
t.equal(drake.dragging, true, 'final state is drake is dragging');
t.end();
function end (item) {
t.equal(item, item1, 'dragend invoked with correct item');
}
function cancel (item, source) {
t.equal(item, item1, 'cancel invoked with correct item');
t.equal(source, div, 'cancel invoked with correct source');
}
function drag (item, container) {
t.pass('drag event was emitted synchronously');
t.equal(item, item2, 'first argument is selected item');
t.equal(container, div, 'second argument is container');
}
});
test('when already dragged, ends (drops) previous drag', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item1 = document.createElement('div');
var item2 = document.createElement('div');
var drake = dragula([div, div2]);
div.appendChild(item1);
div.appendChild(item2);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.start(item1);
div2.appendChild(item1);
drake.on('dragend', end);
drake.on('drop', drop);
drake.on('drag', drag);
events.raise(item2, 'mousedown', { which: 0 });
events.raise(item2, 'mousemove', { which: 0 });
t.plan(8);
t.equal(drake.dragging, true, 'final state is drake is dragging');
t.end();
function end (item) {
t.equal(item, item1, 'dragend invoked with correct item');
}
function drop (item, target, source) {
t.equal(item, item1, 'drop invoked with correct item');
t.equal(source, div, 'drop invoked with correct source');
t.equal(target, div2, 'drop invoked with correct target');
}
function drag (item, container) {
t.pass('drag event was emitted synchronously');
t.equal(item, item2, 'first argument is selected item');
t.equal(container, div, 'second argument is container');
}
});
test('when copying, emits cloned with the copy', function (t) {
var div = document.createElement('div');
var item1 = document.createElement('div');
var item2 = document.createElement('span');
var drake = dragula([div], { copy: true });
item2.innerHTML = '<em>the force is <strong>with this one</strong></em>';
div.appendChild(item1);
div.appendChild(item2);
document.body.appendChild(div);
drake.start(item1);
drake.on('cloned', cloned);
drake.on('drag', drag);
events.raise(item2, 'mousedown', { which: 0 });
events.raise(item2, 'mousemove', { which: 0 });
t.plan(12);
t.equal(drake.dragging, true, 'final state is drake is dragging');
t.end();
function cloned (copy, item) {
t.notEqual(copy, item2, 'first argument is not exactly the target');
t.equal(copy.tagName, item2.tagName, 'first argument has same tag as target');
t.equal(copy.innerHTML, item2.innerHTML, 'first argument has same inner html as target');
t.equal(item, item2, 'second argument is clicked item');
}
function drag (item, container) {
t.pass('drag event was emitted synchronously');
t.equal(item, item2, 'first argument is selected item');
t.equal(container, div, 'second argument is container');
}
});
test('when dragging, element gets gu-transit class', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.equal(item.className, 'gu-transit', 'item has gu-transit class');
t.end();
});
test('when dragging, body gets gu-unselectable class', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.equal(document.body.className, 'gu-unselectable', 'body has gu-unselectable class');
t.end();
});
test('when dragging, element gets a mirror image for show', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
item.innerHTML = '<em>the force is <strong>with this one</strong></em>';
div.appendChild(item);
document.body.appendChild(div);
drake.on('cloned', cloned);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.plan(4);
t.end();
function cloned (mirror, target) {
t.equal(item.className, 'gu-transit', 'item does not have gu-mirror class');
t.equal(mirror.className, 'gu-mirror', 'mirror only has gu-mirror class');
t.equal(mirror.innerHTML, item.innerHTML, 'mirror is passed to \'cloned\' event');
t.equal(target, item, 'cloned lets you know that the mirror is a clone of `item`');
}
});
test('when dragging, mirror element gets appended to configured mirrorContainer', function (t) {
var mirrorContainer = document.createElement('div');
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], {
'mirrorContainer': mirrorContainer
});
item.innerHTML = '<em>the force is <strong>with this one</strong></em>';
div.appendChild(item);
document.body.appendChild(div);
drake.on('cloned', cloned);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.plan(1);
t.end();
function cloned (mirror) {
t.equal(mirror.parentNode, mirrorContainer, 'mirrors parent is the configured mirrorContainer');
}
});
test('when dragging stops, element gets gu-transit class removed', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.equal(item.className, 'gu-transit', 'item has gu-transit class');
drake.end();
t.equal(item.className, '', 'item has gu-transit class removed');
t.end();
});
test('when dragging stops, body becomes selectable again', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.equal(document.body.className, 'gu-unselectable', 'body has gu-unselectable class');
drake.end();
t.equal(document.body.className, '', 'body got gu-unselectable class removed');
t.end();
});
test('when drag begins, check for copy option', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
item.className = 'copyable';
div.className = 'contains';
var drake = dragula([div], {
copy: checkCondition
});
item.innerHTML = '<em>the force is <strong>with this one</strong></em>';
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
events.raise(item, 'mousemove', { which: 0 }); // ensure the copy method condition is only asserted once
t.plan(2);
t.end();
function checkCondition (el, source) {
t.equal(el.className, 'copyable', 'dragged element classname is copyable');
t.equal(source.className, 'contains', 'source container classname is contains');
return true;
}
drake.end();
});
function always () { return true; }
function never () { return false; }

View file

@ -1,27 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('drake can be instantiated without throwing', function (t) {
t.doesNotThrow(drakeFactory, 'calling dragula() without arguments does not throw');
t.end();
function drakeFactory () {
return dragula();
}
});
test('drake has expected api properties', function (t) {
var drake = dragula();
t.ok(drake, 'drake is not null');
t.equal(typeof drake, 'object', 'drake is an object');
t.ok(Array.isArray(drake.containers), 'drake.containers is an array');
t.equal(typeof drake.start, 'function', 'drake.start is a method');
t.equal(typeof drake.end, 'function', 'drake.end is a method');
t.equal(typeof drake.cancel, 'function', 'drake.cancel is a method');
t.equal(typeof drake.remove, 'function', 'drake.remove is a method');
t.equal(typeof drake.destroy, 'function', 'drake.destroy is a method');
t.equal(typeof drake.dragging, 'boolean', 'drake.dragging is a boolean');
t.equal(drake.dragging, false, 'drake.dragging is initialized as false');
t.end();
});

View file

@ -1,77 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('end does not throw when not dragging', function (t) {
t.test('a single time', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.end();
}, 'dragula ignores a single call to drake.end');
st.end();
});
t.test('multiple times', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.end();
drake.end();
drake.end();
drake.end();
}, 'dragula ignores multiple calls to drake.end');
st.end();
});
t.end();
});
test('when already dragging, .end() ends (cancels) previous drag', function (t) {
var div = document.createElement('div');
var item1 = document.createElement('div');
var item2 = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item1);
div.appendChild(item2);
document.body.appendChild(div);
drake.start(item1);
drake.on('dragend', end);
drake.on('cancel', cancel);
drake.end();
t.plan(4);
t.equal(drake.dragging, false, 'final state is: drake is not dragging');
t.end();
function end (item) {
t.equal(item, item1, 'dragend invoked with correct item');
}
function cancel (item, source) {
t.equal(item, item1, 'cancel invoked with correct item');
t.equal(source, div, 'cancel invoked with correct source');
}
});
test('when already dragged, ends (drops) previous drag', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item1 = document.createElement('div');
var item2 = document.createElement('div');
var drake = dragula([div, div2]);
div.appendChild(item1);
div.appendChild(item2);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.start(item1);
div2.appendChild(item1);
drake.on('dragend', end);
drake.on('drop', drop);
drake.end();
t.plan(5);
t.equal(drake.dragging, false, 'final state is: drake is not dragging');
t.end();
function end (item) {
t.equal(item, item1, 'dragend invoked with correct item');
}
function drop (item, target, source) {
t.equal(item, item1, 'drop invoked with correct item');
t.equal(source, div, 'drop invoked with correct source');
t.equal(target, div2, 'drop invoked with correct target');
}
});

View file

@ -1,292 +0,0 @@
'use strict';
var test = require('tape');
var events = require('./lib/events');
var dragula = require('..');
test('.start() emits "cloned" for copies', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], { copy: true });
div.appendChild(item);
document.body.appendChild(div);
drake.on('cloned', cloned);
drake.start(item);
t.plan(3);
t.end();
function cloned (copy, original, type) {
if (type === 'copy') {
t.notEqual(copy, item, 'copy is not a reference to item');
t.deepEqual(copy, item, 'copy of original is provided');
t.equal(original, item, 'original item is provided');
}
}
});
test('.start() emits "drag" for items', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.on('drag', drag);
drake.start(item);
t.plan(2);
t.end();
function drag (original, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(container, div, 'container matches expected div');
}
});
test('.end() emits "cancel" when not moved', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('cancel', cancel);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
drake.end();
t.plan(4);
t.end();
function dragend (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function out (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function cancel (original, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(container, div, 'container matches expected div');
}
});
test('.end() emits "drop" when moved', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div, div2]);
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('drop', drop);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
div2.appendChild(item);
drake.end();
t.plan(5);
t.end();
function dragend (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function out (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function drop (original, target, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(target, div2, 'target matches expected div');
t.equal(container, div, 'container matches expected div');
}
});
test('.remove() emits "remove" for items', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('remove', remove);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
drake.remove();
t.plan(4);
t.end();
function dragend (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function out (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function remove (original, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(container, div, 'container matches expected div');
}
});
test('.remove() emits "cancel" for copies', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], { copy: true });
div.appendChild(item);
document.body.appendChild(div);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('cancel', cancel);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
drake.remove();
t.plan(6);
t.end();
function dragend () {
t.pass('dragend got invoked');
}
function out (copy) {
t.notEqual(copy, item, 'copy is not a reference to item');
t.deepEqual(copy, item, 'item is a copy of item');
}
function cancel (copy, container) {
t.notEqual(copy, item, 'copy is not a reference to item');
t.deepEqual(copy, item, 'item is a copy of item');
t.equal(container, null, 'container matches expectation');
}
});
test('.cancel() emits "cancel" when not moved', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('cancel', cancel);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
drake.cancel();
t.plan(4);
t.end();
function dragend (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function out (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function cancel (original, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(container, div, 'container matches expected div');
}
});
test('.cancel() emits "drop" when not reverted', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('drop', drop);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
div2.appendChild(item);
drake.cancel();
t.plan(5);
t.end();
function dragend (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function out (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function drop (original, parent, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(parent, div2, 'parent matches expected div');
t.equal(container, div, 'container matches expected div');
}
});
test('.cancel() emits "cancel" when reverts', function (t) {
var div = document.createElement('div');
var div2 = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], { revertOnSpill: true });
div.appendChild(item);
document.body.appendChild(div);
document.body.appendChild(div2);
drake.on('dragend', dragend);
drake.on('out', out);
drake.on('cancel', cancel);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
div2.appendChild(item);
drake.cancel();
t.plan(4);
t.end();
function dragend (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function out (original) {
t.equal(original, item, 'item is a reference to moving target');
}
function cancel (original, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(container, div, 'container matches expected div');
}
});
test('mousedown emits "cloned" for mirrors', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.on('cloned', cloned);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.plan(3);
t.end();
function cloned (copy, original, type) {
if (type === 'mirror') {
t.notEqual(copy, item, 'mirror is not a reference to item');
t.deepEqual(copy, item, 'mirror of original is provided');
t.equal(original, item, 'original item is provided');
}
}
});
test('mousedown emits "cloned" for copies', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], { copy: true });
div.appendChild(item);
document.body.appendChild(div);
drake.on('cloned', cloned);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.plan(3);
t.end();
function cloned (copy, original, type) {
if (type === 'copy') {
t.notEqual(copy, item, 'copy is not a reference to item');
t.deepEqual(copy, item, 'copy of original is provided');
t.equal(original, item, 'original item is provided');
}
}
});
test('mousedown emits "drag" for items', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.on('drag', drag);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
t.plan(2);
t.end();
function drag (original, container) {
t.equal(original, item, 'item is a reference to moving target');
t.equal(container, div, 'container matches expected div');
}
});

View file

@ -1,16 +0,0 @@
'use strict';
function raise (el, type, options) {
var o = options || {};
var e = document.createEvent('Event');
e.initEvent(type, true, true);
Object.keys(o).forEach(apply);
el.dispatchEvent(e);
function apply (key) {
e[key] = o[key];
}
}
module.exports = {
raise: raise
};

View file

@ -1,9 +0,0 @@
'use strict';
var test = require('tape');
var dragula = require('..');
test('public api matches expectation', function (t) {
t.equal(typeof dragula, 'function', 'dragula is a function');
t.end();
});

View file

@ -1,106 +0,0 @@
'use strict';
var test = require('tape');
var events = require('./lib/events');
var dragula = require('..');
test('remove does not throw when not dragging', function (t) {
t.test('a single time', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.remove();
}, 'dragula ignores a single call to drake.remove');
st.end();
});
t.test('multiple times', function once (st) {
var drake = dragula();
st.doesNotThrow(function () {
drake.remove();
drake.remove();
drake.remove();
drake.remove();
}, 'dragula ignores multiple calls to drake.remove');
st.end();
});
t.end();
});
test('when dragging and remove gets called, element is removed', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.start(item);
drake.remove();
t.equal(div.children.length, 0, 'item got removed from container');
t.equal(drake.dragging, false, 'drake has stopped dragging');
t.end();
});
test('when dragging and remove gets called, remove event is emitted', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div]);
div.appendChild(item);
document.body.appendChild(div);
drake.start(item);
drake.on('remove', remove);
drake.on('dragend', dragend);
drake.remove();
t.plan(3);
t.end();
function dragend () {
t.pass('dragend got called');
}
function remove (target, container) {
t.equal(target, item, 'remove was invoked with item');
t.equal(container, div, 'remove was invoked with container');
}
});
test('when dragging a copy and remove gets called, cancel event is emitted', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], { copy: true });
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
drake.on('cancel', cancel);
drake.on('dragend', dragend);
drake.remove();
t.plan(4);
t.end();
function dragend () {
t.pass('dragend got called');
}
function cancel (target, container) {
t.equal(target.className, 'gu-transit', 'cancel was invoked with item');
t.notEqual(target, item, 'item is a copy and not the original');
t.equal(container, null, 'cancel was invoked with container');
}
});
test('when dragging a copy and remove gets called, cancel event is emitted', function (t) {
var div = document.createElement('div');
var item = document.createElement('div');
var drake = dragula([div], { copy: true });
div.appendChild(item);
document.body.appendChild(div);
events.raise(item, 'mousedown', { which: 0 });
events.raise(item, 'mousemove', { which: 0 });
drake.on('cancel', cancel);
drake.on('dragend', dragend);
drake.remove();
t.plan(4);
t.end();
function dragend () {
t.pass('dragend got called');
}
function cancel (target, container) {
t.equal(target.className, 'gu-transit', 'cancel was invoked with item');
t.notEqual(target, item, 'item is a copy and not the original');
t.equal(container, null, 'cancel was invoked with container');
}
});

View file

@ -69,25 +69,24 @@
var receipt = product.transaction.appStoreReceipt; var receipt = product.transaction.appStoreReceipt;
var price = product.price; var price = product.price;
HttpClient.send({ ApiClient.ajax({
type: "POST", type: "POST",
url: "https://mb3admin.com/test/admin/service/appstore/register", url: ApiClient.getUrl("Appstore/Register"),
contentType: 'application/x-www-form-urlencoded; charset=UTF-8', contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
data: { data: {
store: "Apple", Parameters: JSON.stringify({
application: "com.emby.mobile", store: "Apple",
product: productId, application: "com.emby.mobile",
type: "Subscription", product: productId,
feature: "MBSClubMonthly", type: "Subscription",
email: enteredEmail, feature: "MBSClubMonthly",
token: receipt, email: enteredEmail,
amt: price, storeToken: receipt,
storeId: transactionId amt: price,
}, storeId: transactionId
headers: { })
"X-Emby-Token": "08606E86D043"
} }
}).done(function () { }).done(function () {
callback(true, product); callback(true, product);

Some files were not shown because too many files have changed in this diff Show more