Commit 240010fc by 陈雨佳

Initial commit

parents
# rails-assets-Sortable
> The Bower package inside a gem
This gem was automatically generated. You can visit [rails-assets.org](https://rails-assets.org) for more information.
## Usage
Add rails-assets source block to your `Gemfile`:
```ruby
source "https://rails-assets.org" do
gem "rails-assets-Sortable"
end
```
Then, import the asset using Sprockets’ `require` directive:
```js
//= require "Sortable"
```
require "bundler/gem_tasks"
//= require Sortable/Sortable.js
//= require Sortable/ng-sortable.js
//= require Sortable/knockout-sortable.js
//= require Sortable/react-sortable-mixin.js
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']);
};
/**
* 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;
};
});
(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);
}
};
});
// 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);
}
}
});
Types = new Mongo.Collection('types');
Attributes = new Mongo.Collection('attributes');
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.');
}
});
'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});
}
});
'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});
}
});
// 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');
});
'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');
});
/**
* @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);
}
}
}));
});
}
};
}]);
});
/**
* @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;
});
(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);
.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;
}
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);
}
<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>
<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>
\ No newline at end of file
<template name="sortable">
{{#each items}}
{{> Template.contentBlock this}}
{{/each}}
</template>
<!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>
<!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>
require "rails-assets-Sortable/version"
module RailsAssetsSortable
def self.gem_path
Pathname(File.realpath(__FILE__)).join('../..')
end
def self.gem_spec
Gem::Specification::load(
gem_path.join("rails-assets-Sortable.gemspec").to_s
)
end
def self.load_paths
gem_path.join('app/assets').each_child.to_a
end
def self.dependencies
[
]
end
if defined?(Rails)
class Engine < ::Rails::Engine
# Rails -> use app/assets directory.
end
end
end
class RailsAssets
@components ||= []
class << self
attr_accessor :components
def load_paths
components.flat_map(&:load_paths)
end
end
end
RailsAssets.components << RailsAssetsSortable
module RailsAssetsSortable
VERSION = "1.4.2"
end
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'rails-assets-Sortable/version'
Gem::Specification.new do |spec|
spec.name = "rails-assets-Sortable"
spec.version = RailsAssetsSortable::VERSION
spec.authors = ["rails-assets.org"]
spec.description = "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery."
spec.summary = "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery."
spec.homepage = "http://rubaxa.github.io/Sortable"
spec.license = "MIT"
spec.files = `find ./* -type f | cut -b 3-`.split($/)
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "rake"
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment