Adding files to repo.
parent
41b83e155f
commit
4e38d8fbe1
|
|
@ -0,0 +1,4 @@
|
||||||
|
.project
|
||||||
|
dist
|
||||||
|
django_admin_sortable.egg-info
|
||||||
|
.pydevproject
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
This software is maintained by:
|
||||||
|
Brandon Taylor <btaylorweb@gmail.com>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
(c) Copyright 2011 Brandon Taylor - bTaylorWeb
|
||||||
|
|
||||||
|
django-dmin-sortable is free software: you can
|
||||||
|
redistribute it and/or modify it under
|
||||||
|
the terms of the Apache Public License v2.
|
||||||
|
|
||||||
|
django-admin-sortable is distributed in the hope
|
||||||
|
that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied
|
||||||
|
warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE.
|
||||||
142
README
142
README
|
|
@ -0,0 +1,142 @@
|
||||||
|
=============
|
||||||
|
admin-sortable
|
||||||
|
=============
|
||||||
|
|
||||||
|
What is it?
|
||||||
|
=============
|
||||||
|
The adminsortable app adds generic drag-and-drop facilities
|
||||||
|
to any Django model class or Tabular Inline via Django Admin
|
||||||
|
and jQueryUI.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
=============
|
||||||
|
1. Run ``setup.py`` or add ``adminsortable`` to your PYTHONPATH.
|
||||||
|
2. Copy the ``adminsortable`` folder from the media folder to the
|
||||||
|
location you server static files from.
|
||||||
|
3. Add ``adminsortable`` to your INSTALLED_APPS.
|
||||||
|
4. Have a look at the included sample_project to see working examples.
|
||||||
|
|
||||||
|
When a model is sortable, a tool-area link will be added that says "Change Order".
|
||||||
|
Click this link, and you will be taken to the custom view where you can drag-and-drop
|
||||||
|
the records into order.
|
||||||
|
|
||||||
|
Tabular inlines may be drag-and-dropped into any order directly from the change form.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=============
|
||||||
|
Models
|
||||||
|
----------------------
|
||||||
|
To add sorting to a model, your model needs to inherit from ``Sortable`` and
|
||||||
|
have an inner Meta class that inherits from ``Sortable.Meta``
|
||||||
|
|
||||||
|
#models.py
|
||||||
|
from adminsortable.models import Sortable
|
||||||
|
|
||||||
|
class MySortableClass(Sortable):
|
||||||
|
class Meta(Sortable.Meta)
|
||||||
|
|
||||||
|
title = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
For models that you want sortable relative to a ``ForeignKey`` field, you need to
|
||||||
|
specify an ``@classmethod`` that returns a double: the foreign key class, and the
|
||||||
|
name of the foreign key property as defined on your model, as a string.
|
||||||
|
|
||||||
|
#admin.py
|
||||||
|
class Category(models.Model):
|
||||||
|
title = models.CharField(max_length=50)
|
||||||
|
...
|
||||||
|
|
||||||
|
class MySortableClass(Sortable):
|
||||||
|
class Meta(Sortable.Meta)
|
||||||
|
|
||||||
|
category = models.ForeignKey(Category)
|
||||||
|
title = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sortable_by(cls):
|
||||||
|
return Category, 'category'
|
||||||
|
|
||||||
|
|
||||||
|
Sortable has one field: `order` and adds a default ordering value set to `order`.
|
||||||
|
|
||||||
|
South
|
||||||
|
------
|
||||||
|
If you're adding Sorting to an existing model, it is recommended that you use django-south,
|
||||||
|
http://south.areacode.com/ to create a migration to add the "order" field to your model.
|
||||||
|
|
||||||
|
|
||||||
|
*Django Admin Usage*
|
||||||
|
To enable sorting in the admin, you need to inherit from SortableAdmin:
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from myapp.models import MySortableClass
|
||||||
|
from adminsortable.admin import SortableAdmin
|
||||||
|
|
||||||
|
class MySortableAdminClass(SortableAdmin):
|
||||||
|
"""Any admin options you need go here"""
|
||||||
|
|
||||||
|
admin.site.register(MySortableClass, MySortableAdminClass)
|
||||||
|
|
||||||
|
|
||||||
|
To enable sorting on TabularInline models, you need to inherit from
|
||||||
|
SortableTabularInline:
|
||||||
|
|
||||||
|
from adminsortable.admin import SortableTabularInline
|
||||||
|
|
||||||
|
class MySortableTabularInline(SortableTabularInline):
|
||||||
|
"""Your inline options go here"""
|
||||||
|
|
||||||
|
|
||||||
|
Rationale
|
||||||
|
=============
|
||||||
|
Other projects have added drag-and-drop ordering to the ChangeList
|
||||||
|
view, however this introduces a couple of problems...
|
||||||
|
|
||||||
|
- The ChangeList view supports pagination, which makes drag-and-drop
|
||||||
|
ordering across pages impossible.
|
||||||
|
- The ChangeList view by default, does not order records based on a
|
||||||
|
foreign key, nor distinguish between rows that are associated with a
|
||||||
|
foreign key. This makes ordering the records grouped by a foreign key
|
||||||
|
impossible.
|
||||||
|
- The ChangeList supports in-line editing, and adding drag-and-drop
|
||||||
|
ordering on top of that just seemed a little much in my opinion.
|
||||||
|
|
||||||
|
Status
|
||||||
|
=============
|
||||||
|
admin-sortable is currently used in production.
|
||||||
|
|
||||||
|
Feautures
|
||||||
|
=============
|
||||||
|
Current
|
||||||
|
---------
|
||||||
|
- Supports Django 1.3+
|
||||||
|
- Adds an admin view to any model that inherits from Sortable and SortableAdmin
|
||||||
|
that allows you to drag and drop objects into any order via jQueryUI.
|
||||||
|
- Adds drag and drop ordering to Tabular Inline models that inherit from
|
||||||
|
SortableTabularInline
|
||||||
|
- Allows ordering of objects that are sorted on a Foreign Key, and adds ordering
|
||||||
|
to the foreign key object if it also inherits from Sortable.
|
||||||
|
|
||||||
|
Future
|
||||||
|
------
|
||||||
|
- Support for foreign keys that are self referential
|
||||||
|
- Support for StackedInline
|
||||||
|
- Support for ForeignKeys that have not been previously defined
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
=============
|
||||||
|
Sample Project
|
||||||
|
----------------
|
||||||
|
Requires django-appmedia, included
|
||||||
|
|
||||||
|
License
|
||||||
|
=============
|
||||||
|
The admin-sortable app is released
|
||||||
|
under the Apache Public License v2.
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
VERSION = (1, 0, 3, "f", 0) # following PEP 386
|
||||||
|
DEV_N = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
version = "%s.%s" % (VERSION[0], VERSION[1])
|
||||||
|
if VERSION[2]:
|
||||||
|
version = "%s.%s" % (version, VERSION[2])
|
||||||
|
if VERSION[3] != "f":
|
||||||
|
version = "%s%s%s" % (version, VERSION[3], VERSION[4])
|
||||||
|
if DEV_N:
|
||||||
|
version = "%s.dev%s" % (version, DEV_N)
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = get_version()
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,141 @@
|
||||||
|
import json
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
from django.contrib.admin import ModelAdmin, TabularInline
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.template.defaultfilters import capfirst
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
from adminsortable.models import Sortable
|
||||||
|
|
||||||
|
STATIC_URL = settings.STATIC_URL
|
||||||
|
|
||||||
|
|
||||||
|
class SortableAdmin(ModelAdmin):
|
||||||
|
ordering = ('order', 'id')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SortableAdmin, self).__init__(*args, **kwargs)
|
||||||
|
self.has_sortable_tabular_inlines = False
|
||||||
|
for klass in self.inlines:
|
||||||
|
if issubclass(klass, SortableTabularInline):
|
||||||
|
self.has_sortable_tabular_inlines = True
|
||||||
|
|
||||||
|
def get_urls(self):
|
||||||
|
urls = super(SortableAdmin, self).get_urls()
|
||||||
|
admin_urls = patterns('',
|
||||||
|
url(r'^sorting/do-sorting/(?P<model_type_id>\d+)/$',
|
||||||
|
self.admin_site.admin_view(self.do_sorting_view),
|
||||||
|
name='admin_do_sorting'), #this view changes the order
|
||||||
|
url(r'^sort/$', self.admin_site.admin_view(self.sort_view),
|
||||||
|
name='admin_sort'), #this view shows a link to the drag-and-drop view
|
||||||
|
)
|
||||||
|
return admin_urls + urls
|
||||||
|
|
||||||
|
def sort_view(self, request):
|
||||||
|
"""
|
||||||
|
Custom admin view that displays the objects as a list whose sort order can be
|
||||||
|
changed via drag-and-drop.
|
||||||
|
"""
|
||||||
|
opts = self.model._meta
|
||||||
|
admin_site = self.admin_site
|
||||||
|
has_perm = request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
|
||||||
|
objects = self.model.objects.all()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Determine if we need to regroup objects relative to a foreign key specified on the
|
||||||
|
model class that is extending Sortable.
|
||||||
|
"""
|
||||||
|
sortable_by = getattr(self.model, 'sortable_by', None)
|
||||||
|
if sortable_by:
|
||||||
|
sortable_by_class, sortable_by_expression = sortable_by()
|
||||||
|
sortable_by_class_display_name = sortable_by_class._meta.verbose_name_plural
|
||||||
|
sortable_by_class_is_sortable = sortable_by_class.is_sortable()
|
||||||
|
else:
|
||||||
|
sortable_by_class = sortable_by_expression = sortable_by_class_display_name = \
|
||||||
|
sortable_by_class_is_sortable = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
verbose_name_plural = opts.verbose_name_plural.__unicode__()
|
||||||
|
except AttributeError:
|
||||||
|
verbose_name_plural = opts.verbose_name_plural
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title' : 'Drag and drop %s to change display order' % capfirst(verbose_name_plural),
|
||||||
|
'opts' : opts,
|
||||||
|
'root_path' : '/%s' % admin_site.root_path,
|
||||||
|
'app_label' : opts.app_label,
|
||||||
|
'has_perm' : has_perm,
|
||||||
|
'objects' : objects,
|
||||||
|
'group_expression' : sortable_by_expression,
|
||||||
|
'sortable_by_class' : sortable_by_class,
|
||||||
|
'sortable_by_class_is_sortable' : sortable_by_class_is_sortable,
|
||||||
|
'sortable_by_class_display_name' : sortable_by_class_display_name
|
||||||
|
}
|
||||||
|
return render(request, 'adminsortable/change_list.html', context)
|
||||||
|
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
"""
|
||||||
|
If the model that inherits Sortable has more than one object,
|
||||||
|
its sort order can be changed. This view adds a link to the object_tools
|
||||||
|
block to take people to the view to change the sorting.
|
||||||
|
"""
|
||||||
|
if self.model.is_sortable():
|
||||||
|
self.change_list_template = 'adminsortable/change_list_with_sort_link.html'
|
||||||
|
return super(SortableAdmin, self).changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
|
def change_view(self, request, object_id, extra_context=None):
|
||||||
|
has_sortable_tabular_inlines = self.has_sortable_tabular_inlines
|
||||||
|
if has_sortable_tabular_inlines:
|
||||||
|
self.change_form_template = 'adminsortable/change_form.html'
|
||||||
|
extra_context = {'has_sortable_tabular_inlines' : self.has_sortable_tabular_inlines}
|
||||||
|
return super(SortableAdmin, self).change_view(request, object_id, extra_context=extra_context)
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def do_sorting_view(self, request, model_type_id=None):
|
||||||
|
"""
|
||||||
|
This view sets the ordering of the objects for the model type and primary keys
|
||||||
|
passed in. It must be an Ajax POST.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if request.is_ajax() and request.method == 'POST':
|
||||||
|
try:
|
||||||
|
indexes = request.POST.get('indexes', []).split(',')
|
||||||
|
klass = ContentType.objects.get(id=model_type_id).model_class()
|
||||||
|
objects_dict = dict([(obj.pk, obj) for obj in klass.objects.filter(pk__in=indexes)])
|
||||||
|
min_index = min(objects_dict.values(), key=lambda x: getattr(x, 'order'))
|
||||||
|
min_index = getattr(min_index, 'order') or 0
|
||||||
|
for index in indexes:
|
||||||
|
obj = objects_dict[int(index)]
|
||||||
|
setattr(obj, 'order', min_index)
|
||||||
|
obj.save()
|
||||||
|
min_index += 1
|
||||||
|
response = {'objects_sorted' : True}
|
||||||
|
except (IndexError, klass.DoesNotExist, AttributeError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
response = {'objects_sorted' : False}
|
||||||
|
return HttpResponse(json.dumps(response, ensure_ascii=False),
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
class SortableTabularInline(TabularInline):
|
||||||
|
"""Custom template that enables sorting for tabular inlines"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SortableTabularInline, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not issubclass(self.model, Sortable):
|
||||||
|
raise Warning(u'Models that inherit SortableTabluarInline must inherit from Sortable')
|
||||||
|
|
||||||
|
"""
|
||||||
|
This property is referenced by tabular.html's <h2> to show a message on whether or
|
||||||
|
not the inlines are sortable. It is exposed in: inline_admin_formset.opts
|
||||||
|
"""
|
||||||
|
self.is_sortable = self.model.is_sortable()
|
||||||
|
|
||||||
|
template = 'adminsortable/edit_inline/tabular.html'
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
#sortable ul
|
||||||
|
{
|
||||||
|
-webkit-padding-start: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul ul
|
||||||
|
{
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul li,
|
||||||
|
#sortable ul li a
|
||||||
|
{
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul li
|
||||||
|
{
|
||||||
|
overflow: auto;
|
||||||
|
margin-left: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable .sortable
|
||||||
|
{
|
||||||
|
list-style: none;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable .sortable.single,
|
||||||
|
#sortable ul ul a
|
||||||
|
{
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul ul a
|
||||||
|
{
|
||||||
|
color: #5B80B2;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable a:hover
|
||||||
|
{
|
||||||
|
color: #003366;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
.tabular .sortable
|
||||||
|
{
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
jQuery(function($){
|
||||||
|
$('.sortable').sortable({
|
||||||
|
axis : 'y',
|
||||||
|
containment : 'parent',
|
||||||
|
tolerance : 'pointer',
|
||||||
|
items : 'li',
|
||||||
|
stop : function(event, ui)
|
||||||
|
{
|
||||||
|
var indexes = Array();
|
||||||
|
ui.item.parent().children('li').each(function(i)
|
||||||
|
{
|
||||||
|
indexes.push($(this).find(':hidden[name="pk"]').val());
|
||||||
|
});
|
||||||
|
$.ajax({
|
||||||
|
url: ui.item.find('a.admin_sorting_url').attr('href'),
|
||||||
|
type: 'POST',
|
||||||
|
data: { indexes: indexes.join(',') }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
jQuery(function($){
|
||||||
|
if ($(':hidden[name="admin_sorting_url"]').length > 0)
|
||||||
|
{
|
||||||
|
var tabular_inline_rows = $('.tabular table tbody tr');
|
||||||
|
tabular_inline_rows.addClass('sortable');
|
||||||
|
$('.tabular.inline-related').sortable({
|
||||||
|
axis : 'y',
|
||||||
|
containment : 'parent',
|
||||||
|
tolerance : 'pointer',
|
||||||
|
items : 'tr',
|
||||||
|
stop : function(event, ui)
|
||||||
|
{
|
||||||
|
var indexes = Array();
|
||||||
|
ui.item.parent().children('tr').each(function(i)
|
||||||
|
{
|
||||||
|
index_value = $(this).find('.original :hidden:first').val();
|
||||||
|
if (index_value != "" && index_value != undefined)
|
||||||
|
indexes.push(index_value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ui.item.parent().find(':hidden[name="admin_sorting_url"]').val(),
|
||||||
|
type: 'POST',
|
||||||
|
data: { indexes : indexes.join(',') },
|
||||||
|
success: function()
|
||||||
|
{
|
||||||
|
//re-stripe table
|
||||||
|
tabular_inline_rows.removeClass('row1 row2');
|
||||||
|
$('.tabular table tbody tr:odd').addClass('row2');
|
||||||
|
$('.tabular table tbody tr:even').addClass('row1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,314 @@
|
||||||
|
/*!
|
||||||
|
* jQuery UI 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
// prevent duplicate loading
|
||||||
|
// this is only a problem because we proxy existing functions
|
||||||
|
// and we don't want to double proxy them
|
||||||
|
$.ui = $.ui || {};
|
||||||
|
if ( $.ui.version ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend( $.ui, {
|
||||||
|
version: "1.8.16",
|
||||||
|
|
||||||
|
keyCode: {
|
||||||
|
ALT: 18,
|
||||||
|
BACKSPACE: 8,
|
||||||
|
CAPS_LOCK: 20,
|
||||||
|
COMMA: 188,
|
||||||
|
COMMAND: 91,
|
||||||
|
COMMAND_LEFT: 91, // COMMAND
|
||||||
|
COMMAND_RIGHT: 93,
|
||||||
|
CONTROL: 17,
|
||||||
|
DELETE: 46,
|
||||||
|
DOWN: 40,
|
||||||
|
END: 35,
|
||||||
|
ENTER: 13,
|
||||||
|
ESCAPE: 27,
|
||||||
|
HOME: 36,
|
||||||
|
INSERT: 45,
|
||||||
|
LEFT: 37,
|
||||||
|
MENU: 93, // COMMAND_RIGHT
|
||||||
|
NUMPAD_ADD: 107,
|
||||||
|
NUMPAD_DECIMAL: 110,
|
||||||
|
NUMPAD_DIVIDE: 111,
|
||||||
|
NUMPAD_ENTER: 108,
|
||||||
|
NUMPAD_MULTIPLY: 106,
|
||||||
|
NUMPAD_SUBTRACT: 109,
|
||||||
|
PAGE_DOWN: 34,
|
||||||
|
PAGE_UP: 33,
|
||||||
|
PERIOD: 190,
|
||||||
|
RIGHT: 39,
|
||||||
|
SHIFT: 16,
|
||||||
|
SPACE: 32,
|
||||||
|
TAB: 9,
|
||||||
|
UP: 38,
|
||||||
|
WINDOWS: 91 // COMMAND
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// plugins
|
||||||
|
$.fn.extend({
|
||||||
|
propAttr: $.fn.prop || $.fn.attr,
|
||||||
|
|
||||||
|
_focus: $.fn.focus,
|
||||||
|
focus: function( delay, fn ) {
|
||||||
|
return typeof delay === "number" ?
|
||||||
|
this.each(function() {
|
||||||
|
var elem = this;
|
||||||
|
setTimeout(function() {
|
||||||
|
$( elem ).focus();
|
||||||
|
if ( fn ) {
|
||||||
|
fn.call( elem );
|
||||||
|
}
|
||||||
|
}, delay );
|
||||||
|
}) :
|
||||||
|
this._focus.apply( this, arguments );
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollParent: function() {
|
||||||
|
var scrollParent;
|
||||||
|
if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
|
||||||
|
scrollParent = this.parents().filter(function() {
|
||||||
|
return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
|
||||||
|
}).eq(0);
|
||||||
|
} else {
|
||||||
|
scrollParent = this.parents().filter(function() {
|
||||||
|
return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
|
||||||
|
}).eq(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
|
||||||
|
},
|
||||||
|
|
||||||
|
zIndex: function( zIndex ) {
|
||||||
|
if ( zIndex !== undefined ) {
|
||||||
|
return this.css( "zIndex", zIndex );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.length ) {
|
||||||
|
var elem = $( this[ 0 ] ), position, value;
|
||||||
|
while ( elem.length && elem[ 0 ] !== document ) {
|
||||||
|
// Ignore z-index if position is set to a value where z-index is ignored by the browser
|
||||||
|
// This makes behavior of this function consistent across browsers
|
||||||
|
// WebKit always returns auto if the element is positioned
|
||||||
|
position = elem.css( "position" );
|
||||||
|
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
|
||||||
|
// IE returns 0 when zIndex is not specified
|
||||||
|
// other browsers return a string
|
||||||
|
// we ignore the case of nested elements with an explicit value of 0
|
||||||
|
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
|
||||||
|
value = parseInt( elem.css( "zIndex" ), 10 );
|
||||||
|
if ( !isNaN( value ) && value !== 0 ) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elem = elem.parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
disableSelection: function() {
|
||||||
|
return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
|
||||||
|
".ui-disableSelection", function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
enableSelection: function() {
|
||||||
|
return this.unbind( ".ui-disableSelection" );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each( [ "Width", "Height" ], function( i, name ) {
|
||||||
|
var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
|
||||||
|
type = name.toLowerCase(),
|
||||||
|
orig = {
|
||||||
|
innerWidth: $.fn.innerWidth,
|
||||||
|
innerHeight: $.fn.innerHeight,
|
||||||
|
outerWidth: $.fn.outerWidth,
|
||||||
|
outerHeight: $.fn.outerHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
function reduce( elem, size, border, margin ) {
|
||||||
|
$.each( side, function() {
|
||||||
|
size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0;
|
||||||
|
if ( border ) {
|
||||||
|
size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0;
|
||||||
|
}
|
||||||
|
if ( margin ) {
|
||||||
|
size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn[ "inner" + name ] = function( size ) {
|
||||||
|
if ( size === undefined ) {
|
||||||
|
return orig[ "inner" + name ].call( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.each(function() {
|
||||||
|
$( this ).css( type, reduce( this, size ) + "px" );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn[ "outer" + name] = function( size, margin ) {
|
||||||
|
if ( typeof size !== "number" ) {
|
||||||
|
return orig[ "outer" + name ].call( this, size );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.each(function() {
|
||||||
|
$( this).css( type, reduce( this, size, true, margin ) + "px" );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// selectors
|
||||||
|
function focusable( element, isTabIndexNotNaN ) {
|
||||||
|
var nodeName = element.nodeName.toLowerCase();
|
||||||
|
if ( "area" === nodeName ) {
|
||||||
|
var map = element.parentNode,
|
||||||
|
mapName = map.name,
|
||||||
|
img;
|
||||||
|
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
img = $( "img[usemap=#" + mapName + "]" )[0];
|
||||||
|
return !!img && visible( img );
|
||||||
|
}
|
||||||
|
return ( /input|select|textarea|button|object/.test( nodeName )
|
||||||
|
? !element.disabled
|
||||||
|
: "a" == nodeName
|
||||||
|
? element.href || isTabIndexNotNaN
|
||||||
|
: isTabIndexNotNaN)
|
||||||
|
// the element and all of its ancestors must be visible
|
||||||
|
&& visible( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
function visible( element ) {
|
||||||
|
return !$( element ).parents().andSelf().filter(function() {
|
||||||
|
return $.curCSS( this, "visibility" ) === "hidden" ||
|
||||||
|
$.expr.filters.hidden( this );
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend( $.expr[ ":" ], {
|
||||||
|
data: function( elem, i, match ) {
|
||||||
|
return !!$.data( elem, match[ 3 ] );
|
||||||
|
},
|
||||||
|
|
||||||
|
focusable: function( element ) {
|
||||||
|
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
tabbable: function( element ) {
|
||||||
|
var tabIndex = $.attr( element, "tabindex" ),
|
||||||
|
isTabIndexNaN = isNaN( tabIndex );
|
||||||
|
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// support
|
||||||
|
$(function() {
|
||||||
|
var body = document.body,
|
||||||
|
div = body.appendChild( div = document.createElement( "div" ) );
|
||||||
|
|
||||||
|
$.extend( div.style, {
|
||||||
|
minHeight: "100px",
|
||||||
|
height: "auto",
|
||||||
|
padding: 0,
|
||||||
|
borderWidth: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$.support.minHeight = div.offsetHeight === 100;
|
||||||
|
$.support.selectstart = "onselectstart" in div;
|
||||||
|
|
||||||
|
// set display to none to avoid a layout bug in IE
|
||||||
|
// http://dev.jquery.com/ticket/4014
|
||||||
|
body.removeChild( div ).style.display = "none";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
$.extend( $.ui, {
|
||||||
|
// $.ui.plugin is deprecated. Use the proxy pattern instead.
|
||||||
|
plugin: {
|
||||||
|
add: function( module, option, set ) {
|
||||||
|
var proto = $.ui[ module ].prototype;
|
||||||
|
for ( var i in set ) {
|
||||||
|
proto.plugins[ i ] = proto.plugins[ i ] || [];
|
||||||
|
proto.plugins[ i ].push( [ option, set[ i ] ] );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
call: function( instance, name, args ) {
|
||||||
|
var set = instance.plugins[ name ];
|
||||||
|
if ( !set || !instance.element[ 0 ].parentNode ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( var i = 0; i < set.length; i++ ) {
|
||||||
|
if ( instance.options[ set[ i ][ 0 ] ] ) {
|
||||||
|
set[ i ][ 1 ].apply( instance.element, args );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// will be deprecated when we switch to jQuery 1.4 - use jQuery.contains()
|
||||||
|
contains: function( a, b ) {
|
||||||
|
return document.compareDocumentPosition ?
|
||||||
|
a.compareDocumentPosition( b ) & 16 :
|
||||||
|
a !== b && a.contains( b );
|
||||||
|
},
|
||||||
|
|
||||||
|
// only used by resizable
|
||||||
|
hasScroll: function( el, a ) {
|
||||||
|
|
||||||
|
//If overflow is hidden, the element might have extra content, but the user wants to hide it
|
||||||
|
if ( $( el ).css( "overflow" ) === "hidden") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
|
||||||
|
has = false;
|
||||||
|
|
||||||
|
if ( el[ scroll ] > 0 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: determine which cases actually cause this to happen
|
||||||
|
// if the element doesn't have the scroll set, see if it's possible to
|
||||||
|
// set the scroll
|
||||||
|
el[ scroll ] = 1;
|
||||||
|
has = ( el[ scroll ] > 0 );
|
||||||
|
el[ scroll ] = 0;
|
||||||
|
return has;
|
||||||
|
},
|
||||||
|
|
||||||
|
// these are odd functions, fix the API or move into individual plugins
|
||||||
|
isOverAxis: function( x, reference, size ) {
|
||||||
|
//Determines when x coordinate is over "b" element axis
|
||||||
|
return ( x > reference ) && ( x < ( reference + size ) );
|
||||||
|
},
|
||||||
|
isOver: function( y, x, top, left, height, width ) {
|
||||||
|
//Determines when x, y coordinates is over "b" element
|
||||||
|
return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})( jQuery );
|
||||||
|
|
@ -0,0 +1,825 @@
|
||||||
|
/*
|
||||||
|
* jQuery UI Draggable 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Draggables
|
||||||
|
*
|
||||||
|
* Depends:
|
||||||
|
* jquery.ui.core.js
|
||||||
|
* jquery.ui.mouse.js
|
||||||
|
* jquery.ui.widget.js
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
$.widget("ui.draggable", $.ui.mouse, {
|
||||||
|
widgetEventPrefix: "drag",
|
||||||
|
options: {
|
||||||
|
addClasses: true,
|
||||||
|
appendTo: "parent",
|
||||||
|
axis: false,
|
||||||
|
connectToSortable: false,
|
||||||
|
containment: false,
|
||||||
|
cursor: "auto",
|
||||||
|
cursorAt: false,
|
||||||
|
grid: false,
|
||||||
|
handle: false,
|
||||||
|
helper: "original",
|
||||||
|
iframeFix: false,
|
||||||
|
opacity: false,
|
||||||
|
refreshPositions: false,
|
||||||
|
revert: false,
|
||||||
|
revertDuration: 500,
|
||||||
|
scope: "default",
|
||||||
|
scroll: true,
|
||||||
|
scrollSensitivity: 20,
|
||||||
|
scrollSpeed: 20,
|
||||||
|
snap: false,
|
||||||
|
snapMode: "both",
|
||||||
|
snapTolerance: 20,
|
||||||
|
stack: false,
|
||||||
|
zIndex: false
|
||||||
|
},
|
||||||
|
_create: function() {
|
||||||
|
|
||||||
|
if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
|
||||||
|
this.element[0].style.position = 'relative';
|
||||||
|
|
||||||
|
(this.options.addClasses && this.element.addClass("ui-draggable"));
|
||||||
|
(this.options.disabled && this.element.addClass("ui-draggable-disabled"));
|
||||||
|
|
||||||
|
this._mouseInit();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
if(!this.element.data('draggable')) return;
|
||||||
|
this.element
|
||||||
|
.removeData("draggable")
|
||||||
|
.unbind(".draggable")
|
||||||
|
.removeClass("ui-draggable"
|
||||||
|
+ " ui-draggable-dragging"
|
||||||
|
+ " ui-draggable-disabled");
|
||||||
|
this._mouseDestroy();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseCapture: function(event) {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
|
||||||
|
// among others, prevent a drag on a resizable-handle
|
||||||
|
if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Quit if we're not on a valid handle
|
||||||
|
this.handle = this._getHandle(event);
|
||||||
|
if (!this.handle)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( o.iframeFix ) {
|
||||||
|
$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
|
||||||
|
$('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
|
||||||
|
.css({
|
||||||
|
width: this.offsetWidth+"px", height: this.offsetHeight+"px",
|
||||||
|
position: "absolute", opacity: "0.001", zIndex: 1000
|
||||||
|
})
|
||||||
|
.css($(this).offset())
|
||||||
|
.appendTo("body");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseStart: function(event) {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
|
||||||
|
//Create and append the visible helper
|
||||||
|
this.helper = this._createHelper(event);
|
||||||
|
|
||||||
|
//Cache the helper size
|
||||||
|
this._cacheHelperProportions();
|
||||||
|
|
||||||
|
//If ddmanager is used for droppables, set the global draggable
|
||||||
|
if($.ui.ddmanager)
|
||||||
|
$.ui.ddmanager.current = this;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Position generation -
|
||||||
|
* This block generates everything position related - it's the core of draggables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Cache the margins of the original element
|
||||||
|
this._cacheMargins();
|
||||||
|
|
||||||
|
//Store the helper's css position
|
||||||
|
this.cssPosition = this.helper.css("position");
|
||||||
|
this.scrollParent = this.helper.scrollParent();
|
||||||
|
|
||||||
|
//The element's absolute position on the page minus margins
|
||||||
|
this.offset = this.positionAbs = this.element.offset();
|
||||||
|
this.offset = {
|
||||||
|
top: this.offset.top - this.margins.top,
|
||||||
|
left: this.offset.left - this.margins.left
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend(this.offset, {
|
||||||
|
click: { //Where the click happened, relative to the element
|
||||||
|
left: event.pageX - this.offset.left,
|
||||||
|
top: event.pageY - this.offset.top
|
||||||
|
},
|
||||||
|
parent: this._getParentOffset(),
|
||||||
|
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
|
||||||
|
});
|
||||||
|
|
||||||
|
//Generate the original position
|
||||||
|
this.originalPosition = this.position = this._generatePosition(event);
|
||||||
|
this.originalPageX = event.pageX;
|
||||||
|
this.originalPageY = event.pageY;
|
||||||
|
|
||||||
|
//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
|
||||||
|
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
|
||||||
|
|
||||||
|
//Set a containment if given in the options
|
||||||
|
if(o.containment)
|
||||||
|
this._setContainment();
|
||||||
|
|
||||||
|
//Trigger event + callbacks
|
||||||
|
if(this._trigger("start", event) === false) {
|
||||||
|
this._clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Recache the helper size
|
||||||
|
this._cacheHelperProportions();
|
||||||
|
|
||||||
|
//Prepare the droppable offsets
|
||||||
|
if ($.ui.ddmanager && !o.dropBehaviour)
|
||||||
|
$.ui.ddmanager.prepareOffsets(this, event);
|
||||||
|
|
||||||
|
this.helper.addClass("ui-draggable-dragging");
|
||||||
|
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
|
||||||
|
|
||||||
|
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
|
||||||
|
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDrag: function(event, noPropagation) {
|
||||||
|
|
||||||
|
//Compute the helpers position
|
||||||
|
this.position = this._generatePosition(event);
|
||||||
|
this.positionAbs = this._convertPositionTo("absolute");
|
||||||
|
|
||||||
|
//Call plugins and callbacks and use the resulting position if something is returned
|
||||||
|
if (!noPropagation) {
|
||||||
|
var ui = this._uiHash();
|
||||||
|
if(this._trigger('drag', event, ui) === false) {
|
||||||
|
this._mouseUp({});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.position = ui.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
|
||||||
|
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
|
||||||
|
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseStop: function(event) {
|
||||||
|
|
||||||
|
//If we are using droppables, inform the manager about the drop
|
||||||
|
var dropped = false;
|
||||||
|
if ($.ui.ddmanager && !this.options.dropBehaviour)
|
||||||
|
dropped = $.ui.ddmanager.drop(this, event);
|
||||||
|
|
||||||
|
//if a drop comes from outside (a sortable)
|
||||||
|
if(this.dropped) {
|
||||||
|
dropped = this.dropped;
|
||||||
|
this.dropped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if the original element is removed, don't bother to continue if helper is set to "original"
|
||||||
|
if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
|
||||||
|
var self = this;
|
||||||
|
$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
|
||||||
|
if(self._trigger("stop", event) !== false) {
|
||||||
|
self._clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(this._trigger("stop", event) !== false) {
|
||||||
|
this._clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseUp: function(event) {
|
||||||
|
if (this.options.iframeFix === true) {
|
||||||
|
$("div.ui-draggable-iframeFix").each(function() {
|
||||||
|
this.parentNode.removeChild(this);
|
||||||
|
}); //Remove frame helpers
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
|
||||||
|
if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
|
||||||
|
|
||||||
|
return $.ui.mouse.prototype._mouseUp.call(this, event);
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel: function() {
|
||||||
|
|
||||||
|
if(this.helper.is(".ui-draggable-dragging")) {
|
||||||
|
this._mouseUp({});
|
||||||
|
} else {
|
||||||
|
this._clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_getHandle: function(event) {
|
||||||
|
|
||||||
|
var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
|
||||||
|
$(this.options.handle, this.element)
|
||||||
|
.find("*")
|
||||||
|
.andSelf()
|
||||||
|
.each(function() {
|
||||||
|
if(this == event.target) handle = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_createHelper: function(event) {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
|
||||||
|
|
||||||
|
if(!helper.parents('body').length)
|
||||||
|
helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
|
||||||
|
|
||||||
|
if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
|
||||||
|
helper.css("position", "absolute");
|
||||||
|
|
||||||
|
return helper;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_adjustOffsetFromHelper: function(obj) {
|
||||||
|
if (typeof obj == 'string') {
|
||||||
|
obj = obj.split(' ');
|
||||||
|
}
|
||||||
|
if ($.isArray(obj)) {
|
||||||
|
obj = {left: +obj[0], top: +obj[1] || 0};
|
||||||
|
}
|
||||||
|
if ('left' in obj) {
|
||||||
|
this.offset.click.left = obj.left + this.margins.left;
|
||||||
|
}
|
||||||
|
if ('right' in obj) {
|
||||||
|
this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
|
||||||
|
}
|
||||||
|
if ('top' in obj) {
|
||||||
|
this.offset.click.top = obj.top + this.margins.top;
|
||||||
|
}
|
||||||
|
if ('bottom' in obj) {
|
||||||
|
this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getParentOffset: function() {
|
||||||
|
|
||||||
|
//Get the offsetParent and cache its position
|
||||||
|
this.offsetParent = this.helper.offsetParent();
|
||||||
|
var po = this.offsetParent.offset();
|
||||||
|
|
||||||
|
// This is a special case where we need to modify a offset calculated on start, since the following happened:
|
||||||
|
// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
|
||||||
|
// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
|
||||||
|
// the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
|
||||||
|
if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
|
||||||
|
po.left += this.scrollParent.scrollLeft();
|
||||||
|
po.top += this.scrollParent.scrollTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
|
||||||
|
|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
|
||||||
|
po = { top: 0, left: 0 };
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
|
||||||
|
left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_getRelativeOffset: function() {
|
||||||
|
|
||||||
|
if(this.cssPosition == "relative") {
|
||||||
|
var p = this.element.position();
|
||||||
|
return {
|
||||||
|
top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
|
||||||
|
left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { top: 0, left: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_cacheMargins: function() {
|
||||||
|
this.margins = {
|
||||||
|
left: (parseInt(this.element.css("marginLeft"),10) || 0),
|
||||||
|
top: (parseInt(this.element.css("marginTop"),10) || 0),
|
||||||
|
right: (parseInt(this.element.css("marginRight"),10) || 0),
|
||||||
|
bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_cacheHelperProportions: function() {
|
||||||
|
this.helperProportions = {
|
||||||
|
width: this.helper.outerWidth(),
|
||||||
|
height: this.helper.outerHeight()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_setContainment: function() {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
|
||||||
|
if(o.containment == 'document' || o.containment == 'window') this.containment = [
|
||||||
|
o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
|
||||||
|
o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
|
||||||
|
(o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
|
||||||
|
(o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
|
||||||
|
];
|
||||||
|
|
||||||
|
if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
|
||||||
|
var c = $(o.containment);
|
||||||
|
var ce = c[0]; if(!ce) return;
|
||||||
|
var co = c.offset();
|
||||||
|
var over = ($(ce).css("overflow") != 'hidden');
|
||||||
|
|
||||||
|
this.containment = [
|
||||||
|
(parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
|
||||||
|
(parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
|
||||||
|
(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
|
||||||
|
(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
|
||||||
|
];
|
||||||
|
this.relative_container = c;
|
||||||
|
|
||||||
|
} else if(o.containment.constructor == Array) {
|
||||||
|
this.containment = o.containment;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_convertPositionTo: function(d, pos) {
|
||||||
|
|
||||||
|
if(!pos) pos = this.position;
|
||||||
|
var mod = d == "absolute" ? 1 : -1;
|
||||||
|
var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: (
|
||||||
|
pos.top // The absolute mouse position
|
||||||
|
+ this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
+ this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
|
||||||
|
- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
|
||||||
|
),
|
||||||
|
left: (
|
||||||
|
pos.left // The absolute mouse position
|
||||||
|
+ this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
+ this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
|
||||||
|
- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_generatePosition: function(event) {
|
||||||
|
|
||||||
|
var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
|
||||||
|
var pageX = event.pageX;
|
||||||
|
var pageY = event.pageY;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Position constraining -
|
||||||
|
* Constrain the position to a mix of grid, containment.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(this.originalPosition) { //If we are not dragging yet, we won't check for options
|
||||||
|
var containment;
|
||||||
|
if(this.containment) {
|
||||||
|
if (this.relative_container){
|
||||||
|
var co = this.relative_container.offset();
|
||||||
|
containment = [ this.containment[0] + co.left,
|
||||||
|
this.containment[1] + co.top,
|
||||||
|
this.containment[2] + co.left,
|
||||||
|
this.containment[3] + co.top ];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
containment = this.containment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
|
||||||
|
if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
|
||||||
|
if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
|
||||||
|
if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o.grid) {
|
||||||
|
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
|
||||||
|
var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
|
||||||
|
pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
|
||||||
|
|
||||||
|
var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
|
||||||
|
pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: (
|
||||||
|
pageY // The absolute mouse position
|
||||||
|
- this.offset.click.top // Click offset (relative to the element)
|
||||||
|
- this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
- this.offset.parent.top // The offsetParent's offset without borders (offset + border)
|
||||||
|
+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
|
||||||
|
),
|
||||||
|
left: (
|
||||||
|
pageX // The absolute mouse position
|
||||||
|
- this.offset.click.left // Click offset (relative to the element)
|
||||||
|
- this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
- this.offset.parent.left // The offsetParent's offset without borders (offset + border)
|
||||||
|
+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_clear: function() {
|
||||||
|
this.helper.removeClass("ui-draggable-dragging");
|
||||||
|
if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
|
||||||
|
//if($.ui.ddmanager) $.ui.ddmanager.current = null;
|
||||||
|
this.helper = null;
|
||||||
|
this.cancelHelperRemoval = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// From now on bulk stuff - mainly helpers
|
||||||
|
|
||||||
|
_trigger: function(type, event, ui) {
|
||||||
|
ui = ui || this._uiHash();
|
||||||
|
$.ui.plugin.call(this, type, [event, ui]);
|
||||||
|
if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
|
||||||
|
return $.Widget.prototype._trigger.call(this, type, event, ui);
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: {},
|
||||||
|
|
||||||
|
_uiHash: function(event) {
|
||||||
|
return {
|
||||||
|
helper: this.helper,
|
||||||
|
position: this.position,
|
||||||
|
originalPosition: this.originalPosition,
|
||||||
|
offset: this.positionAbs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$.extend($.ui.draggable, {
|
||||||
|
version: "1.8.16"
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "connectToSortable", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
|
||||||
|
var inst = $(this).data("draggable"), o = inst.options,
|
||||||
|
uiSortable = $.extend({}, ui, { item: inst.element });
|
||||||
|
inst.sortables = [];
|
||||||
|
$(o.connectToSortable).each(function() {
|
||||||
|
var sortable = $.data(this, 'sortable');
|
||||||
|
if (sortable && !sortable.options.disabled) {
|
||||||
|
inst.sortables.push({
|
||||||
|
instance: sortable,
|
||||||
|
shouldRevert: sortable.options.revert
|
||||||
|
});
|
||||||
|
sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
|
||||||
|
sortable._trigger("activate", event, uiSortable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
|
||||||
|
//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
|
||||||
|
var inst = $(this).data("draggable"),
|
||||||
|
uiSortable = $.extend({}, ui, { item: inst.element });
|
||||||
|
|
||||||
|
$.each(inst.sortables, function() {
|
||||||
|
if(this.instance.isOver) {
|
||||||
|
|
||||||
|
this.instance.isOver = 0;
|
||||||
|
|
||||||
|
inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
|
||||||
|
this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
|
||||||
|
|
||||||
|
//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
|
||||||
|
if(this.shouldRevert) this.instance.options.revert = true;
|
||||||
|
|
||||||
|
//Trigger the stop of the sortable
|
||||||
|
this.instance._mouseStop(event);
|
||||||
|
|
||||||
|
this.instance.options.helper = this.instance.options._helper;
|
||||||
|
|
||||||
|
//If the helper has been the original item, restore properties in the sortable
|
||||||
|
if(inst.options.helper == 'original')
|
||||||
|
this.instance.currentItem.css({ top: 'auto', left: 'auto' });
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
|
||||||
|
this.instance._trigger("deactivate", event, uiSortable);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
drag: function(event, ui) {
|
||||||
|
|
||||||
|
var inst = $(this).data("draggable"), self = this;
|
||||||
|
|
||||||
|
var checkPos = function(o) {
|
||||||
|
var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
|
||||||
|
var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
|
||||||
|
var itemHeight = o.height, itemWidth = o.width;
|
||||||
|
var itemTop = o.top, itemLeft = o.left;
|
||||||
|
|
||||||
|
return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.each(inst.sortables, function(i) {
|
||||||
|
|
||||||
|
//Copy over some variables to allow calling the sortable's native _intersectsWith
|
||||||
|
this.instance.positionAbs = inst.positionAbs;
|
||||||
|
this.instance.helperProportions = inst.helperProportions;
|
||||||
|
this.instance.offset.click = inst.offset.click;
|
||||||
|
|
||||||
|
if(this.instance._intersectsWith(this.instance.containerCache)) {
|
||||||
|
|
||||||
|
//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
|
||||||
|
if(!this.instance.isOver) {
|
||||||
|
|
||||||
|
this.instance.isOver = 1;
|
||||||
|
//Now we fake the start of dragging for the sortable instance,
|
||||||
|
//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
|
||||||
|
//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
|
||||||
|
this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
|
||||||
|
this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
|
||||||
|
this.instance.options.helper = function() { return ui.helper[0]; };
|
||||||
|
|
||||||
|
event.target = this.instance.currentItem[0];
|
||||||
|
this.instance._mouseCapture(event, true);
|
||||||
|
this.instance._mouseStart(event, true, true);
|
||||||
|
|
||||||
|
//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
|
||||||
|
this.instance.offset.click.top = inst.offset.click.top;
|
||||||
|
this.instance.offset.click.left = inst.offset.click.left;
|
||||||
|
this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
|
||||||
|
this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
|
||||||
|
|
||||||
|
inst._trigger("toSortable", event);
|
||||||
|
inst.dropped = this.instance.element; //draggable revert needs that
|
||||||
|
//hack so receive/update callbacks work (mostly)
|
||||||
|
inst.currentItem = inst.element;
|
||||||
|
this.instance.fromOutside = inst;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
|
||||||
|
if(this.instance.currentItem) this.instance._mouseDrag(event);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//If it doesn't intersect with the sortable, and it intersected before,
|
||||||
|
//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
|
||||||
|
if(this.instance.isOver) {
|
||||||
|
|
||||||
|
this.instance.isOver = 0;
|
||||||
|
this.instance.cancelHelperRemoval = true;
|
||||||
|
|
||||||
|
//Prevent reverting on this forced stop
|
||||||
|
this.instance.options.revert = false;
|
||||||
|
|
||||||
|
// The out event needs to be triggered independently
|
||||||
|
this.instance._trigger('out', event, this.instance._uiHash(this.instance));
|
||||||
|
|
||||||
|
this.instance._mouseStop(event, true);
|
||||||
|
this.instance.options.helper = this.instance.options._helper;
|
||||||
|
|
||||||
|
//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
|
||||||
|
this.instance.currentItem.remove();
|
||||||
|
if(this.instance.placeholder) this.instance.placeholder.remove();
|
||||||
|
|
||||||
|
inst._trigger("fromSortable", event);
|
||||||
|
inst.dropped = false; //draggable revert needs that
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "cursor", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var t = $('body'), o = $(this).data('draggable').options;
|
||||||
|
if (t.css("cursor")) o._cursor = t.css("cursor");
|
||||||
|
t.css("cursor", o.cursor);
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var o = $(this).data('draggable').options;
|
||||||
|
if (o._cursor) $('body').css("cursor", o._cursor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "opacity", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var t = $(ui.helper), o = $(this).data('draggable').options;
|
||||||
|
if(t.css("opacity")) o._opacity = t.css("opacity");
|
||||||
|
t.css('opacity', o.opacity);
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var o = $(this).data('draggable').options;
|
||||||
|
if(o._opacity) $(ui.helper).css('opacity', o._opacity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "scroll", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var i = $(this).data("draggable");
|
||||||
|
if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
|
||||||
|
},
|
||||||
|
drag: function(event, ui) {
|
||||||
|
|
||||||
|
var i = $(this).data("draggable"), o = i.options, scrolled = false;
|
||||||
|
|
||||||
|
if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'x') {
|
||||||
|
if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
|
||||||
|
else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'y') {
|
||||||
|
if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
|
||||||
|
else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'x') {
|
||||||
|
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
||||||
|
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'y') {
|
||||||
|
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
||||||
|
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
|
||||||
|
$.ui.ddmanager.prepareOffsets(i, event);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "snap", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
|
||||||
|
var i = $(this).data("draggable"), o = i.options;
|
||||||
|
i.snapElements = [];
|
||||||
|
|
||||||
|
$(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
|
||||||
|
var $t = $(this); var $o = $t.offset();
|
||||||
|
if(this != i.element[0]) i.snapElements.push({
|
||||||
|
item: this,
|
||||||
|
width: $t.outerWidth(), height: $t.outerHeight(),
|
||||||
|
top: $o.top, left: $o.left
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
drag: function(event, ui) {
|
||||||
|
|
||||||
|
var inst = $(this).data("draggable"), o = inst.options;
|
||||||
|
var d = o.snapTolerance;
|
||||||
|
|
||||||
|
var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
|
||||||
|
y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
|
||||||
|
|
||||||
|
for (var i = inst.snapElements.length - 1; i >= 0; i--){
|
||||||
|
|
||||||
|
var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
|
||||||
|
t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
|
||||||
|
|
||||||
|
//Yes, I know, this is insane ;)
|
||||||
|
if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
|
||||||
|
if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
|
||||||
|
inst.snapElements[i].snapping = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o.snapMode != 'inner') {
|
||||||
|
var ts = Math.abs(t - y2) <= d;
|
||||||
|
var bs = Math.abs(b - y1) <= d;
|
||||||
|
var ls = Math.abs(l - x2) <= d;
|
||||||
|
var rs = Math.abs(r - x1) <= d;
|
||||||
|
if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
|
||||||
|
if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
|
||||||
|
if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
|
||||||
|
if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = (ts || bs || ls || rs);
|
||||||
|
|
||||||
|
if(o.snapMode != 'outer') {
|
||||||
|
var ts = Math.abs(t - y1) <= d;
|
||||||
|
var bs = Math.abs(b - y2) <= d;
|
||||||
|
var ls = Math.abs(l - x1) <= d;
|
||||||
|
var rs = Math.abs(r - x2) <= d;
|
||||||
|
if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
|
||||||
|
if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
|
||||||
|
if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
|
||||||
|
if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
|
||||||
|
(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
|
||||||
|
inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "stack", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
|
||||||
|
var o = $(this).data("draggable").options;
|
||||||
|
|
||||||
|
var group = $.makeArray($(o.stack)).sort(function(a,b) {
|
||||||
|
return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
|
||||||
|
});
|
||||||
|
if (!group.length) { return; }
|
||||||
|
|
||||||
|
var min = parseInt(group[0].style.zIndex) || 0;
|
||||||
|
$(group).each(function(i) {
|
||||||
|
this.style.zIndex = min + i;
|
||||||
|
});
|
||||||
|
|
||||||
|
this[0].style.zIndex = min + group.length;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "zIndex", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var t = $(ui.helper), o = $(this).data("draggable").options;
|
||||||
|
if(t.css("zIndex")) o._zIndex = t.css("zIndex");
|
||||||
|
t.css('zIndex', o.zIndex);
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var o = $(this).data("draggable").options;
|
||||||
|
if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
/*
|
||||||
|
* jQuery UI Droppable 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Droppables
|
||||||
|
*
|
||||||
|
* Depends:
|
||||||
|
* jquery.ui.core.js
|
||||||
|
* jquery.ui.widget.js
|
||||||
|
* jquery.ui.mouse.js
|
||||||
|
* jquery.ui.draggable.js
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
$.widget("ui.droppable", {
|
||||||
|
widgetEventPrefix: "drop",
|
||||||
|
options: {
|
||||||
|
accept: '*',
|
||||||
|
activeClass: false,
|
||||||
|
addClasses: true,
|
||||||
|
greedy: false,
|
||||||
|
hoverClass: false,
|
||||||
|
scope: 'default',
|
||||||
|
tolerance: 'intersect'
|
||||||
|
},
|
||||||
|
_create: function() {
|
||||||
|
|
||||||
|
var o = this.options, accept = o.accept;
|
||||||
|
this.isover = 0; this.isout = 1;
|
||||||
|
|
||||||
|
this.accept = $.isFunction(accept) ? accept : function(d) {
|
||||||
|
return d.is(accept);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Store the droppable's proportions
|
||||||
|
this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
|
||||||
|
|
||||||
|
// Add the reference and positions to the manager
|
||||||
|
$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
|
||||||
|
$.ui.ddmanager.droppables[o.scope].push(this);
|
||||||
|
|
||||||
|
(o.addClasses && this.element.addClass("ui-droppable"));
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
var drop = $.ui.ddmanager.droppables[this.options.scope];
|
||||||
|
for ( var i = 0; i < drop.length; i++ )
|
||||||
|
if ( drop[i] == this )
|
||||||
|
drop.splice(i, 1);
|
||||||
|
|
||||||
|
this.element
|
||||||
|
.removeClass("ui-droppable ui-droppable-disabled")
|
||||||
|
.removeData("droppable")
|
||||||
|
.unbind(".droppable");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_setOption: function(key, value) {
|
||||||
|
|
||||||
|
if(key == 'accept') {
|
||||||
|
this.accept = $.isFunction(value) ? value : function(d) {
|
||||||
|
return d.is(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$.Widget.prototype._setOption.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
_activate: function(event) {
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if(this.options.activeClass) this.element.addClass(this.options.activeClass);
|
||||||
|
(draggable && this._trigger('activate', event, this.ui(draggable)));
|
||||||
|
},
|
||||||
|
|
||||||
|
_deactivate: function(event) {
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
|
||||||
|
(draggable && this._trigger('deactivate', event, this.ui(draggable)));
|
||||||
|
},
|
||||||
|
|
||||||
|
_over: function(event) {
|
||||||
|
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
|
||||||
|
|
||||||
|
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
|
||||||
|
this._trigger('over', event, this.ui(draggable));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_out: function(event) {
|
||||||
|
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
|
||||||
|
|
||||||
|
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
|
||||||
|
this._trigger('out', event, this.ui(draggable));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_drop: function(event,custom) {
|
||||||
|
|
||||||
|
var draggable = custom || $.ui.ddmanager.current;
|
||||||
|
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
|
||||||
|
|
||||||
|
var childrenIntersection = false;
|
||||||
|
this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
|
||||||
|
var inst = $.data(this, 'droppable');
|
||||||
|
if(
|
||||||
|
inst.options.greedy
|
||||||
|
&& !inst.options.disabled
|
||||||
|
&& inst.options.scope == draggable.options.scope
|
||||||
|
&& inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
|
||||||
|
&& $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
|
||||||
|
) { childrenIntersection = true; return false; }
|
||||||
|
});
|
||||||
|
if(childrenIntersection) return false;
|
||||||
|
|
||||||
|
if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
|
||||||
|
if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
|
||||||
|
this._trigger('drop', event, this.ui(draggable));
|
||||||
|
return this.element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
ui: function(c) {
|
||||||
|
return {
|
||||||
|
draggable: (c.currentItem || c.element),
|
||||||
|
helper: c.helper,
|
||||||
|
position: c.position,
|
||||||
|
offset: c.positionAbs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$.extend($.ui.droppable, {
|
||||||
|
version: "1.8.16"
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.intersect = function(draggable, droppable, toleranceMode) {
|
||||||
|
|
||||||
|
if (!droppable.offset) return false;
|
||||||
|
|
||||||
|
var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
|
||||||
|
y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
|
||||||
|
var l = droppable.offset.left, r = l + droppable.proportions.width,
|
||||||
|
t = droppable.offset.top, b = t + droppable.proportions.height;
|
||||||
|
|
||||||
|
switch (toleranceMode) {
|
||||||
|
case 'fit':
|
||||||
|
return (l <= x1 && x2 <= r
|
||||||
|
&& t <= y1 && y2 <= b);
|
||||||
|
break;
|
||||||
|
case 'intersect':
|
||||||
|
return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
|
||||||
|
&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
|
||||||
|
&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
|
||||||
|
&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
|
||||||
|
break;
|
||||||
|
case 'pointer':
|
||||||
|
var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
|
||||||
|
draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
|
||||||
|
isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
|
||||||
|
return isOver;
|
||||||
|
break;
|
||||||
|
case 'touch':
|
||||||
|
return (
|
||||||
|
(y1 >= t && y1 <= b) || // Top edge touching
|
||||||
|
(y2 >= t && y2 <= b) || // Bottom edge touching
|
||||||
|
(y1 < t && y2 > b) // Surrounded vertically
|
||||||
|
) && (
|
||||||
|
(x1 >= l && x1 <= r) || // Left edge touching
|
||||||
|
(x2 >= l && x2 <= r) || // Right edge touching
|
||||||
|
(x1 < l && x2 > r) // Surrounded horizontally
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
This manager tracks offsets of draggables and droppables
|
||||||
|
*/
|
||||||
|
$.ui.ddmanager = {
|
||||||
|
current: null,
|
||||||
|
droppables: { 'default': [] },
|
||||||
|
prepareOffsets: function(t, event) {
|
||||||
|
|
||||||
|
var m = $.ui.ddmanager.droppables[t.options.scope] || [];
|
||||||
|
var type = event ? event.type : null; // workaround for #2317
|
||||||
|
var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
|
||||||
|
|
||||||
|
droppablesLoop: for (var i = 0; i < m.length; i++) {
|
||||||
|
|
||||||
|
if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
|
||||||
|
for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
|
||||||
|
m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
|
||||||
|
|
||||||
|
if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
|
||||||
|
|
||||||
|
m[i].offset = m[i].element.offset();
|
||||||
|
m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
drop: function(draggable, event) {
|
||||||
|
|
||||||
|
var dropped = false;
|
||||||
|
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
|
||||||
|
|
||||||
|
if(!this.options) return;
|
||||||
|
if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
|
||||||
|
dropped = dropped || this._drop.call(this, event);
|
||||||
|
|
||||||
|
if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
this.isout = 1; this.isover = 0;
|
||||||
|
this._deactivate.call(this, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return dropped;
|
||||||
|
|
||||||
|
},
|
||||||
|
dragStart: function( draggable, event ) {
|
||||||
|
//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
|
||||||
|
draggable.element.parents( ":not(body,html)" ).bind( "scroll.droppable", function() {
|
||||||
|
if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
|
||||||
|
});
|
||||||
|
},
|
||||||
|
drag: function(draggable, event) {
|
||||||
|
|
||||||
|
//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
|
||||||
|
if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
|
||||||
|
|
||||||
|
//Run through all droppables and check their positions based on specific tolerance options
|
||||||
|
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
|
||||||
|
|
||||||
|
if(this.options.disabled || this.greedyChild || !this.visible) return;
|
||||||
|
var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
|
||||||
|
|
||||||
|
var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
|
||||||
|
if(!c) return;
|
||||||
|
|
||||||
|
var parentInstance;
|
||||||
|
if (this.options.greedy) {
|
||||||
|
var parent = this.element.parents(':data(droppable):eq(0)');
|
||||||
|
if (parent.length) {
|
||||||
|
parentInstance = $.data(parent[0], 'droppable');
|
||||||
|
parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we just moved into a greedy child
|
||||||
|
if (parentInstance && c == 'isover') {
|
||||||
|
parentInstance['isover'] = 0;
|
||||||
|
parentInstance['isout'] = 1;
|
||||||
|
parentInstance._out.call(parentInstance, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
|
||||||
|
this[c == "isover" ? "_over" : "_out"].call(this, event);
|
||||||
|
|
||||||
|
// we just moved out of a greedy child
|
||||||
|
if (parentInstance && c == 'isout') {
|
||||||
|
parentInstance['isout'] = 0;
|
||||||
|
parentInstance['isover'] = 1;
|
||||||
|
parentInstance._over.call(parentInstance, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
dragStop: function( draggable, event ) {
|
||||||
|
draggable.element.parents( ":not(body,html)" ).unbind( "scroll.droppable" );
|
||||||
|
//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
|
||||||
|
if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*!
|
||||||
|
* jQuery UI Mouse 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Mouse
|
||||||
|
*
|
||||||
|
* Depends:
|
||||||
|
* jquery.ui.widget.js
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
var mouseHandled = false;
|
||||||
|
$( document ).mouseup( function( e ) {
|
||||||
|
mouseHandled = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$.widget("ui.mouse", {
|
||||||
|
options: {
|
||||||
|
cancel: ':input,option',
|
||||||
|
distance: 1,
|
||||||
|
delay: 0
|
||||||
|
},
|
||||||
|
_mouseInit: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.element
|
||||||
|
.bind('mousedown.'+this.widgetName, function(event) {
|
||||||
|
return self._mouseDown(event);
|
||||||
|
})
|
||||||
|
.bind('click.'+this.widgetName, function(event) {
|
||||||
|
if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) {
|
||||||
|
$.removeData(event.target, self.widgetName + '.preventClickEvent');
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.started = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: make sure destroying one instance of mouse doesn't mess with
|
||||||
|
// other instances of mouse
|
||||||
|
_mouseDestroy: function() {
|
||||||
|
this.element.unbind('.'+this.widgetName);
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDown: function(event) {
|
||||||
|
// don't let more than one widget handle mouseStart
|
||||||
|
if( mouseHandled ) { return };
|
||||||
|
|
||||||
|
// we may have missed mouseup (out of window)
|
||||||
|
(this._mouseStarted && this._mouseUp(event));
|
||||||
|
|
||||||
|
this._mouseDownEvent = event;
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
btnIsLeft = (event.which == 1),
|
||||||
|
// event.target.nodeName works around a bug in IE 8 with
|
||||||
|
// disabled inputs (#7620)
|
||||||
|
elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
|
||||||
|
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mouseDelayMet = !this.options.delay;
|
||||||
|
if (!this.mouseDelayMet) {
|
||||||
|
this._mouseDelayTimer = setTimeout(function() {
|
||||||
|
self.mouseDelayMet = true;
|
||||||
|
}, this.options.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
||||||
|
this._mouseStarted = (this._mouseStart(event) !== false);
|
||||||
|
if (!this._mouseStarted) {
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click event may never have fired (Gecko & Opera)
|
||||||
|
if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
|
||||||
|
$.removeData(event.target, this.widgetName + '.preventClickEvent');
|
||||||
|
}
|
||||||
|
|
||||||
|
// these delegates are required to keep context
|
||||||
|
this._mouseMoveDelegate = function(event) {
|
||||||
|
return self._mouseMove(event);
|
||||||
|
};
|
||||||
|
this._mouseUpDelegate = function(event) {
|
||||||
|
return self._mouseUp(event);
|
||||||
|
};
|
||||||
|
$(document)
|
||||||
|
.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
|
||||||
|
.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
mouseHandled = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseMove: function(event) {
|
||||||
|
// IE mouseup check - mouseup happened when mouse was out of window
|
||||||
|
if ($.browser.msie && !(document.documentMode >= 9) && !event.button) {
|
||||||
|
return this._mouseUp(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mouseStarted) {
|
||||||
|
this._mouseDrag(event);
|
||||||
|
return event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
||||||
|
this._mouseStarted =
|
||||||
|
(this._mouseStart(this._mouseDownEvent, event) !== false);
|
||||||
|
(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this._mouseStarted;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseUp: function(event) {
|
||||||
|
$(document)
|
||||||
|
.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
|
||||||
|
.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
|
||||||
|
|
||||||
|
if (this._mouseStarted) {
|
||||||
|
this._mouseStarted = false;
|
||||||
|
|
||||||
|
if (event.target == this._mouseDownEvent.target) {
|
||||||
|
$.data(event.target, this.widgetName + '.preventClickEvent', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mouseStop(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDistanceMet: function(event) {
|
||||||
|
return (Math.max(
|
||||||
|
Math.abs(this._mouseDownEvent.pageX - event.pageX),
|
||||||
|
Math.abs(this._mouseDownEvent.pageY - event.pageY)
|
||||||
|
) >= this.options.distance
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDelayMet: function(event) {
|
||||||
|
return this.mouseDelayMet;
|
||||||
|
},
|
||||||
|
|
||||||
|
// These are placeholder methods, to be overriden by extending plugin
|
||||||
|
_mouseStart: function(event) {},
|
||||||
|
_mouseDrag: function(event) {},
|
||||||
|
_mouseStop: function(event) {},
|
||||||
|
_mouseCapture: function(event) { return true; }
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,268 @@
|
||||||
|
/*!
|
||||||
|
* jQuery UI Widget 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Widget
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
// jQuery 1.4+
|
||||||
|
if ( $.cleanData ) {
|
||||||
|
var _cleanData = $.cleanData;
|
||||||
|
$.cleanData = function( elems ) {
|
||||||
|
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
|
||||||
|
try {
|
||||||
|
$( elem ).triggerHandler( "remove" );
|
||||||
|
// http://bugs.jquery.com/ticket/8235
|
||||||
|
} catch( e ) {}
|
||||||
|
}
|
||||||
|
_cleanData( elems );
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var _remove = $.fn.remove;
|
||||||
|
$.fn.remove = function( selector, keepData ) {
|
||||||
|
return this.each(function() {
|
||||||
|
if ( !keepData ) {
|
||||||
|
if ( !selector || $.filter( selector, [ this ] ).length ) {
|
||||||
|
$( "*", this ).add( [ this ] ).each(function() {
|
||||||
|
try {
|
||||||
|
$( this ).triggerHandler( "remove" );
|
||||||
|
// http://bugs.jquery.com/ticket/8235
|
||||||
|
} catch( e ) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _remove.call( $(this), selector, keepData );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$.widget = function( name, base, prototype ) {
|
||||||
|
var namespace = name.split( "." )[ 0 ],
|
||||||
|
fullName;
|
||||||
|
name = name.split( "." )[ 1 ];
|
||||||
|
fullName = namespace + "-" + name;
|
||||||
|
|
||||||
|
if ( !prototype ) {
|
||||||
|
prototype = base;
|
||||||
|
base = $.Widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create selector for plugin
|
||||||
|
$.expr[ ":" ][ fullName ] = function( elem ) {
|
||||||
|
return !!$.data( elem, name );
|
||||||
|
};
|
||||||
|
|
||||||
|
$[ namespace ] = $[ namespace ] || {};
|
||||||
|
$[ namespace ][ name ] = function( options, element ) {
|
||||||
|
// allow instantiation without initializing for simple inheritance
|
||||||
|
if ( arguments.length ) {
|
||||||
|
this._createWidget( options, element );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var basePrototype = new base();
|
||||||
|
// we need to make the options hash a property directly on the new instance
|
||||||
|
// otherwise we'll modify the options hash on the prototype that we're
|
||||||
|
// inheriting from
|
||||||
|
// $.each( basePrototype, function( key, val ) {
|
||||||
|
// if ( $.isPlainObject(val) ) {
|
||||||
|
// basePrototype[ key ] = $.extend( {}, val );
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
basePrototype.options = $.extend( true, {}, basePrototype.options );
|
||||||
|
$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
|
||||||
|
namespace: namespace,
|
||||||
|
widgetName: name,
|
||||||
|
widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
|
||||||
|
widgetBaseClass: fullName
|
||||||
|
}, prototype );
|
||||||
|
|
||||||
|
$.widget.bridge( name, $[ namespace ][ name ] );
|
||||||
|
};
|
||||||
|
|
||||||
|
$.widget.bridge = function( name, object ) {
|
||||||
|
$.fn[ name ] = function( options ) {
|
||||||
|
var isMethodCall = typeof options === "string",
|
||||||
|
args = Array.prototype.slice.call( arguments, 1 ),
|
||||||
|
returnValue = this;
|
||||||
|
|
||||||
|
// allow multiple hashes to be passed on init
|
||||||
|
options = !isMethodCall && args.length ?
|
||||||
|
$.extend.apply( null, [ true, options ].concat(args) ) :
|
||||||
|
options;
|
||||||
|
|
||||||
|
// prevent calls to internal methods
|
||||||
|
if ( isMethodCall && options.charAt( 0 ) === "_" ) {
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isMethodCall ) {
|
||||||
|
this.each(function() {
|
||||||
|
var instance = $.data( this, name ),
|
||||||
|
methodValue = instance && $.isFunction( instance[options] ) ?
|
||||||
|
instance[ options ].apply( instance, args ) :
|
||||||
|
instance;
|
||||||
|
// TODO: add this back in 1.9 and use $.error() (see #5972)
|
||||||
|
// if ( !instance ) {
|
||||||
|
// throw "cannot call methods on " + name + " prior to initialization; " +
|
||||||
|
// "attempted to call method '" + options + "'";
|
||||||
|
// }
|
||||||
|
// if ( !$.isFunction( instance[options] ) ) {
|
||||||
|
// throw "no such method '" + options + "' for " + name + " widget instance";
|
||||||
|
// }
|
||||||
|
// var methodValue = instance[ options ].apply( instance, args );
|
||||||
|
if ( methodValue !== instance && methodValue !== undefined ) {
|
||||||
|
returnValue = methodValue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.each(function() {
|
||||||
|
var instance = $.data( this, name );
|
||||||
|
if ( instance ) {
|
||||||
|
instance.option( options || {} )._init();
|
||||||
|
} else {
|
||||||
|
$.data( this, name, new object( options, this ) );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Widget = function( options, element ) {
|
||||||
|
// allow instantiation without initializing for simple inheritance
|
||||||
|
if ( arguments.length ) {
|
||||||
|
this._createWidget( options, element );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Widget.prototype = {
|
||||||
|
widgetName: "widget",
|
||||||
|
widgetEventPrefix: "",
|
||||||
|
options: {
|
||||||
|
disabled: false
|
||||||
|
},
|
||||||
|
_createWidget: function( options, element ) {
|
||||||
|
// $.widget.bridge stores the plugin instance, but we do it anyway
|
||||||
|
// so that it's stored even before the _create function runs
|
||||||
|
$.data( element, this.widgetName, this );
|
||||||
|
this.element = $( element );
|
||||||
|
this.options = $.extend( true, {},
|
||||||
|
this.options,
|
||||||
|
this._getCreateOptions(),
|
||||||
|
options );
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.element.bind( "remove." + this.widgetName, function() {
|
||||||
|
self.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._create();
|
||||||
|
this._trigger( "create" );
|
||||||
|
this._init();
|
||||||
|
},
|
||||||
|
_getCreateOptions: function() {
|
||||||
|
return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
|
||||||
|
},
|
||||||
|
_create: function() {},
|
||||||
|
_init: function() {},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this.element
|
||||||
|
.unbind( "." + this.widgetName )
|
||||||
|
.removeData( this.widgetName );
|
||||||
|
this.widget()
|
||||||
|
.unbind( "." + this.widgetName )
|
||||||
|
.removeAttr( "aria-disabled" )
|
||||||
|
.removeClass(
|
||||||
|
this.widgetBaseClass + "-disabled " +
|
||||||
|
"ui-state-disabled" );
|
||||||
|
},
|
||||||
|
|
||||||
|
widget: function() {
|
||||||
|
return this.element;
|
||||||
|
},
|
||||||
|
|
||||||
|
option: function( key, value ) {
|
||||||
|
var options = key;
|
||||||
|
|
||||||
|
if ( arguments.length === 0 ) {
|
||||||
|
// don't return a reference to the internal hash
|
||||||
|
return $.extend( {}, this.options );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof key === "string" ) {
|
||||||
|
if ( value === undefined ) {
|
||||||
|
return this.options[ key ];
|
||||||
|
}
|
||||||
|
options = {};
|
||||||
|
options[ key ] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setOptions( options );
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
_setOptions: function( options ) {
|
||||||
|
var self = this;
|
||||||
|
$.each( options, function( key, value ) {
|
||||||
|
self._setOption( key, value );
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
_setOption: function( key, value ) {
|
||||||
|
this.options[ key ] = value;
|
||||||
|
|
||||||
|
if ( key === "disabled" ) {
|
||||||
|
this.widget()
|
||||||
|
[ value ? "addClass" : "removeClass"](
|
||||||
|
this.widgetBaseClass + "-disabled" + " " +
|
||||||
|
"ui-state-disabled" )
|
||||||
|
.attr( "aria-disabled", value );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function() {
|
||||||
|
return this._setOption( "disabled", false );
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
return this._setOption( "disabled", true );
|
||||||
|
},
|
||||||
|
|
||||||
|
_trigger: function( type, event, data ) {
|
||||||
|
var callback = this.options[ type ];
|
||||||
|
|
||||||
|
event = $.Event( event );
|
||||||
|
event.type = ( type === this.widgetEventPrefix ?
|
||||||
|
type :
|
||||||
|
this.widgetEventPrefix + type ).toLowerCase();
|
||||||
|
data = data || {};
|
||||||
|
|
||||||
|
// copy original event properties over to the new event
|
||||||
|
// this would happen if we could call $.event.fix instead of $.Event
|
||||||
|
// but we don't have a way to force an event to be fixed multiple times
|
||||||
|
if ( event.originalEvent ) {
|
||||||
|
for ( var i = $.event.props.length, prop; i; ) {
|
||||||
|
prop = $.event.props[ --i ];
|
||||||
|
event[ prop ] = event.originalEvent[ prop ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element.trigger( event, data );
|
||||||
|
|
||||||
|
return !( $.isFunction(callback) &&
|
||||||
|
callback.call( this.element[0], event, data ) === false ||
|
||||||
|
event.isDefaultPrevented() );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})( jQuery );
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Sortable(models.Model):
|
||||||
|
order = models.PositiveIntegerField(editable=False, default=1, db_index=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ['order', 'id']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_sortable(cls):
|
||||||
|
return True if cls.objects.count() > 1 else False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def model_type_id(cls):
|
||||||
|
return ContentType.objects.get_for_model(cls).id
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.id:
|
||||||
|
self.order = self.__class__.objects.count() + 1
|
||||||
|
super(Sortable, self).save(*args, **kwargs)
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "admin/change_form.html" %}
|
||||||
|
{% load i18n admin_modify adminmedia %}
|
||||||
|
{% load url from future %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% url 'admin:jsi18n' as jsi18nurl %}
|
||||||
|
|
||||||
|
{% if has_sortable_tabular_inlines %}
|
||||||
|
{% include 'adminsortable/shared/javascript_includes.html' %}
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/admin.sortable.tabular.inlines.js"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrastyle %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
|
||||||
|
{% if has_sortable_tabular_inlines %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}adminsortable/css/admin.sortable.tabular.inline.css" />
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
{% extends 'admin/change_list.html' %}
|
||||||
|
{% load adminmedia admin_list i18n adminsortable_tags %}
|
||||||
|
|
||||||
|
{% block extrastyle %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" />
|
||||||
|
<link rel="stylesheet" href="{{ STATIC_URL }}adminsortable/css/admin.sortable.css" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{% include 'adminsortable/shared/javascript_includes.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="../../../">
|
||||||
|
{% trans "Home" %}
|
||||||
|
</a>
|
||||||
|
›
|
||||||
|
<a href="../../">
|
||||||
|
{{ app_label|capfirst }}
|
||||||
|
</a>
|
||||||
|
›
|
||||||
|
Reorder
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_title %}
|
||||||
|
<h1>Drag and drop {% if sort_type %}{{ sort_type }} {% endif %}{{ opts.verbose_name_plural|capfirst }} to change their order.</h1>
|
||||||
|
{% if sortable_by_class.is_sortable %}
|
||||||
|
<p>You may also drag and drop {{ sortable_by_class_display_name }} to change thier order.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="content-main">
|
||||||
|
{% block object-tools %}
|
||||||
|
<ul class="object-tools">
|
||||||
|
<li>
|
||||||
|
<a href="../../">Return to {{ opts.verbose_name_plural|capfirst }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
{% if objects %}
|
||||||
|
<div id="sortable">
|
||||||
|
{% if group_expression %}
|
||||||
|
{% render_nested_sortable_objects objects group_expression %}
|
||||||
|
{% else %}
|
||||||
|
{% render_sortable_objects objects %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends 'admin/change_list.html' %}
|
||||||
|
|
||||||
|
{% block object-tools-items %}
|
||||||
|
<li>
|
||||||
|
<a href="./sort/">Change Order</a>
|
||||||
|
</li>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
{% load i18n adminmedia admin_modify %}
|
||||||
|
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
||||||
|
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
|
||||||
|
{{ inline_admin_formset.formset.management_form }}
|
||||||
|
<fieldset class="module">
|
||||||
|
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {% if inline_admin_formset.opts.is_sortable %} - drag and drop to change order{% endif %}</h2>
|
||||||
|
{{ inline_admin_formset.formset.non_form_errors }}
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
{% for field in inline_admin_formset.fields %}
|
||||||
|
{% if not field.widget.is_hidden %}
|
||||||
|
<th{% if forloop.first %} colspan="2"{% endif %}{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
|
||||||
|
</tr></thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for inline_admin_form in inline_admin_formset %}
|
||||||
|
{% if inline_admin_form.form.non_field_errors %}
|
||||||
|
<tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
|
||||||
|
id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
|
||||||
|
<td class="original">
|
||||||
|
|
||||||
|
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
|
||||||
|
{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
|
||||||
|
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
|
||||||
|
</p>{% endif %}
|
||||||
|
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
||||||
|
{{ inline_admin_form.fk_field.field }}
|
||||||
|
{% spaceless %}
|
||||||
|
{% for fieldset in inline_admin_form %}
|
||||||
|
{% for line in fieldset %}
|
||||||
|
{% for field in line %}
|
||||||
|
{% if field.is_hidden %} {{ field.field }} {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endspaceless %}
|
||||||
|
{% if inline_admin_form.original %}
|
||||||
|
<input type="hidden" name="admin_sorting_url" value="{% url admin:admin_do_sorting inline_admin_form.original.model_type_id %}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% for fieldset in inline_admin_form %}
|
||||||
|
{% for line in fieldset %}
|
||||||
|
{% for field in line %}
|
||||||
|
<td class="{{ field.field.name }}">
|
||||||
|
{% if field.is_readonly %}
|
||||||
|
<p>{{ field.contents }}</p>
|
||||||
|
{% else %}
|
||||||
|
{{ field.field.errors.as_ul }}
|
||||||
|
{{ field.field }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if inline_admin_formset.formset.can_delete %}
|
||||||
|
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function($) {
|
||||||
|
$(document).ready(function($) {
|
||||||
|
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
|
||||||
|
var alternatingRows = function(row) {
|
||||||
|
$(rows).not(".add-row").removeClass("row1 row2")
|
||||||
|
.filter(":even").addClass("row1").end()
|
||||||
|
.filter(rows + ":odd").addClass("row2");
|
||||||
|
}
|
||||||
|
var reinitDateTimeShortCuts = function() {
|
||||||
|
// Reinitialize the calendar and clock widgets by force
|
||||||
|
if (typeof DateTimeShortcuts != "undefined") {
|
||||||
|
$(".datetimeshortcuts").remove();
|
||||||
|
DateTimeShortcuts.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var updateSelectFilter = function() {
|
||||||
|
// If any SelectFilter widgets are a part of the new form,
|
||||||
|
// instantiate a new SelectFilter instance for it.
|
||||||
|
if (typeof SelectFilter != "undefined"){
|
||||||
|
$(".selectfilter").each(function(index, value){
|
||||||
|
var namearr = value.name.split('-');
|
||||||
|
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
|
||||||
|
});
|
||||||
|
$(".selectfilterstacked").each(function(index, value){
|
||||||
|
var namearr = value.name.split('-');
|
||||||
|
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var initPrepopulatedFields = function(row) {
|
||||||
|
row.find('.prepopulated_field').each(function() {
|
||||||
|
var field = $(this);
|
||||||
|
var input = field.find('input, select, textarea');
|
||||||
|
var dependency_list = input.data('dependency_list') || [];
|
||||||
|
var dependencies = [];
|
||||||
|
$.each(dependency_list, function(i, field_name) {
|
||||||
|
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
|
||||||
|
});
|
||||||
|
if (dependencies.length) {
|
||||||
|
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$(rows).formset({
|
||||||
|
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||||
|
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||||
|
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
||||||
|
deleteCssClass: "inline-deletelink",
|
||||||
|
deleteText: "{% trans "Remove" %}",
|
||||||
|
emptyCssClass: "empty-form",
|
||||||
|
removed: alternatingRows,
|
||||||
|
added: (function(row) {
|
||||||
|
initPrepopulatedFields(row);
|
||||||
|
reinitDateTimeShortCuts();
|
||||||
|
updateSelectFilter();
|
||||||
|
alternatingRows(row);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})(django.jQuery);
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/jquery.ui.core.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/jquery.ui.widget.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/jquery.ui.mouse.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/jquery.ui.draggable.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/jquery.ui.droppable.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/jquery.ui.sortable.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ STATIC_URL }}adminsortable/js/admin.sortable.js"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% load adminsortable_tags %}
|
||||||
|
{% for object in list_objects %}
|
||||||
|
<li>
|
||||||
|
{% render_object_rep object %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% load django_template_additions adminsortable_tags %}
|
||||||
|
{% dynamic_regroup objects by group_expression as regrouped_objects %}
|
||||||
|
{% if regrouped_objects %}
|
||||||
|
|
||||||
|
<ul {% if sortable_by_class_is_sortable %}class="sortable"{% endif %}>
|
||||||
|
{% for regrouped_object in regrouped_objects %}
|
||||||
|
<li>
|
||||||
|
{% with object=regrouped_object.grouper %}
|
||||||
|
{% render_object_rep object %}
|
||||||
|
{% endwith %}
|
||||||
|
{% if regrouped_object.list %}
|
||||||
|
<ul {% if regrouped_object.grouper.is_sortable %}class="sortable"{% endif %}>
|
||||||
|
{% render_list_items regrouped_object.list %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<form>
|
||||||
|
<input name="pk" type="hidden" value="{{ object.pk }}" />
|
||||||
|
</form>
|
||||||
|
<a href="{% url admin:admin_do_sorting object.model_type_id %}" class="admin_sorting_url">{{ object }}</a>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% load adminsortable_tags %}
|
||||||
|
|
||||||
|
{% if objects %}
|
||||||
|
<ul class="sortable single">
|
||||||
|
{% render_list_items objects %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('adminsortable/shared/objects.html', takes_context=True)
|
||||||
|
def render_sortable_objects(context, objects):
|
||||||
|
return {'objects' : objects}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('adminsortable/shared/nested_objects.html', takes_context=True)
|
||||||
|
def render_nested_sortable_objects(context, objects, group_expression):
|
||||||
|
group_expression = context.get('group_expression')
|
||||||
|
sortable_on_class = context.get('sortable_on_class')
|
||||||
|
return {'objects' : objects, 'group_expression' : group_expression,
|
||||||
|
'sortable_on_class' : sortable_on_class,
|
||||||
|
'sortable_by_class_is_sortable' : context.get('sortable_by_class_is_sortable')}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('adminsortable/shared/list_items.html', takes_context=True)
|
||||||
|
def render_list_items(context, list_objects):
|
||||||
|
return {'list_objects' : list_objects}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('adminsortable/shared/object_rep.html', takes_context=True)
|
||||||
|
def render_object_rep(context, object):
|
||||||
|
print object
|
||||||
|
return {'object' : object}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
from itertools import groupby
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicRegroupNode(template.Node):
|
||||||
|
"""
|
||||||
|
Extends Django's regroup tag to accept a variable instead of a string literal
|
||||||
|
for the property you want to regroup on
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, target, parser, expression, var_name):
|
||||||
|
self.target = target
|
||||||
|
self.expression = template.Variable(expression)
|
||||||
|
self.var_name = var_name
|
||||||
|
self.parser = parser
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
obj_list = self.target.resolve(context, True)
|
||||||
|
if obj_list == None:
|
||||||
|
# target variable wasn't found in context; fail silently.
|
||||||
|
context[self.var_name] = []
|
||||||
|
return ''
|
||||||
|
# List of dictionaries in the format:
|
||||||
|
# {'grouper': 'key', 'list': [list of contents]}.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Try to resolve the filter expression from the template context.
|
||||||
|
If the variable doesn't exist, accept the value that passed to the
|
||||||
|
template tag and convert it to a string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
exp = self.expression.resolve(context)
|
||||||
|
except template.VariableDoesNotExist:
|
||||||
|
exp = str(self.expression)
|
||||||
|
|
||||||
|
filter_exp = self.parser.compile_filter(exp)
|
||||||
|
|
||||||
|
context[self.var_name] = [
|
||||||
|
{'grouper': key, 'list': list(val)}
|
||||||
|
for key, val in
|
||||||
|
groupby(obj_list, lambda v, f=filter_exp.resolve: f(v, True))
|
||||||
|
]
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def dynamic_regroup(parser, token):
|
||||||
|
firstbits = token.contents.split(None, 3)
|
||||||
|
if len(firstbits) != 4:
|
||||||
|
raise TemplateSyntaxError("'regroup' tag takes five arguments")
|
||||||
|
target = parser.compile_filter(firstbits[1])
|
||||||
|
if firstbits[2] != 'by':
|
||||||
|
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
|
||||||
|
lastbits_reversed = firstbits[3][::-1].split(None, 2)
|
||||||
|
if lastbits_reversed[1][::-1] != 'as':
|
||||||
|
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
|
||||||
|
" be 'as'")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Django expects the value of `expression` to be an attribute available on
|
||||||
|
your objects. The value you pass to the template tag gets converted into a
|
||||||
|
FilterExpression object from the literal.
|
||||||
|
|
||||||
|
Sometimes we need the attribute to group on to be dynamic. So, instead
|
||||||
|
of converting the value to a FilterExpression here, we're going to pass the
|
||||||
|
value as-is and convert it in the Node.
|
||||||
|
"""
|
||||||
|
expression = lastbits_reversed[2][::-1]
|
||||||
|
var_name = lastbits_reversed[0][::-1]
|
||||||
|
|
||||||
|
"""
|
||||||
|
We also need to hand the parser to the node in order to convert the value
|
||||||
|
for `expression` to a FilterExpression.
|
||||||
|
"""
|
||||||
|
return DynamicRegroupNode(target, parser, expression, var_name)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,18 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from adminsortable.admin import SortableAdmin, SortableTabularInline
|
||||||
|
from app.models import Category, Project, Credit
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Category, SortableAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
class CreditInline(SortableTabularInline):
|
||||||
|
model = Credit
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectAdmin(SortableAdmin):
|
||||||
|
inlines = [CreditInline]
|
||||||
|
list_display = ['__unicode__', 'category']
|
||||||
|
|
||||||
|
admin.site.register(Project, ProjectAdmin)
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,136 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "app.category",
|
||||||
|
"fields": {
|
||||||
|
"order": 2,
|
||||||
|
"title": "Category 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "app.category",
|
||||||
|
"fields": {
|
||||||
|
"order": 1,
|
||||||
|
"title": "Category 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 3,
|
||||||
|
"model": "app.category",
|
||||||
|
"fields": {
|
||||||
|
"order": 3,
|
||||||
|
"title": "Category 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "app.project",
|
||||||
|
"fields": {
|
||||||
|
"category": 1,
|
||||||
|
"description": "Sample category 1",
|
||||||
|
"order": 1,
|
||||||
|
"title": "Sample Project 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "app.project",
|
||||||
|
"fields": {
|
||||||
|
"category": 1,
|
||||||
|
"description": "Another sample project.",
|
||||||
|
"order": 2,
|
||||||
|
"title": "Sample Project 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 3,
|
||||||
|
"model": "app.project",
|
||||||
|
"fields": {
|
||||||
|
"category": 3,
|
||||||
|
"description": "Yest another sample project.",
|
||||||
|
"order": 3,
|
||||||
|
"title": "Sample Project 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 1,
|
||||||
|
"first_name": "Bob",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 2,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 1,
|
||||||
|
"first_name": "Sally",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 3,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 1,
|
||||||
|
"first_name": "Johnny",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 4,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 3,
|
||||||
|
"first_name": "Sally Ann",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 5,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 3,
|
||||||
|
"first_name": "George",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 6,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 2,
|
||||||
|
"first_name": "Bob",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 7,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 2,
|
||||||
|
"first_name": "George",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 8,
|
||||||
|
"model": "app.credit",
|
||||||
|
"fields": {
|
||||||
|
"project": 2,
|
||||||
|
"first_name": "Sally",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"order": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
|
||||||
|
# Adding model 'Category'
|
||||||
|
db.create_table('app_category', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
|
||||||
|
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
|
||||||
|
))
|
||||||
|
db.send_create_signal('app', ['Category'])
|
||||||
|
|
||||||
|
# Adding model 'Project'
|
||||||
|
db.create_table('app_project', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
|
||||||
|
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
|
||||||
|
('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Category'])),
|
||||||
|
('description', self.gf('django.db.models.fields.TextField')()),
|
||||||
|
))
|
||||||
|
db.send_create_signal('app', ['Project'])
|
||||||
|
|
||||||
|
# Adding model 'Credit'
|
||||||
|
db.create_table('app_credit', (
|
||||||
|
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||||
|
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
|
||||||
|
('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Project'])),
|
||||||
|
('first_name', self.gf('django.db.models.fields.CharField')(max_length=30)),
|
||||||
|
('last_name', self.gf('django.db.models.fields.CharField')(max_length=30)),
|
||||||
|
))
|
||||||
|
db.send_create_signal('app', ['Credit'])
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
|
||||||
|
# Deleting model 'Category'
|
||||||
|
db.delete_table('app_category')
|
||||||
|
|
||||||
|
# Deleting model 'Project'
|
||||||
|
db.delete_table('app_project')
|
||||||
|
|
||||||
|
# Deleting model 'Credit'
|
||||||
|
db.delete_table('app_credit')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'app.category': {
|
||||||
|
'Meta': {'ordering': "['order', 'id']", 'object_name': 'Category'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
|
||||||
|
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||||
|
},
|
||||||
|
'app.credit': {
|
||||||
|
'Meta': {'ordering': "['order', 'id']", 'object_name': 'Credit'},
|
||||||
|
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
|
||||||
|
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Project']"})
|
||||||
|
},
|
||||||
|
'app.project': {
|
||||||
|
'Meta': {'ordering': "['order', 'id']", 'object_name': 'Project'},
|
||||||
|
'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['app.Category']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
|
||||||
|
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['app']
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,48 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from adminsortable.models import Sortable
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleModel(models.Model):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
title = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
#a model that is sortable
|
||||||
|
class Category(SimpleModel, Sortable):
|
||||||
|
class Meta(Sortable.Meta):
|
||||||
|
"""
|
||||||
|
Classes that inherit from Sortable must define an inner
|
||||||
|
Meta class that inherits from Sortable.Meta or ordering
|
||||||
|
won't work as expected
|
||||||
|
"""
|
||||||
|
verbose_name_plural = 'Categories'
|
||||||
|
|
||||||
|
|
||||||
|
#a model that is sortable relative to a foreign key that is also sortable
|
||||||
|
class Project(SimpleModel, Sortable):
|
||||||
|
class Meta(Sortable.Meta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sortable_by(cls):
|
||||||
|
return Category, 'category'
|
||||||
|
|
||||||
|
category = models.ForeignKey(Category)
|
||||||
|
description = models.TextField()
|
||||||
|
|
||||||
|
|
||||||
|
#registered as an inline on project
|
||||||
|
class Credit(Sortable):
|
||||||
|
project = models.ForeignKey(Project)
|
||||||
|
first_name = models.CharField(max_length=30)
|
||||||
|
last_name = models.CharField(max_length=30)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s %s' % (self.first_name, self.last_name)
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,139 @@
|
||||||
|
import httplib
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.contrib.auth.forms import authenticate, UserCreationForm
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.middleware import csrf
|
||||||
|
from django.db import models
|
||||||
|
from django.template.defaultfilters import urlencode
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import Client, RequestFactory
|
||||||
|
|
||||||
|
from models import Sortable
|
||||||
|
from app.models import Category
|
||||||
|
|
||||||
|
|
||||||
|
class TestSortableModel(Sortable):
|
||||||
|
title = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class SortableTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.user_raw_password = 'admin'
|
||||||
|
self.user = User.objects.create_user('admin', 'admin@admin.com',
|
||||||
|
self.user_raw_password)
|
||||||
|
self.user.is_staff = True
|
||||||
|
self.user.is_superuser = True
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
def create_category(self, title='Category 1'):
|
||||||
|
category = Category.objects.create(title=title)
|
||||||
|
return category
|
||||||
|
|
||||||
|
def test_new_user_is_authenticated(self):
|
||||||
|
self.assertEqual(self.user.is_authenticated(), True, 'User is not authenticated')
|
||||||
|
|
||||||
|
def test_new_user_is_staff(self):
|
||||||
|
self.assertEqual(self.user.is_staff, True, 'User is not staff')
|
||||||
|
|
||||||
|
def test_is_not_sortable(self):
|
||||||
|
"""
|
||||||
|
A model should only become sortable if it has more than
|
||||||
|
record to sort.
|
||||||
|
"""
|
||||||
|
category = self.create_category()
|
||||||
|
self.assertFalse(Category.is_sortable(),
|
||||||
|
'Category only has one record. It should not be sortable.')
|
||||||
|
|
||||||
|
def test_is_sortable(self):
|
||||||
|
category1 = self.create_category()
|
||||||
|
category2 = self.create_category(title='Category 2')
|
||||||
|
self.assertTrue(Category.is_sortable(),
|
||||||
|
'Category has more than one record. It should be sortable.')
|
||||||
|
|
||||||
|
def test_save_order_incremented(self):
|
||||||
|
category1 = self.create_category()
|
||||||
|
self.assertEqual(category1.order, 1, 'Category 1 order should be 1.')
|
||||||
|
|
||||||
|
category2 = self.create_category(title='Category 2')
|
||||||
|
self.assertEqual(category2.order, 2, 'Category 2 order should be 2.')
|
||||||
|
|
||||||
|
def test_adminsortable_change_list_view(self):
|
||||||
|
self.client.login(username=self.user.username, password=self.user_raw_password)
|
||||||
|
response = self.client.get('/admin/app/category/sort/')
|
||||||
|
self.assertEquals(response.status_code, httplib.OK, 'Unable to reach sort view.')
|
||||||
|
|
||||||
|
def make_test_categories(self):
|
||||||
|
category1 = self.create_category()
|
||||||
|
category2 = self.create_category(title='Category 2')
|
||||||
|
category3 = self.create_category(title='Category 3')
|
||||||
|
return category1, category2, category3
|
||||||
|
|
||||||
|
def get_sorting_url(self):
|
||||||
|
return reverse('admin:admin_do_sorting', args=(),
|
||||||
|
kwargs={'model_type_id' : Category.model_type_id()})
|
||||||
|
|
||||||
|
def get_category_indexes(self, *categories):
|
||||||
|
return {'indexes' : ','.join([str(c.id) for c in categories])}
|
||||||
|
|
||||||
|
def test_adminsortable_changelist_templates(self):
|
||||||
|
logged_in = self.client.login(username=self.user.username, password=self.user_raw_password)
|
||||||
|
self.assertTrue(logged_in, 'User is not logged in')
|
||||||
|
|
||||||
|
response = self.client.get(reverse('admin:admin_sort'))
|
||||||
|
self.assertEqual(response.status_code, httplib.OK, u'Admin sort request failed.')
|
||||||
|
|
||||||
|
#assert adminsortable change list templates are used
|
||||||
|
template_names = [t.name for t in response.templates]
|
||||||
|
self.assertTrue('adminsortable/change_list.html' in template_names,
|
||||||
|
u'adminsortable/change_list.html was not rendered')
|
||||||
|
self.assertTrue('adminsortable/shared/javascript_includes.html' in template_names,
|
||||||
|
u'JavaScript includes for adminsortable change list were not rendered')
|
||||||
|
|
||||||
|
def test_adminsortable_change_list_sorting_fails_if_not_ajax(self):
|
||||||
|
logged_in = self.client.login(username=self.user.username, password=self.user_raw_password)
|
||||||
|
self.assertTrue(logged_in, 'User is not logged in')
|
||||||
|
|
||||||
|
category1, category2, category3 = self.make_test_categories()
|
||||||
|
#make a normal POST
|
||||||
|
response = self.client.post(self.get_sorting_url(),
|
||||||
|
data=self.get_category_indexes(category1, category2, category3))
|
||||||
|
content = json.loads(response.content)
|
||||||
|
self.assertFalse(content.get('objects_sorted'), u'Objects should not have been sorted. An ajax post is required.')
|
||||||
|
|
||||||
|
def test_adminsortable_change_list_sorting_successful(self):
|
||||||
|
logged_in = self.client.login(username=self.user.username, password=self.user_raw_password)
|
||||||
|
self.assertTrue(logged_in, 'User is not logged in')
|
||||||
|
|
||||||
|
#make categories
|
||||||
|
category1, category2, category3 = self.make_test_categories()
|
||||||
|
|
||||||
|
#make an Ajax POST
|
||||||
|
response = self.client.post(self.get_sorting_url(),
|
||||||
|
data=self.get_category_indexes(category3, category2, category1),
|
||||||
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
content = json.loads(response.content)
|
||||||
|
self.assertTrue(content.get('objects_sorted'), u'Objects should have been sorted.')
|
||||||
|
|
||||||
|
#assert order is correct
|
||||||
|
categories = Category.objects.all()
|
||||||
|
cat1 = categories[0]
|
||||||
|
cat2 = categories[1]
|
||||||
|
cat3 = categories[2]
|
||||||
|
|
||||||
|
self.assertEqual(cat1.order, 1, u'First category returned should have order == 1')
|
||||||
|
self.assertEqual(cat1.pk, 3, u'Category ID 3 should have been first in queryset')
|
||||||
|
|
||||||
|
self.assertEqual(cat2.order, 2, u'Second category returned should have order == 2')
|
||||||
|
self.assertEqual(cat2.pk, 2, u'Category ID 2 should have been second in queryset')
|
||||||
|
|
||||||
|
self.assertEqual(cat3.order, 3, u'Third category returned should have order == 3')
|
||||||
|
self.assertEqual(cat3.pk, 1, u'Category ID 1 should have been third in queryset')
|
||||||
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,2 @@
|
||||||
|
VERSION = (1, 0, 1)
|
||||||
|
__version__ = '1.0.1'
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,15 @@
|
||||||
|
import os, os.path as osp
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db.models import get_apps
|
||||||
|
|
||||||
|
def get_app_resource(app, path):
|
||||||
|
apps = get_apps()
|
||||||
|
for x in apps:
|
||||||
|
app_dir = osp.dirname(x.__file__)
|
||||||
|
if app == x.__name__.split('.')[-2]:
|
||||||
|
resource_dir = osp.join(app_dir, "resources")
|
||||||
|
asset = osp.join(resource_dir, path)
|
||||||
|
if osp.exists(asset):
|
||||||
|
return asset
|
||||||
|
continue
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,35 @@
|
||||||
|
from django.core.management.base import LabelCommand, CommandError
|
||||||
|
from django.db.models import get_apps
|
||||||
|
import os, os.path as osp
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
class Command(LabelCommand):
|
||||||
|
args = ''
|
||||||
|
label = 'directory'
|
||||||
|
|
||||||
|
def handle(self, *labels, **options):
|
||||||
|
if not labels or len(labels) > 1:
|
||||||
|
raise CommandError('Enter one directory name.')
|
||||||
|
|
||||||
|
label = labels[0]
|
||||||
|
final_dest = osp.join(os.getcwd(), label)
|
||||||
|
if osp.exists(final_dest):
|
||||||
|
raise CommandError('Directory already exists')
|
||||||
|
|
||||||
|
os.mkdir(final_dest)
|
||||||
|
|
||||||
|
apps = get_apps()
|
||||||
|
for x in apps:
|
||||||
|
app_dir = osp.dirname(x.__file__)
|
||||||
|
module = x.__name__
|
||||||
|
app = module.split('.')[-2]
|
||||||
|
|
||||||
|
if app == 'admin': continue
|
||||||
|
|
||||||
|
|
||||||
|
media_dir = osp.join(app_dir, "media", app)
|
||||||
|
if not osp.isdir(media_dir):
|
||||||
|
media_dir = osp.join(app_dir, "media")
|
||||||
|
if osp.exists(media_dir):
|
||||||
|
print "copy", media_dir, '->', osp.join(final_dest, app)
|
||||||
|
shutil.copytree(media_dir, osp.join(final_dest, app))
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
from django.core.management.base import NoArgsCommand
|
||||||
|
from django.db.models import get_apps
|
||||||
|
|
||||||
|
|
||||||
|
class Command(NoArgsCommand):
|
||||||
|
help = """
|
||||||
|
Removes all symlinks in MEDIA_ROOT and then scans all installed applications for a media folder to symlink to MEDIA_ROOT.
|
||||||
|
|
||||||
|
If installed app has a media folder, it first attempts to symlink the contents
|
||||||
|
ie: app/media/app_name -> MEDIA_ROOT/app_name
|
||||||
|
|
||||||
|
If the symlink name already exists, it assumes the media directory is not subfoldered and attempts:
|
||||||
|
ie: app/media -> MEDIA_ROOT/app_name"""
|
||||||
|
|
||||||
|
def handle_noargs(self, **options):
|
||||||
|
from django.conf import settings
|
||||||
|
import os
|
||||||
|
|
||||||
|
media_path = settings.MEDIA_ROOT
|
||||||
|
print "creating symlinks for app media under %s" % media_path
|
||||||
|
for d in os.listdir(media_path):
|
||||||
|
path = os.path.join(media_path, d)
|
||||||
|
if os.path.islink(path):
|
||||||
|
os.remove(os.path.join(path))
|
||||||
|
print " - removed %s" % path
|
||||||
|
|
||||||
|
apps = get_apps()
|
||||||
|
for app in apps:
|
||||||
|
|
||||||
|
app_file = app.__file__
|
||||||
|
if os.path.splitext(app_file)[0].endswith('/__init__'):
|
||||||
|
# models are an folder, go one level up
|
||||||
|
app_file = os.path.dirname(app_file)
|
||||||
|
|
||||||
|
app_path = os.path.dirname(app_file)
|
||||||
|
if 'media' in os.listdir(app_path) and os.path.isdir(os.path.join(app_path,'media')):
|
||||||
|
module = app.__name__
|
||||||
|
app_name = module.split('.')[-2]
|
||||||
|
app_media = os.path.join(app_path, "media", app_name)
|
||||||
|
if not os.path.isdir(app_media):
|
||||||
|
app_media = os.path.join(app_path, "media")
|
||||||
|
try:
|
||||||
|
os.symlink(app_media, os.path.join(media_path, app_name))
|
||||||
|
print " + added %s as %s" % (os.path.join(app_media), os.path.join(media_path, app_name))
|
||||||
|
except OSError, e:
|
||||||
|
if e.errno == 17:
|
||||||
|
pass
|
||||||
|
print " o skipping %s" % app_media
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
# try:
|
||||||
|
# os.symlink(app_media, os.path.join(media_path,app.split('.')[-1]))
|
||||||
|
# print " + added %s as %s" % (app_media, os.path.join(media_path, app.split('.')[-1]))
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from md5 import md5
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from appmedia.BeautifulSoup import BeautifulSoup, Tag
|
||||||
|
|
||||||
|
boundary = '*#*'
|
||||||
|
|
||||||
|
class ReplaceAssets(object):
|
||||||
|
def process_response(self, request, response):
|
||||||
|
if response['Content-Type'].startswith('text/html') and settings.CACHE_BACKEND not in ['', None, 'dummy:///']:
|
||||||
|
soup = BeautifulSoup(response.content)
|
||||||
|
head = soup.head
|
||||||
|
|
||||||
|
#[script.extract() for script in head.findAll(lambda x: x.name == 'script' and 'src' in dict(x.attrs) and x['src'].startswith(settings.MEDIA_URL) )]
|
||||||
|
#[css.extract() for css in head.findAll(lambda x: x.name == 'link' and 'href' in dict(x.attrs) and x['href'].startswith(settings.MEDIA_URL) )]
|
||||||
|
|
||||||
|
scripts = head.findAll(lambda x: x.name == 'script' and 'src' in dict(x.attrs) and x['src'].startswith(settings.MEDIA_URL) )
|
||||||
|
css = head.findAll(lambda x: x.name == 'link' and 'href' in dict(x.attrs) and x['href'].startswith(settings.MEDIA_URL) )
|
||||||
|
|
||||||
|
script_sources = [x['src'] for x in scripts]
|
||||||
|
new_script = md5(boundary.join(script_sources)).hexdigest()
|
||||||
|
cache.set(new_script, script_sources)
|
||||||
|
[x.extract() for x in scripts]
|
||||||
|
|
||||||
|
css_sources = [x['href'] for x in css]
|
||||||
|
new_css = md5(boundary.join(css_sources)).hexdigest()
|
||||||
|
cache.set(new_css, css_sources)
|
||||||
|
[x.extract() for x in css]
|
||||||
|
|
||||||
|
tag = Tag(soup, "script", [("type", "text/javascript"), ("src", reverse('cached_asset', kwargs={'asset':new_script+".js"}) )])
|
||||||
|
head.insert(0, tag)
|
||||||
|
tag = Tag(soup, "link", [("type", "text/css"), ("href", reverse('cached_asset', kwargs={'asset':new_css+".css"})), ('rel', 'stylesheet')])
|
||||||
|
head.insert(0, tag)
|
||||||
|
response.content = soup.prettify()
|
||||||
|
return response
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^cached-asset/(?P<asset>.*)$', 'appmedia.views.serve_cached_asset', name="cached_asset"),
|
||||||
|
(r'^(?P<app>[^/]*)/(?P<path>.*)$', 'appmedia.views.serve'),
|
||||||
|
)
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,81 @@
|
||||||
|
import os, os.path as osp
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.views.static import serve as django_serve
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
|
from django.db.models import get_apps
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.http import Http404, HttpResponse
|
||||||
|
|
||||||
|
def serve(request, app, path, show_indexes=True):
|
||||||
|
if request.method == 'GET':
|
||||||
|
apps = get_apps()
|
||||||
|
for x in apps:
|
||||||
|
app_dir = osp.dirname(x.__file__)
|
||||||
|
module = x.__name__
|
||||||
|
if app == module.split('.')[-2]: #we get the models module here
|
||||||
|
if app_dir.endswith("models"):
|
||||||
|
# this can happen only in case when models are an directory
|
||||||
|
app_dir = osp.split(app_dir)[0]
|
||||||
|
media_dir = osp.join(app_dir, "media", app)
|
||||||
|
if not osp.isdir(media_dir):
|
||||||
|
media_dir = osp.join(app_dir, "media")
|
||||||
|
asset = osp.join(media_dir, path)
|
||||||
|
if osp.exists(asset):
|
||||||
|
return django_serve(request, path, document_root=media_dir, show_indexes=show_indexes)
|
||||||
|
#continue
|
||||||
|
return django_serve(request, app+"/"+path, document_root=settings.MEDIA_ROOT, show_indexes=show_indexes)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
data = request.POST.get("data", "")
|
||||||
|
apps = get_apps()
|
||||||
|
for x in apps:
|
||||||
|
app_dir = osp.dirname(x.__file__)
|
||||||
|
module = x.__name__
|
||||||
|
if app == module.split('.')[-2]: #we get the models module here
|
||||||
|
media_dir = osp.join(app_dir, "media")
|
||||||
|
asset = osp.join(media_dir, path)
|
||||||
|
if osp.exists(asset):
|
||||||
|
f = file(asset, 'w')
|
||||||
|
for line in data.split('\n'):
|
||||||
|
line.strip()
|
||||||
|
line = line[:-1]
|
||||||
|
if line:
|
||||||
|
selector, datap = line.split('{')
|
||||||
|
print >>f, selector, '{'
|
||||||
|
datap.strip()
|
||||||
|
lines = datap.split(';')
|
||||||
|
if lines:
|
||||||
|
print >>f, " "+";\n ".join(lines)
|
||||||
|
print >>f, '}\n'
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
return django_serve(request, path, document_root=media_dir, show_indexes=show_indexes)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def get_file(path):
|
||||||
|
app = path.split('/')[2]
|
||||||
|
path = "/".join(path.split('/')[3:])
|
||||||
|
apps = get_apps()
|
||||||
|
for x in apps:
|
||||||
|
app_dir = osp.dirname(x.__file__)
|
||||||
|
module = x.__name__
|
||||||
|
if app == module.split('.')[-2]: #we get the models module here
|
||||||
|
media_dir = osp.join(app_dir, "media")
|
||||||
|
asset = osp.join(media_dir, path)
|
||||||
|
if osp.exists(asset):
|
||||||
|
print osp.join(media_dir, path)
|
||||||
|
return osp.join(media_dir, path)
|
||||||
|
return osp.join(settings.MEDIA_ROOT, app+"/"+path)
|
||||||
|
|
||||||
|
@cache_page(24*60*60)
|
||||||
|
def serve_cached_asset(request, asset):
|
||||||
|
name, ext = asset.split('.')
|
||||||
|
files = cache.get(name)
|
||||||
|
if ext == 'js':
|
||||||
|
response = HttpResponse("\n".join([file(get_file(path)).read() for path in files]), mimetype="text/javascript")
|
||||||
|
return response
|
||||||
|
elif ext == 'css':
|
||||||
|
response = HttpResponse("\n".join([file(get_file(path)).read() for path in files]), mimetype="text/css")
|
||||||
|
return response
|
||||||
|
raise Http404()
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from django.core.management import execute_manager
|
||||||
|
import imp
|
||||||
|
try:
|
||||||
|
imp.find_module('settings') # Assumed to be in the same directory.
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
import settings
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
execute_manager(settings)
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
def map_path(directory_name):
|
||||||
|
return os.path.join(os.path.dirname(__file__), directory_name).replace('\\', '/')
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
SERVE_STATIC_MEDIA = True
|
||||||
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
|
ADMINS = (
|
||||||
|
)
|
||||||
|
|
||||||
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||||
|
'NAME': 'adminsortable.sqlite', # Or path to database file if using sqlite3.
|
||||||
|
'USER': '', # Not used with sqlite3.
|
||||||
|
'PASSWORD': '', # Not used with sqlite3.
|
||||||
|
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
|
||||||
|
'PORT': '', # Set to empty string for default. Not used with sqlite3.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Local time zone for this installation. Choices can be found here:
|
||||||
|
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||||
|
# although not all choices may be available on all operating systems.
|
||||||
|
# On Unix systems, a value of None will cause Django to use the same
|
||||||
|
# timezone as the operating system.
|
||||||
|
# If running in a Windows environment this must be set to the same as your
|
||||||
|
# system time zone.
|
||||||
|
TIME_ZONE = 'America/Chicago'
|
||||||
|
|
||||||
|
# Language code for this installation. All choices can be found here:
|
||||||
|
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# If you set this to False, Django will make some optimizations so as not
|
||||||
|
# to load the internationalization machinery.
|
||||||
|
USE_I18N = False
|
||||||
|
|
||||||
|
# If you set this to False, Django will not format dates, numbers and
|
||||||
|
# calendars according to the current locale
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||||
|
# Example: "/home/media/media.lawrence.com/media/"
|
||||||
|
MEDIA_ROOT = map_path('static')
|
||||||
|
|
||||||
|
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||||
|
# trailing slash.
|
||||||
|
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
|
||||||
|
MEDIA_URL = '/static/'
|
||||||
|
|
||||||
|
# Absolute path to the directory static files should be collected to.
|
||||||
|
# Don't put anything in this directory yourself; store your static files
|
||||||
|
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||||
|
# Example: "/home/media/media.lawrence.com/static/"
|
||||||
|
STATIC_ROOT = ''
|
||||||
|
|
||||||
|
# URL prefix for static files.
|
||||||
|
# Example: "http://media.lawrence.com/static/"
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# URL prefix for admin static files -- CSS, JavaScript and images.
|
||||||
|
# Make sure to use a trailing slash.
|
||||||
|
# Examples: "http://foo.com/static/admin/", "/static/admin/".
|
||||||
|
ADMIN_MEDIA_PREFIX = '/media/'
|
||||||
|
|
||||||
|
# Additional locations of static files
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
# Put strings here, like "/home/html/static" or "C:/www/django/static".
|
||||||
|
# Always use forward slashes, even on Windows.
|
||||||
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
|
)
|
||||||
|
|
||||||
|
# List of finder classes that know how to find static files in
|
||||||
|
# various locations.
|
||||||
|
STATICFILES_FINDERS = (
|
||||||
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||||
|
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make this unique, and don't share it with anybody.
|
||||||
|
SECRET_KEY = 'uz-di2#4pzf77@9-+hh&lyypgg#--zk%$%l7p7h385#4u7ra98'
|
||||||
|
|
||||||
|
# List of callables that know how to import templates from various sources.
|
||||||
|
TEMPLATE_LOADERS = (
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
'django.template.loaders.app_directories.Loader',
|
||||||
|
# 'django.template.loaders.eggs.Loader',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.middleware.gzip.GZipMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.core.context_processors.i18n',
|
||||||
|
'django.core.context_processors.request',
|
||||||
|
'django.core.context_processors.media',
|
||||||
|
'django.core.context_processors.static',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'sample_project.urls'
|
||||||
|
|
||||||
|
TEMPLATE_DIRS = (
|
||||||
|
map_path('templates'),
|
||||||
|
)
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.admindocs',
|
||||||
|
|
||||||
|
'appmedia',
|
||||||
|
'south',
|
||||||
|
'adminsortable',
|
||||||
|
'app',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# A sample logging configuration. The only tangible logging
|
||||||
|
# performed by this configuration is to send an email to
|
||||||
|
# the site admins on every HTTP 500 error.
|
||||||
|
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||||
|
# more details on how to customize your logging configuration.
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'handlers': {
|
||||||
|
'mail_admins': {
|
||||||
|
'level': 'ERROR',
|
||||||
|
'class': 'django.utils.log.AdminEmailHandler'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'django.request': {
|
||||||
|
'handlers': ['mail_admins'],
|
||||||
|
'level': 'ERROR',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,48 @@
|
||||||
|
#sortable ul
|
||||||
|
{
|
||||||
|
-webkit-padding-start: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul ul
|
||||||
|
{
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul li,
|
||||||
|
#sortable ul li a
|
||||||
|
{
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul li
|
||||||
|
{
|
||||||
|
overflow: auto;
|
||||||
|
margin-left: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable .sortable
|
||||||
|
{
|
||||||
|
list-style: none;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable .sortable.single,
|
||||||
|
#sortable ul ul a
|
||||||
|
{
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sortable ul ul a
|
||||||
|
{
|
||||||
|
color: #5B80B2;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable a:hover
|
||||||
|
{
|
||||||
|
color: #003366;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
.tabular .sortable
|
||||||
|
{
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
jQuery(function($){
|
||||||
|
$('.sortable').sortable({
|
||||||
|
axis : 'y',
|
||||||
|
containment : 'parent',
|
||||||
|
tolerance : 'pointer',
|
||||||
|
items : 'li',
|
||||||
|
stop : function(event, ui)
|
||||||
|
{
|
||||||
|
var indexes = Array();
|
||||||
|
ui.item.parent().children('li').each(function(i)
|
||||||
|
{
|
||||||
|
indexes.push($(this).find(':hidden[name="pk"]').val());
|
||||||
|
});
|
||||||
|
$.ajax({
|
||||||
|
url: ui.item.find('a.admin_sorting_url').attr('href'),
|
||||||
|
type: 'POST',
|
||||||
|
data: { indexes: indexes.join(',') }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
jQuery(function($){
|
||||||
|
if ($(':hidden[name="admin_sorting_url"]').length > 0)
|
||||||
|
{
|
||||||
|
var tabular_inline_rows = $('.tabular table tbody tr');
|
||||||
|
tabular_inline_rows.addClass('sortable');
|
||||||
|
$('.tabular.inline-related').sortable({
|
||||||
|
axis : 'y',
|
||||||
|
containment : 'parent',
|
||||||
|
tolerance : 'pointer',
|
||||||
|
items : 'tr',
|
||||||
|
stop : function(event, ui)
|
||||||
|
{
|
||||||
|
var indexes = Array();
|
||||||
|
ui.item.parent().children('tr').each(function(i)
|
||||||
|
{
|
||||||
|
index_value = $(this).find('.original :hidden:first').val();
|
||||||
|
if (index_value != "" && index_value != undefined)
|
||||||
|
indexes.push(index_value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ui.item.parent().find(':hidden[name="admin_sorting_url"]').val(),
|
||||||
|
type: 'POST',
|
||||||
|
data: { indexes : indexes.join(',') },
|
||||||
|
success: function()
|
||||||
|
{
|
||||||
|
//re-stripe table
|
||||||
|
tabular_inline_rows.removeClass('row1 row2');
|
||||||
|
$('.tabular table tbody tr:odd').addClass('row2');
|
||||||
|
$('.tabular table tbody tr:even').addClass('row1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,314 @@
|
||||||
|
/*!
|
||||||
|
* jQuery UI 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
// prevent duplicate loading
|
||||||
|
// this is only a problem because we proxy existing functions
|
||||||
|
// and we don't want to double proxy them
|
||||||
|
$.ui = $.ui || {};
|
||||||
|
if ( $.ui.version ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend( $.ui, {
|
||||||
|
version: "1.8.16",
|
||||||
|
|
||||||
|
keyCode: {
|
||||||
|
ALT: 18,
|
||||||
|
BACKSPACE: 8,
|
||||||
|
CAPS_LOCK: 20,
|
||||||
|
COMMA: 188,
|
||||||
|
COMMAND: 91,
|
||||||
|
COMMAND_LEFT: 91, // COMMAND
|
||||||
|
COMMAND_RIGHT: 93,
|
||||||
|
CONTROL: 17,
|
||||||
|
DELETE: 46,
|
||||||
|
DOWN: 40,
|
||||||
|
END: 35,
|
||||||
|
ENTER: 13,
|
||||||
|
ESCAPE: 27,
|
||||||
|
HOME: 36,
|
||||||
|
INSERT: 45,
|
||||||
|
LEFT: 37,
|
||||||
|
MENU: 93, // COMMAND_RIGHT
|
||||||
|
NUMPAD_ADD: 107,
|
||||||
|
NUMPAD_DECIMAL: 110,
|
||||||
|
NUMPAD_DIVIDE: 111,
|
||||||
|
NUMPAD_ENTER: 108,
|
||||||
|
NUMPAD_MULTIPLY: 106,
|
||||||
|
NUMPAD_SUBTRACT: 109,
|
||||||
|
PAGE_DOWN: 34,
|
||||||
|
PAGE_UP: 33,
|
||||||
|
PERIOD: 190,
|
||||||
|
RIGHT: 39,
|
||||||
|
SHIFT: 16,
|
||||||
|
SPACE: 32,
|
||||||
|
TAB: 9,
|
||||||
|
UP: 38,
|
||||||
|
WINDOWS: 91 // COMMAND
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// plugins
|
||||||
|
$.fn.extend({
|
||||||
|
propAttr: $.fn.prop || $.fn.attr,
|
||||||
|
|
||||||
|
_focus: $.fn.focus,
|
||||||
|
focus: function( delay, fn ) {
|
||||||
|
return typeof delay === "number" ?
|
||||||
|
this.each(function() {
|
||||||
|
var elem = this;
|
||||||
|
setTimeout(function() {
|
||||||
|
$( elem ).focus();
|
||||||
|
if ( fn ) {
|
||||||
|
fn.call( elem );
|
||||||
|
}
|
||||||
|
}, delay );
|
||||||
|
}) :
|
||||||
|
this._focus.apply( this, arguments );
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollParent: function() {
|
||||||
|
var scrollParent;
|
||||||
|
if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
|
||||||
|
scrollParent = this.parents().filter(function() {
|
||||||
|
return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
|
||||||
|
}).eq(0);
|
||||||
|
} else {
|
||||||
|
scrollParent = this.parents().filter(function() {
|
||||||
|
return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
|
||||||
|
}).eq(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
|
||||||
|
},
|
||||||
|
|
||||||
|
zIndex: function( zIndex ) {
|
||||||
|
if ( zIndex !== undefined ) {
|
||||||
|
return this.css( "zIndex", zIndex );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.length ) {
|
||||||
|
var elem = $( this[ 0 ] ), position, value;
|
||||||
|
while ( elem.length && elem[ 0 ] !== document ) {
|
||||||
|
// Ignore z-index if position is set to a value where z-index is ignored by the browser
|
||||||
|
// This makes behavior of this function consistent across browsers
|
||||||
|
// WebKit always returns auto if the element is positioned
|
||||||
|
position = elem.css( "position" );
|
||||||
|
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
|
||||||
|
// IE returns 0 when zIndex is not specified
|
||||||
|
// other browsers return a string
|
||||||
|
// we ignore the case of nested elements with an explicit value of 0
|
||||||
|
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
|
||||||
|
value = parseInt( elem.css( "zIndex" ), 10 );
|
||||||
|
if ( !isNaN( value ) && value !== 0 ) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elem = elem.parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
disableSelection: function() {
|
||||||
|
return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
|
||||||
|
".ui-disableSelection", function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
enableSelection: function() {
|
||||||
|
return this.unbind( ".ui-disableSelection" );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each( [ "Width", "Height" ], function( i, name ) {
|
||||||
|
var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
|
||||||
|
type = name.toLowerCase(),
|
||||||
|
orig = {
|
||||||
|
innerWidth: $.fn.innerWidth,
|
||||||
|
innerHeight: $.fn.innerHeight,
|
||||||
|
outerWidth: $.fn.outerWidth,
|
||||||
|
outerHeight: $.fn.outerHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
function reduce( elem, size, border, margin ) {
|
||||||
|
$.each( side, function() {
|
||||||
|
size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0;
|
||||||
|
if ( border ) {
|
||||||
|
size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0;
|
||||||
|
}
|
||||||
|
if ( margin ) {
|
||||||
|
size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn[ "inner" + name ] = function( size ) {
|
||||||
|
if ( size === undefined ) {
|
||||||
|
return orig[ "inner" + name ].call( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.each(function() {
|
||||||
|
$( this ).css( type, reduce( this, size ) + "px" );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn[ "outer" + name] = function( size, margin ) {
|
||||||
|
if ( typeof size !== "number" ) {
|
||||||
|
return orig[ "outer" + name ].call( this, size );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.each(function() {
|
||||||
|
$( this).css( type, reduce( this, size, true, margin ) + "px" );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// selectors
|
||||||
|
function focusable( element, isTabIndexNotNaN ) {
|
||||||
|
var nodeName = element.nodeName.toLowerCase();
|
||||||
|
if ( "area" === nodeName ) {
|
||||||
|
var map = element.parentNode,
|
||||||
|
mapName = map.name,
|
||||||
|
img;
|
||||||
|
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
img = $( "img[usemap=#" + mapName + "]" )[0];
|
||||||
|
return !!img && visible( img );
|
||||||
|
}
|
||||||
|
return ( /input|select|textarea|button|object/.test( nodeName )
|
||||||
|
? !element.disabled
|
||||||
|
: "a" == nodeName
|
||||||
|
? element.href || isTabIndexNotNaN
|
||||||
|
: isTabIndexNotNaN)
|
||||||
|
// the element and all of its ancestors must be visible
|
||||||
|
&& visible( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
function visible( element ) {
|
||||||
|
return !$( element ).parents().andSelf().filter(function() {
|
||||||
|
return $.curCSS( this, "visibility" ) === "hidden" ||
|
||||||
|
$.expr.filters.hidden( this );
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend( $.expr[ ":" ], {
|
||||||
|
data: function( elem, i, match ) {
|
||||||
|
return !!$.data( elem, match[ 3 ] );
|
||||||
|
},
|
||||||
|
|
||||||
|
focusable: function( element ) {
|
||||||
|
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
tabbable: function( element ) {
|
||||||
|
var tabIndex = $.attr( element, "tabindex" ),
|
||||||
|
isTabIndexNaN = isNaN( tabIndex );
|
||||||
|
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// support
|
||||||
|
$(function() {
|
||||||
|
var body = document.body,
|
||||||
|
div = body.appendChild( div = document.createElement( "div" ) );
|
||||||
|
|
||||||
|
$.extend( div.style, {
|
||||||
|
minHeight: "100px",
|
||||||
|
height: "auto",
|
||||||
|
padding: 0,
|
||||||
|
borderWidth: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$.support.minHeight = div.offsetHeight === 100;
|
||||||
|
$.support.selectstart = "onselectstart" in div;
|
||||||
|
|
||||||
|
// set display to none to avoid a layout bug in IE
|
||||||
|
// http://dev.jquery.com/ticket/4014
|
||||||
|
body.removeChild( div ).style.display = "none";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
$.extend( $.ui, {
|
||||||
|
// $.ui.plugin is deprecated. Use the proxy pattern instead.
|
||||||
|
plugin: {
|
||||||
|
add: function( module, option, set ) {
|
||||||
|
var proto = $.ui[ module ].prototype;
|
||||||
|
for ( var i in set ) {
|
||||||
|
proto.plugins[ i ] = proto.plugins[ i ] || [];
|
||||||
|
proto.plugins[ i ].push( [ option, set[ i ] ] );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
call: function( instance, name, args ) {
|
||||||
|
var set = instance.plugins[ name ];
|
||||||
|
if ( !set || !instance.element[ 0 ].parentNode ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( var i = 0; i < set.length; i++ ) {
|
||||||
|
if ( instance.options[ set[ i ][ 0 ] ] ) {
|
||||||
|
set[ i ][ 1 ].apply( instance.element, args );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// will be deprecated when we switch to jQuery 1.4 - use jQuery.contains()
|
||||||
|
contains: function( a, b ) {
|
||||||
|
return document.compareDocumentPosition ?
|
||||||
|
a.compareDocumentPosition( b ) & 16 :
|
||||||
|
a !== b && a.contains( b );
|
||||||
|
},
|
||||||
|
|
||||||
|
// only used by resizable
|
||||||
|
hasScroll: function( el, a ) {
|
||||||
|
|
||||||
|
//If overflow is hidden, the element might have extra content, but the user wants to hide it
|
||||||
|
if ( $( el ).css( "overflow" ) === "hidden") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
|
||||||
|
has = false;
|
||||||
|
|
||||||
|
if ( el[ scroll ] > 0 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: determine which cases actually cause this to happen
|
||||||
|
// if the element doesn't have the scroll set, see if it's possible to
|
||||||
|
// set the scroll
|
||||||
|
el[ scroll ] = 1;
|
||||||
|
has = ( el[ scroll ] > 0 );
|
||||||
|
el[ scroll ] = 0;
|
||||||
|
return has;
|
||||||
|
},
|
||||||
|
|
||||||
|
// these are odd functions, fix the API or move into individual plugins
|
||||||
|
isOverAxis: function( x, reference, size ) {
|
||||||
|
//Determines when x coordinate is over "b" element axis
|
||||||
|
return ( x > reference ) && ( x < ( reference + size ) );
|
||||||
|
},
|
||||||
|
isOver: function( y, x, top, left, height, width ) {
|
||||||
|
//Determines when x, y coordinates is over "b" element
|
||||||
|
return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})( jQuery );
|
||||||
|
|
@ -0,0 +1,825 @@
|
||||||
|
/*
|
||||||
|
* jQuery UI Draggable 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Draggables
|
||||||
|
*
|
||||||
|
* Depends:
|
||||||
|
* jquery.ui.core.js
|
||||||
|
* jquery.ui.mouse.js
|
||||||
|
* jquery.ui.widget.js
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
$.widget("ui.draggable", $.ui.mouse, {
|
||||||
|
widgetEventPrefix: "drag",
|
||||||
|
options: {
|
||||||
|
addClasses: true,
|
||||||
|
appendTo: "parent",
|
||||||
|
axis: false,
|
||||||
|
connectToSortable: false,
|
||||||
|
containment: false,
|
||||||
|
cursor: "auto",
|
||||||
|
cursorAt: false,
|
||||||
|
grid: false,
|
||||||
|
handle: false,
|
||||||
|
helper: "original",
|
||||||
|
iframeFix: false,
|
||||||
|
opacity: false,
|
||||||
|
refreshPositions: false,
|
||||||
|
revert: false,
|
||||||
|
revertDuration: 500,
|
||||||
|
scope: "default",
|
||||||
|
scroll: true,
|
||||||
|
scrollSensitivity: 20,
|
||||||
|
scrollSpeed: 20,
|
||||||
|
snap: false,
|
||||||
|
snapMode: "both",
|
||||||
|
snapTolerance: 20,
|
||||||
|
stack: false,
|
||||||
|
zIndex: false
|
||||||
|
},
|
||||||
|
_create: function() {
|
||||||
|
|
||||||
|
if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
|
||||||
|
this.element[0].style.position = 'relative';
|
||||||
|
|
||||||
|
(this.options.addClasses && this.element.addClass("ui-draggable"));
|
||||||
|
(this.options.disabled && this.element.addClass("ui-draggable-disabled"));
|
||||||
|
|
||||||
|
this._mouseInit();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
if(!this.element.data('draggable')) return;
|
||||||
|
this.element
|
||||||
|
.removeData("draggable")
|
||||||
|
.unbind(".draggable")
|
||||||
|
.removeClass("ui-draggable"
|
||||||
|
+ " ui-draggable-dragging"
|
||||||
|
+ " ui-draggable-disabled");
|
||||||
|
this._mouseDestroy();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseCapture: function(event) {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
|
||||||
|
// among others, prevent a drag on a resizable-handle
|
||||||
|
if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Quit if we're not on a valid handle
|
||||||
|
this.handle = this._getHandle(event);
|
||||||
|
if (!this.handle)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( o.iframeFix ) {
|
||||||
|
$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
|
||||||
|
$('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
|
||||||
|
.css({
|
||||||
|
width: this.offsetWidth+"px", height: this.offsetHeight+"px",
|
||||||
|
position: "absolute", opacity: "0.001", zIndex: 1000
|
||||||
|
})
|
||||||
|
.css($(this).offset())
|
||||||
|
.appendTo("body");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseStart: function(event) {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
|
||||||
|
//Create and append the visible helper
|
||||||
|
this.helper = this._createHelper(event);
|
||||||
|
|
||||||
|
//Cache the helper size
|
||||||
|
this._cacheHelperProportions();
|
||||||
|
|
||||||
|
//If ddmanager is used for droppables, set the global draggable
|
||||||
|
if($.ui.ddmanager)
|
||||||
|
$.ui.ddmanager.current = this;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Position generation -
|
||||||
|
* This block generates everything position related - it's the core of draggables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Cache the margins of the original element
|
||||||
|
this._cacheMargins();
|
||||||
|
|
||||||
|
//Store the helper's css position
|
||||||
|
this.cssPosition = this.helper.css("position");
|
||||||
|
this.scrollParent = this.helper.scrollParent();
|
||||||
|
|
||||||
|
//The element's absolute position on the page minus margins
|
||||||
|
this.offset = this.positionAbs = this.element.offset();
|
||||||
|
this.offset = {
|
||||||
|
top: this.offset.top - this.margins.top,
|
||||||
|
left: this.offset.left - this.margins.left
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend(this.offset, {
|
||||||
|
click: { //Where the click happened, relative to the element
|
||||||
|
left: event.pageX - this.offset.left,
|
||||||
|
top: event.pageY - this.offset.top
|
||||||
|
},
|
||||||
|
parent: this._getParentOffset(),
|
||||||
|
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
|
||||||
|
});
|
||||||
|
|
||||||
|
//Generate the original position
|
||||||
|
this.originalPosition = this.position = this._generatePosition(event);
|
||||||
|
this.originalPageX = event.pageX;
|
||||||
|
this.originalPageY = event.pageY;
|
||||||
|
|
||||||
|
//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
|
||||||
|
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
|
||||||
|
|
||||||
|
//Set a containment if given in the options
|
||||||
|
if(o.containment)
|
||||||
|
this._setContainment();
|
||||||
|
|
||||||
|
//Trigger event + callbacks
|
||||||
|
if(this._trigger("start", event) === false) {
|
||||||
|
this._clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Recache the helper size
|
||||||
|
this._cacheHelperProportions();
|
||||||
|
|
||||||
|
//Prepare the droppable offsets
|
||||||
|
if ($.ui.ddmanager && !o.dropBehaviour)
|
||||||
|
$.ui.ddmanager.prepareOffsets(this, event);
|
||||||
|
|
||||||
|
this.helper.addClass("ui-draggable-dragging");
|
||||||
|
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
|
||||||
|
|
||||||
|
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
|
||||||
|
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDrag: function(event, noPropagation) {
|
||||||
|
|
||||||
|
//Compute the helpers position
|
||||||
|
this.position = this._generatePosition(event);
|
||||||
|
this.positionAbs = this._convertPositionTo("absolute");
|
||||||
|
|
||||||
|
//Call plugins and callbacks and use the resulting position if something is returned
|
||||||
|
if (!noPropagation) {
|
||||||
|
var ui = this._uiHash();
|
||||||
|
if(this._trigger('drag', event, ui) === false) {
|
||||||
|
this._mouseUp({});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.position = ui.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
|
||||||
|
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
|
||||||
|
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseStop: function(event) {
|
||||||
|
|
||||||
|
//If we are using droppables, inform the manager about the drop
|
||||||
|
var dropped = false;
|
||||||
|
if ($.ui.ddmanager && !this.options.dropBehaviour)
|
||||||
|
dropped = $.ui.ddmanager.drop(this, event);
|
||||||
|
|
||||||
|
//if a drop comes from outside (a sortable)
|
||||||
|
if(this.dropped) {
|
||||||
|
dropped = this.dropped;
|
||||||
|
this.dropped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if the original element is removed, don't bother to continue if helper is set to "original"
|
||||||
|
if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
|
||||||
|
var self = this;
|
||||||
|
$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
|
||||||
|
if(self._trigger("stop", event) !== false) {
|
||||||
|
self._clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(this._trigger("stop", event) !== false) {
|
||||||
|
this._clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseUp: function(event) {
|
||||||
|
if (this.options.iframeFix === true) {
|
||||||
|
$("div.ui-draggable-iframeFix").each(function() {
|
||||||
|
this.parentNode.removeChild(this);
|
||||||
|
}); //Remove frame helpers
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
|
||||||
|
if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
|
||||||
|
|
||||||
|
return $.ui.mouse.prototype._mouseUp.call(this, event);
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel: function() {
|
||||||
|
|
||||||
|
if(this.helper.is(".ui-draggable-dragging")) {
|
||||||
|
this._mouseUp({});
|
||||||
|
} else {
|
||||||
|
this._clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_getHandle: function(event) {
|
||||||
|
|
||||||
|
var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
|
||||||
|
$(this.options.handle, this.element)
|
||||||
|
.find("*")
|
||||||
|
.andSelf()
|
||||||
|
.each(function() {
|
||||||
|
if(this == event.target) handle = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_createHelper: function(event) {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
|
||||||
|
|
||||||
|
if(!helper.parents('body').length)
|
||||||
|
helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
|
||||||
|
|
||||||
|
if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
|
||||||
|
helper.css("position", "absolute");
|
||||||
|
|
||||||
|
return helper;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_adjustOffsetFromHelper: function(obj) {
|
||||||
|
if (typeof obj == 'string') {
|
||||||
|
obj = obj.split(' ');
|
||||||
|
}
|
||||||
|
if ($.isArray(obj)) {
|
||||||
|
obj = {left: +obj[0], top: +obj[1] || 0};
|
||||||
|
}
|
||||||
|
if ('left' in obj) {
|
||||||
|
this.offset.click.left = obj.left + this.margins.left;
|
||||||
|
}
|
||||||
|
if ('right' in obj) {
|
||||||
|
this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
|
||||||
|
}
|
||||||
|
if ('top' in obj) {
|
||||||
|
this.offset.click.top = obj.top + this.margins.top;
|
||||||
|
}
|
||||||
|
if ('bottom' in obj) {
|
||||||
|
this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getParentOffset: function() {
|
||||||
|
|
||||||
|
//Get the offsetParent and cache its position
|
||||||
|
this.offsetParent = this.helper.offsetParent();
|
||||||
|
var po = this.offsetParent.offset();
|
||||||
|
|
||||||
|
// This is a special case where we need to modify a offset calculated on start, since the following happened:
|
||||||
|
// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
|
||||||
|
// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
|
||||||
|
// the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
|
||||||
|
if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
|
||||||
|
po.left += this.scrollParent.scrollLeft();
|
||||||
|
po.top += this.scrollParent.scrollTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
|
||||||
|
|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
|
||||||
|
po = { top: 0, left: 0 };
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
|
||||||
|
left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_getRelativeOffset: function() {
|
||||||
|
|
||||||
|
if(this.cssPosition == "relative") {
|
||||||
|
var p = this.element.position();
|
||||||
|
return {
|
||||||
|
top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
|
||||||
|
left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { top: 0, left: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_cacheMargins: function() {
|
||||||
|
this.margins = {
|
||||||
|
left: (parseInt(this.element.css("marginLeft"),10) || 0),
|
||||||
|
top: (parseInt(this.element.css("marginTop"),10) || 0),
|
||||||
|
right: (parseInt(this.element.css("marginRight"),10) || 0),
|
||||||
|
bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_cacheHelperProportions: function() {
|
||||||
|
this.helperProportions = {
|
||||||
|
width: this.helper.outerWidth(),
|
||||||
|
height: this.helper.outerHeight()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_setContainment: function() {
|
||||||
|
|
||||||
|
var o = this.options;
|
||||||
|
if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
|
||||||
|
if(o.containment == 'document' || o.containment == 'window') this.containment = [
|
||||||
|
o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
|
||||||
|
o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
|
||||||
|
(o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
|
||||||
|
(o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
|
||||||
|
];
|
||||||
|
|
||||||
|
if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
|
||||||
|
var c = $(o.containment);
|
||||||
|
var ce = c[0]; if(!ce) return;
|
||||||
|
var co = c.offset();
|
||||||
|
var over = ($(ce).css("overflow") != 'hidden');
|
||||||
|
|
||||||
|
this.containment = [
|
||||||
|
(parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
|
||||||
|
(parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
|
||||||
|
(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
|
||||||
|
(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
|
||||||
|
];
|
||||||
|
this.relative_container = c;
|
||||||
|
|
||||||
|
} else if(o.containment.constructor == Array) {
|
||||||
|
this.containment = o.containment;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_convertPositionTo: function(d, pos) {
|
||||||
|
|
||||||
|
if(!pos) pos = this.position;
|
||||||
|
var mod = d == "absolute" ? 1 : -1;
|
||||||
|
var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: (
|
||||||
|
pos.top // The absolute mouse position
|
||||||
|
+ this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
+ this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
|
||||||
|
- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
|
||||||
|
),
|
||||||
|
left: (
|
||||||
|
pos.left // The absolute mouse position
|
||||||
|
+ this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
+ this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
|
||||||
|
- ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_generatePosition: function(event) {
|
||||||
|
|
||||||
|
var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
|
||||||
|
var pageX = event.pageX;
|
||||||
|
var pageY = event.pageY;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Position constraining -
|
||||||
|
* Constrain the position to a mix of grid, containment.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(this.originalPosition) { //If we are not dragging yet, we won't check for options
|
||||||
|
var containment;
|
||||||
|
if(this.containment) {
|
||||||
|
if (this.relative_container){
|
||||||
|
var co = this.relative_container.offset();
|
||||||
|
containment = [ this.containment[0] + co.left,
|
||||||
|
this.containment[1] + co.top,
|
||||||
|
this.containment[2] + co.left,
|
||||||
|
this.containment[3] + co.top ];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
containment = this.containment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
|
||||||
|
if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
|
||||||
|
if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
|
||||||
|
if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o.grid) {
|
||||||
|
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
|
||||||
|
var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
|
||||||
|
pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
|
||||||
|
|
||||||
|
var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
|
||||||
|
pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: (
|
||||||
|
pageY // The absolute mouse position
|
||||||
|
- this.offset.click.top // Click offset (relative to the element)
|
||||||
|
- this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
- this.offset.parent.top // The offsetParent's offset without borders (offset + border)
|
||||||
|
+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
|
||||||
|
),
|
||||||
|
left: (
|
||||||
|
pageX // The absolute mouse position
|
||||||
|
- this.offset.click.left // Click offset (relative to the element)
|
||||||
|
- this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
|
||||||
|
- this.offset.parent.left // The offsetParent's offset without borders (offset + border)
|
||||||
|
+ ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_clear: function() {
|
||||||
|
this.helper.removeClass("ui-draggable-dragging");
|
||||||
|
if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
|
||||||
|
//if($.ui.ddmanager) $.ui.ddmanager.current = null;
|
||||||
|
this.helper = null;
|
||||||
|
this.cancelHelperRemoval = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// From now on bulk stuff - mainly helpers
|
||||||
|
|
||||||
|
_trigger: function(type, event, ui) {
|
||||||
|
ui = ui || this._uiHash();
|
||||||
|
$.ui.plugin.call(this, type, [event, ui]);
|
||||||
|
if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
|
||||||
|
return $.Widget.prototype._trigger.call(this, type, event, ui);
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: {},
|
||||||
|
|
||||||
|
_uiHash: function(event) {
|
||||||
|
return {
|
||||||
|
helper: this.helper,
|
||||||
|
position: this.position,
|
||||||
|
originalPosition: this.originalPosition,
|
||||||
|
offset: this.positionAbs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$.extend($.ui.draggable, {
|
||||||
|
version: "1.8.16"
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "connectToSortable", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
|
||||||
|
var inst = $(this).data("draggable"), o = inst.options,
|
||||||
|
uiSortable = $.extend({}, ui, { item: inst.element });
|
||||||
|
inst.sortables = [];
|
||||||
|
$(o.connectToSortable).each(function() {
|
||||||
|
var sortable = $.data(this, 'sortable');
|
||||||
|
if (sortable && !sortable.options.disabled) {
|
||||||
|
inst.sortables.push({
|
||||||
|
instance: sortable,
|
||||||
|
shouldRevert: sortable.options.revert
|
||||||
|
});
|
||||||
|
sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
|
||||||
|
sortable._trigger("activate", event, uiSortable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
|
||||||
|
//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
|
||||||
|
var inst = $(this).data("draggable"),
|
||||||
|
uiSortable = $.extend({}, ui, { item: inst.element });
|
||||||
|
|
||||||
|
$.each(inst.sortables, function() {
|
||||||
|
if(this.instance.isOver) {
|
||||||
|
|
||||||
|
this.instance.isOver = 0;
|
||||||
|
|
||||||
|
inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
|
||||||
|
this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
|
||||||
|
|
||||||
|
//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
|
||||||
|
if(this.shouldRevert) this.instance.options.revert = true;
|
||||||
|
|
||||||
|
//Trigger the stop of the sortable
|
||||||
|
this.instance._mouseStop(event);
|
||||||
|
|
||||||
|
this.instance.options.helper = this.instance.options._helper;
|
||||||
|
|
||||||
|
//If the helper has been the original item, restore properties in the sortable
|
||||||
|
if(inst.options.helper == 'original')
|
||||||
|
this.instance.currentItem.css({ top: 'auto', left: 'auto' });
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
|
||||||
|
this.instance._trigger("deactivate", event, uiSortable);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
drag: function(event, ui) {
|
||||||
|
|
||||||
|
var inst = $(this).data("draggable"), self = this;
|
||||||
|
|
||||||
|
var checkPos = function(o) {
|
||||||
|
var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
|
||||||
|
var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
|
||||||
|
var itemHeight = o.height, itemWidth = o.width;
|
||||||
|
var itemTop = o.top, itemLeft = o.left;
|
||||||
|
|
||||||
|
return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.each(inst.sortables, function(i) {
|
||||||
|
|
||||||
|
//Copy over some variables to allow calling the sortable's native _intersectsWith
|
||||||
|
this.instance.positionAbs = inst.positionAbs;
|
||||||
|
this.instance.helperProportions = inst.helperProportions;
|
||||||
|
this.instance.offset.click = inst.offset.click;
|
||||||
|
|
||||||
|
if(this.instance._intersectsWith(this.instance.containerCache)) {
|
||||||
|
|
||||||
|
//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
|
||||||
|
if(!this.instance.isOver) {
|
||||||
|
|
||||||
|
this.instance.isOver = 1;
|
||||||
|
//Now we fake the start of dragging for the sortable instance,
|
||||||
|
//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
|
||||||
|
//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
|
||||||
|
this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
|
||||||
|
this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
|
||||||
|
this.instance.options.helper = function() { return ui.helper[0]; };
|
||||||
|
|
||||||
|
event.target = this.instance.currentItem[0];
|
||||||
|
this.instance._mouseCapture(event, true);
|
||||||
|
this.instance._mouseStart(event, true, true);
|
||||||
|
|
||||||
|
//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
|
||||||
|
this.instance.offset.click.top = inst.offset.click.top;
|
||||||
|
this.instance.offset.click.left = inst.offset.click.left;
|
||||||
|
this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
|
||||||
|
this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
|
||||||
|
|
||||||
|
inst._trigger("toSortable", event);
|
||||||
|
inst.dropped = this.instance.element; //draggable revert needs that
|
||||||
|
//hack so receive/update callbacks work (mostly)
|
||||||
|
inst.currentItem = inst.element;
|
||||||
|
this.instance.fromOutside = inst;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
|
||||||
|
if(this.instance.currentItem) this.instance._mouseDrag(event);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//If it doesn't intersect with the sortable, and it intersected before,
|
||||||
|
//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
|
||||||
|
if(this.instance.isOver) {
|
||||||
|
|
||||||
|
this.instance.isOver = 0;
|
||||||
|
this.instance.cancelHelperRemoval = true;
|
||||||
|
|
||||||
|
//Prevent reverting on this forced stop
|
||||||
|
this.instance.options.revert = false;
|
||||||
|
|
||||||
|
// The out event needs to be triggered independently
|
||||||
|
this.instance._trigger('out', event, this.instance._uiHash(this.instance));
|
||||||
|
|
||||||
|
this.instance._mouseStop(event, true);
|
||||||
|
this.instance.options.helper = this.instance.options._helper;
|
||||||
|
|
||||||
|
//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
|
||||||
|
this.instance.currentItem.remove();
|
||||||
|
if(this.instance.placeholder) this.instance.placeholder.remove();
|
||||||
|
|
||||||
|
inst._trigger("fromSortable", event);
|
||||||
|
inst.dropped = false; //draggable revert needs that
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "cursor", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var t = $('body'), o = $(this).data('draggable').options;
|
||||||
|
if (t.css("cursor")) o._cursor = t.css("cursor");
|
||||||
|
t.css("cursor", o.cursor);
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var o = $(this).data('draggable').options;
|
||||||
|
if (o._cursor) $('body').css("cursor", o._cursor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "opacity", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var t = $(ui.helper), o = $(this).data('draggable').options;
|
||||||
|
if(t.css("opacity")) o._opacity = t.css("opacity");
|
||||||
|
t.css('opacity', o.opacity);
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var o = $(this).data('draggable').options;
|
||||||
|
if(o._opacity) $(ui.helper).css('opacity', o._opacity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "scroll", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var i = $(this).data("draggable");
|
||||||
|
if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
|
||||||
|
},
|
||||||
|
drag: function(event, ui) {
|
||||||
|
|
||||||
|
var i = $(this).data("draggable"), o = i.options, scrolled = false;
|
||||||
|
|
||||||
|
if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'x') {
|
||||||
|
if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
|
||||||
|
else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'y') {
|
||||||
|
if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
|
||||||
|
else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
|
||||||
|
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'x') {
|
||||||
|
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
||||||
|
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!o.axis || o.axis != 'y') {
|
||||||
|
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
||||||
|
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
|
||||||
|
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
|
||||||
|
$.ui.ddmanager.prepareOffsets(i, event);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "snap", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
|
||||||
|
var i = $(this).data("draggable"), o = i.options;
|
||||||
|
i.snapElements = [];
|
||||||
|
|
||||||
|
$(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
|
||||||
|
var $t = $(this); var $o = $t.offset();
|
||||||
|
if(this != i.element[0]) i.snapElements.push({
|
||||||
|
item: this,
|
||||||
|
width: $t.outerWidth(), height: $t.outerHeight(),
|
||||||
|
top: $o.top, left: $o.left
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
drag: function(event, ui) {
|
||||||
|
|
||||||
|
var inst = $(this).data("draggable"), o = inst.options;
|
||||||
|
var d = o.snapTolerance;
|
||||||
|
|
||||||
|
var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
|
||||||
|
y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
|
||||||
|
|
||||||
|
for (var i = inst.snapElements.length - 1; i >= 0; i--){
|
||||||
|
|
||||||
|
var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
|
||||||
|
t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
|
||||||
|
|
||||||
|
//Yes, I know, this is insane ;)
|
||||||
|
if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
|
||||||
|
if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
|
||||||
|
inst.snapElements[i].snapping = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o.snapMode != 'inner') {
|
||||||
|
var ts = Math.abs(t - y2) <= d;
|
||||||
|
var bs = Math.abs(b - y1) <= d;
|
||||||
|
var ls = Math.abs(l - x2) <= d;
|
||||||
|
var rs = Math.abs(r - x1) <= d;
|
||||||
|
if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
|
||||||
|
if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
|
||||||
|
if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
|
||||||
|
if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = (ts || bs || ls || rs);
|
||||||
|
|
||||||
|
if(o.snapMode != 'outer') {
|
||||||
|
var ts = Math.abs(t - y1) <= d;
|
||||||
|
var bs = Math.abs(b - y2) <= d;
|
||||||
|
var ls = Math.abs(l - x1) <= d;
|
||||||
|
var rs = Math.abs(r - x2) <= d;
|
||||||
|
if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
|
||||||
|
if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
|
||||||
|
if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
|
||||||
|
if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
|
||||||
|
(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
|
||||||
|
inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "stack", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
|
||||||
|
var o = $(this).data("draggable").options;
|
||||||
|
|
||||||
|
var group = $.makeArray($(o.stack)).sort(function(a,b) {
|
||||||
|
return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
|
||||||
|
});
|
||||||
|
if (!group.length) { return; }
|
||||||
|
|
||||||
|
var min = parseInt(group[0].style.zIndex) || 0;
|
||||||
|
$(group).each(function(i) {
|
||||||
|
this.style.zIndex = min + i;
|
||||||
|
});
|
||||||
|
|
||||||
|
this[0].style.zIndex = min + group.length;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.plugin.add("draggable", "zIndex", {
|
||||||
|
start: function(event, ui) {
|
||||||
|
var t = $(ui.helper), o = $(this).data("draggable").options;
|
||||||
|
if(t.css("zIndex")) o._zIndex = t.css("zIndex");
|
||||||
|
t.css('zIndex', o.zIndex);
|
||||||
|
},
|
||||||
|
stop: function(event, ui) {
|
||||||
|
var o = $(this).data("draggable").options;
|
||||||
|
if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
/*
|
||||||
|
* jQuery UI Droppable 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Droppables
|
||||||
|
*
|
||||||
|
* Depends:
|
||||||
|
* jquery.ui.core.js
|
||||||
|
* jquery.ui.widget.js
|
||||||
|
* jquery.ui.mouse.js
|
||||||
|
* jquery.ui.draggable.js
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
$.widget("ui.droppable", {
|
||||||
|
widgetEventPrefix: "drop",
|
||||||
|
options: {
|
||||||
|
accept: '*',
|
||||||
|
activeClass: false,
|
||||||
|
addClasses: true,
|
||||||
|
greedy: false,
|
||||||
|
hoverClass: false,
|
||||||
|
scope: 'default',
|
||||||
|
tolerance: 'intersect'
|
||||||
|
},
|
||||||
|
_create: function() {
|
||||||
|
|
||||||
|
var o = this.options, accept = o.accept;
|
||||||
|
this.isover = 0; this.isout = 1;
|
||||||
|
|
||||||
|
this.accept = $.isFunction(accept) ? accept : function(d) {
|
||||||
|
return d.is(accept);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Store the droppable's proportions
|
||||||
|
this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
|
||||||
|
|
||||||
|
// Add the reference and positions to the manager
|
||||||
|
$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
|
||||||
|
$.ui.ddmanager.droppables[o.scope].push(this);
|
||||||
|
|
||||||
|
(o.addClasses && this.element.addClass("ui-droppable"));
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
var drop = $.ui.ddmanager.droppables[this.options.scope];
|
||||||
|
for ( var i = 0; i < drop.length; i++ )
|
||||||
|
if ( drop[i] == this )
|
||||||
|
drop.splice(i, 1);
|
||||||
|
|
||||||
|
this.element
|
||||||
|
.removeClass("ui-droppable ui-droppable-disabled")
|
||||||
|
.removeData("droppable")
|
||||||
|
.unbind(".droppable");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_setOption: function(key, value) {
|
||||||
|
|
||||||
|
if(key == 'accept') {
|
||||||
|
this.accept = $.isFunction(value) ? value : function(d) {
|
||||||
|
return d.is(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$.Widget.prototype._setOption.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
_activate: function(event) {
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if(this.options.activeClass) this.element.addClass(this.options.activeClass);
|
||||||
|
(draggable && this._trigger('activate', event, this.ui(draggable)));
|
||||||
|
},
|
||||||
|
|
||||||
|
_deactivate: function(event) {
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
|
||||||
|
(draggable && this._trigger('deactivate', event, this.ui(draggable)));
|
||||||
|
},
|
||||||
|
|
||||||
|
_over: function(event) {
|
||||||
|
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
|
||||||
|
|
||||||
|
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
|
||||||
|
this._trigger('over', event, this.ui(draggable));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_out: function(event) {
|
||||||
|
|
||||||
|
var draggable = $.ui.ddmanager.current;
|
||||||
|
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
|
||||||
|
|
||||||
|
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
|
||||||
|
this._trigger('out', event, this.ui(draggable));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_drop: function(event,custom) {
|
||||||
|
|
||||||
|
var draggable = custom || $.ui.ddmanager.current;
|
||||||
|
if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
|
||||||
|
|
||||||
|
var childrenIntersection = false;
|
||||||
|
this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
|
||||||
|
var inst = $.data(this, 'droppable');
|
||||||
|
if(
|
||||||
|
inst.options.greedy
|
||||||
|
&& !inst.options.disabled
|
||||||
|
&& inst.options.scope == draggable.options.scope
|
||||||
|
&& inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
|
||||||
|
&& $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
|
||||||
|
) { childrenIntersection = true; return false; }
|
||||||
|
});
|
||||||
|
if(childrenIntersection) return false;
|
||||||
|
|
||||||
|
if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
|
||||||
|
if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
|
||||||
|
this._trigger('drop', event, this.ui(draggable));
|
||||||
|
return this.element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
ui: function(c) {
|
||||||
|
return {
|
||||||
|
draggable: (c.currentItem || c.element),
|
||||||
|
helper: c.helper,
|
||||||
|
position: c.position,
|
||||||
|
offset: c.positionAbs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$.extend($.ui.droppable, {
|
||||||
|
version: "1.8.16"
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ui.intersect = function(draggable, droppable, toleranceMode) {
|
||||||
|
|
||||||
|
if (!droppable.offset) return false;
|
||||||
|
|
||||||
|
var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
|
||||||
|
y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
|
||||||
|
var l = droppable.offset.left, r = l + droppable.proportions.width,
|
||||||
|
t = droppable.offset.top, b = t + droppable.proportions.height;
|
||||||
|
|
||||||
|
switch (toleranceMode) {
|
||||||
|
case 'fit':
|
||||||
|
return (l <= x1 && x2 <= r
|
||||||
|
&& t <= y1 && y2 <= b);
|
||||||
|
break;
|
||||||
|
case 'intersect':
|
||||||
|
return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
|
||||||
|
&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
|
||||||
|
&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
|
||||||
|
&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
|
||||||
|
break;
|
||||||
|
case 'pointer':
|
||||||
|
var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
|
||||||
|
draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
|
||||||
|
isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
|
||||||
|
return isOver;
|
||||||
|
break;
|
||||||
|
case 'touch':
|
||||||
|
return (
|
||||||
|
(y1 >= t && y1 <= b) || // Top edge touching
|
||||||
|
(y2 >= t && y2 <= b) || // Bottom edge touching
|
||||||
|
(y1 < t && y2 > b) // Surrounded vertically
|
||||||
|
) && (
|
||||||
|
(x1 >= l && x1 <= r) || // Left edge touching
|
||||||
|
(x2 >= l && x2 <= r) || // Right edge touching
|
||||||
|
(x1 < l && x2 > r) // Surrounded horizontally
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
This manager tracks offsets of draggables and droppables
|
||||||
|
*/
|
||||||
|
$.ui.ddmanager = {
|
||||||
|
current: null,
|
||||||
|
droppables: { 'default': [] },
|
||||||
|
prepareOffsets: function(t, event) {
|
||||||
|
|
||||||
|
var m = $.ui.ddmanager.droppables[t.options.scope] || [];
|
||||||
|
var type = event ? event.type : null; // workaround for #2317
|
||||||
|
var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
|
||||||
|
|
||||||
|
droppablesLoop: for (var i = 0; i < m.length; i++) {
|
||||||
|
|
||||||
|
if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
|
||||||
|
for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
|
||||||
|
m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
|
||||||
|
|
||||||
|
if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
|
||||||
|
|
||||||
|
m[i].offset = m[i].element.offset();
|
||||||
|
m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
drop: function(draggable, event) {
|
||||||
|
|
||||||
|
var dropped = false;
|
||||||
|
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
|
||||||
|
|
||||||
|
if(!this.options) return;
|
||||||
|
if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
|
||||||
|
dropped = dropped || this._drop.call(this, event);
|
||||||
|
|
||||||
|
if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
||||||
|
this.isout = 1; this.isover = 0;
|
||||||
|
this._deactivate.call(this, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return dropped;
|
||||||
|
|
||||||
|
},
|
||||||
|
dragStart: function( draggable, event ) {
|
||||||
|
//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
|
||||||
|
draggable.element.parents( ":not(body,html)" ).bind( "scroll.droppable", function() {
|
||||||
|
if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
|
||||||
|
});
|
||||||
|
},
|
||||||
|
drag: function(draggable, event) {
|
||||||
|
|
||||||
|
//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
|
||||||
|
if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
|
||||||
|
|
||||||
|
//Run through all droppables and check their positions based on specific tolerance options
|
||||||
|
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
|
||||||
|
|
||||||
|
if(this.options.disabled || this.greedyChild || !this.visible) return;
|
||||||
|
var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
|
||||||
|
|
||||||
|
var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
|
||||||
|
if(!c) return;
|
||||||
|
|
||||||
|
var parentInstance;
|
||||||
|
if (this.options.greedy) {
|
||||||
|
var parent = this.element.parents(':data(droppable):eq(0)');
|
||||||
|
if (parent.length) {
|
||||||
|
parentInstance = $.data(parent[0], 'droppable');
|
||||||
|
parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we just moved into a greedy child
|
||||||
|
if (parentInstance && c == 'isover') {
|
||||||
|
parentInstance['isover'] = 0;
|
||||||
|
parentInstance['isout'] = 1;
|
||||||
|
parentInstance._out.call(parentInstance, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
|
||||||
|
this[c == "isover" ? "_over" : "_out"].call(this, event);
|
||||||
|
|
||||||
|
// we just moved out of a greedy child
|
||||||
|
if (parentInstance && c == 'isout') {
|
||||||
|
parentInstance['isout'] = 0;
|
||||||
|
parentInstance['isover'] = 1;
|
||||||
|
parentInstance._over.call(parentInstance, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
dragStop: function( draggable, event ) {
|
||||||
|
draggable.element.parents( ":not(body,html)" ).unbind( "scroll.droppable" );
|
||||||
|
//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
|
||||||
|
if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*!
|
||||||
|
* jQuery UI Mouse 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Mouse
|
||||||
|
*
|
||||||
|
* Depends:
|
||||||
|
* jquery.ui.widget.js
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
var mouseHandled = false;
|
||||||
|
$( document ).mouseup( function( e ) {
|
||||||
|
mouseHandled = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$.widget("ui.mouse", {
|
||||||
|
options: {
|
||||||
|
cancel: ':input,option',
|
||||||
|
distance: 1,
|
||||||
|
delay: 0
|
||||||
|
},
|
||||||
|
_mouseInit: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.element
|
||||||
|
.bind('mousedown.'+this.widgetName, function(event) {
|
||||||
|
return self._mouseDown(event);
|
||||||
|
})
|
||||||
|
.bind('click.'+this.widgetName, function(event) {
|
||||||
|
if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) {
|
||||||
|
$.removeData(event.target, self.widgetName + '.preventClickEvent');
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.started = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: make sure destroying one instance of mouse doesn't mess with
|
||||||
|
// other instances of mouse
|
||||||
|
_mouseDestroy: function() {
|
||||||
|
this.element.unbind('.'+this.widgetName);
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDown: function(event) {
|
||||||
|
// don't let more than one widget handle mouseStart
|
||||||
|
if( mouseHandled ) { return };
|
||||||
|
|
||||||
|
// we may have missed mouseup (out of window)
|
||||||
|
(this._mouseStarted && this._mouseUp(event));
|
||||||
|
|
||||||
|
this._mouseDownEvent = event;
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
btnIsLeft = (event.which == 1),
|
||||||
|
// event.target.nodeName works around a bug in IE 8 with
|
||||||
|
// disabled inputs (#7620)
|
||||||
|
elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
|
||||||
|
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mouseDelayMet = !this.options.delay;
|
||||||
|
if (!this.mouseDelayMet) {
|
||||||
|
this._mouseDelayTimer = setTimeout(function() {
|
||||||
|
self.mouseDelayMet = true;
|
||||||
|
}, this.options.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
||||||
|
this._mouseStarted = (this._mouseStart(event) !== false);
|
||||||
|
if (!this._mouseStarted) {
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click event may never have fired (Gecko & Opera)
|
||||||
|
if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
|
||||||
|
$.removeData(event.target, this.widgetName + '.preventClickEvent');
|
||||||
|
}
|
||||||
|
|
||||||
|
// these delegates are required to keep context
|
||||||
|
this._mouseMoveDelegate = function(event) {
|
||||||
|
return self._mouseMove(event);
|
||||||
|
};
|
||||||
|
this._mouseUpDelegate = function(event) {
|
||||||
|
return self._mouseUp(event);
|
||||||
|
};
|
||||||
|
$(document)
|
||||||
|
.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
|
||||||
|
.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
mouseHandled = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseMove: function(event) {
|
||||||
|
// IE mouseup check - mouseup happened when mouse was out of window
|
||||||
|
if ($.browser.msie && !(document.documentMode >= 9) && !event.button) {
|
||||||
|
return this._mouseUp(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mouseStarted) {
|
||||||
|
this._mouseDrag(event);
|
||||||
|
return event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
||||||
|
this._mouseStarted =
|
||||||
|
(this._mouseStart(this._mouseDownEvent, event) !== false);
|
||||||
|
(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this._mouseStarted;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseUp: function(event) {
|
||||||
|
$(document)
|
||||||
|
.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
|
||||||
|
.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
|
||||||
|
|
||||||
|
if (this._mouseStarted) {
|
||||||
|
this._mouseStarted = false;
|
||||||
|
|
||||||
|
if (event.target == this._mouseDownEvent.target) {
|
||||||
|
$.data(event.target, this.widgetName + '.preventClickEvent', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mouseStop(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDistanceMet: function(event) {
|
||||||
|
return (Math.max(
|
||||||
|
Math.abs(this._mouseDownEvent.pageX - event.pageX),
|
||||||
|
Math.abs(this._mouseDownEvent.pageY - event.pageY)
|
||||||
|
) >= this.options.distance
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_mouseDelayMet: function(event) {
|
||||||
|
return this.mouseDelayMet;
|
||||||
|
},
|
||||||
|
|
||||||
|
// These are placeholder methods, to be overriden by extending plugin
|
||||||
|
_mouseStart: function(event) {},
|
||||||
|
_mouseDrag: function(event) {},
|
||||||
|
_mouseStop: function(event) {},
|
||||||
|
_mouseCapture: function(event) { return true; }
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,268 @@
|
||||||
|
/*!
|
||||||
|
* jQuery UI Widget 1.8.16
|
||||||
|
*
|
||||||
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
* http://jquery.org/license
|
||||||
|
*
|
||||||
|
* http://docs.jquery.com/UI/Widget
|
||||||
|
*/
|
||||||
|
(function( $, undefined ) {
|
||||||
|
|
||||||
|
// jQuery 1.4+
|
||||||
|
if ( $.cleanData ) {
|
||||||
|
var _cleanData = $.cleanData;
|
||||||
|
$.cleanData = function( elems ) {
|
||||||
|
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
|
||||||
|
try {
|
||||||
|
$( elem ).triggerHandler( "remove" );
|
||||||
|
// http://bugs.jquery.com/ticket/8235
|
||||||
|
} catch( e ) {}
|
||||||
|
}
|
||||||
|
_cleanData( elems );
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var _remove = $.fn.remove;
|
||||||
|
$.fn.remove = function( selector, keepData ) {
|
||||||
|
return this.each(function() {
|
||||||
|
if ( !keepData ) {
|
||||||
|
if ( !selector || $.filter( selector, [ this ] ).length ) {
|
||||||
|
$( "*", this ).add( [ this ] ).each(function() {
|
||||||
|
try {
|
||||||
|
$( this ).triggerHandler( "remove" );
|
||||||
|
// http://bugs.jquery.com/ticket/8235
|
||||||
|
} catch( e ) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _remove.call( $(this), selector, keepData );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$.widget = function( name, base, prototype ) {
|
||||||
|
var namespace = name.split( "." )[ 0 ],
|
||||||
|
fullName;
|
||||||
|
name = name.split( "." )[ 1 ];
|
||||||
|
fullName = namespace + "-" + name;
|
||||||
|
|
||||||
|
if ( !prototype ) {
|
||||||
|
prototype = base;
|
||||||
|
base = $.Widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create selector for plugin
|
||||||
|
$.expr[ ":" ][ fullName ] = function( elem ) {
|
||||||
|
return !!$.data( elem, name );
|
||||||
|
};
|
||||||
|
|
||||||
|
$[ namespace ] = $[ namespace ] || {};
|
||||||
|
$[ namespace ][ name ] = function( options, element ) {
|
||||||
|
// allow instantiation without initializing for simple inheritance
|
||||||
|
if ( arguments.length ) {
|
||||||
|
this._createWidget( options, element );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var basePrototype = new base();
|
||||||
|
// we need to make the options hash a property directly on the new instance
|
||||||
|
// otherwise we'll modify the options hash on the prototype that we're
|
||||||
|
// inheriting from
|
||||||
|
// $.each( basePrototype, function( key, val ) {
|
||||||
|
// if ( $.isPlainObject(val) ) {
|
||||||
|
// basePrototype[ key ] = $.extend( {}, val );
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
basePrototype.options = $.extend( true, {}, basePrototype.options );
|
||||||
|
$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
|
||||||
|
namespace: namespace,
|
||||||
|
widgetName: name,
|
||||||
|
widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
|
||||||
|
widgetBaseClass: fullName
|
||||||
|
}, prototype );
|
||||||
|
|
||||||
|
$.widget.bridge( name, $[ namespace ][ name ] );
|
||||||
|
};
|
||||||
|
|
||||||
|
$.widget.bridge = function( name, object ) {
|
||||||
|
$.fn[ name ] = function( options ) {
|
||||||
|
var isMethodCall = typeof options === "string",
|
||||||
|
args = Array.prototype.slice.call( arguments, 1 ),
|
||||||
|
returnValue = this;
|
||||||
|
|
||||||
|
// allow multiple hashes to be passed on init
|
||||||
|
options = !isMethodCall && args.length ?
|
||||||
|
$.extend.apply( null, [ true, options ].concat(args) ) :
|
||||||
|
options;
|
||||||
|
|
||||||
|
// prevent calls to internal methods
|
||||||
|
if ( isMethodCall && options.charAt( 0 ) === "_" ) {
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isMethodCall ) {
|
||||||
|
this.each(function() {
|
||||||
|
var instance = $.data( this, name ),
|
||||||
|
methodValue = instance && $.isFunction( instance[options] ) ?
|
||||||
|
instance[ options ].apply( instance, args ) :
|
||||||
|
instance;
|
||||||
|
// TODO: add this back in 1.9 and use $.error() (see #5972)
|
||||||
|
// if ( !instance ) {
|
||||||
|
// throw "cannot call methods on " + name + " prior to initialization; " +
|
||||||
|
// "attempted to call method '" + options + "'";
|
||||||
|
// }
|
||||||
|
// if ( !$.isFunction( instance[options] ) ) {
|
||||||
|
// throw "no such method '" + options + "' for " + name + " widget instance";
|
||||||
|
// }
|
||||||
|
// var methodValue = instance[ options ].apply( instance, args );
|
||||||
|
if ( methodValue !== instance && methodValue !== undefined ) {
|
||||||
|
returnValue = methodValue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.each(function() {
|
||||||
|
var instance = $.data( this, name );
|
||||||
|
if ( instance ) {
|
||||||
|
instance.option( options || {} )._init();
|
||||||
|
} else {
|
||||||
|
$.data( this, name, new object( options, this ) );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Widget = function( options, element ) {
|
||||||
|
// allow instantiation without initializing for simple inheritance
|
||||||
|
if ( arguments.length ) {
|
||||||
|
this._createWidget( options, element );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Widget.prototype = {
|
||||||
|
widgetName: "widget",
|
||||||
|
widgetEventPrefix: "",
|
||||||
|
options: {
|
||||||
|
disabled: false
|
||||||
|
},
|
||||||
|
_createWidget: function( options, element ) {
|
||||||
|
// $.widget.bridge stores the plugin instance, but we do it anyway
|
||||||
|
// so that it's stored even before the _create function runs
|
||||||
|
$.data( element, this.widgetName, this );
|
||||||
|
this.element = $( element );
|
||||||
|
this.options = $.extend( true, {},
|
||||||
|
this.options,
|
||||||
|
this._getCreateOptions(),
|
||||||
|
options );
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.element.bind( "remove." + this.widgetName, function() {
|
||||||
|
self.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._create();
|
||||||
|
this._trigger( "create" );
|
||||||
|
this._init();
|
||||||
|
},
|
||||||
|
_getCreateOptions: function() {
|
||||||
|
return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
|
||||||
|
},
|
||||||
|
_create: function() {},
|
||||||
|
_init: function() {},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this.element
|
||||||
|
.unbind( "." + this.widgetName )
|
||||||
|
.removeData( this.widgetName );
|
||||||
|
this.widget()
|
||||||
|
.unbind( "." + this.widgetName )
|
||||||
|
.removeAttr( "aria-disabled" )
|
||||||
|
.removeClass(
|
||||||
|
this.widgetBaseClass + "-disabled " +
|
||||||
|
"ui-state-disabled" );
|
||||||
|
},
|
||||||
|
|
||||||
|
widget: function() {
|
||||||
|
return this.element;
|
||||||
|
},
|
||||||
|
|
||||||
|
option: function( key, value ) {
|
||||||
|
var options = key;
|
||||||
|
|
||||||
|
if ( arguments.length === 0 ) {
|
||||||
|
// don't return a reference to the internal hash
|
||||||
|
return $.extend( {}, this.options );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof key === "string" ) {
|
||||||
|
if ( value === undefined ) {
|
||||||
|
return this.options[ key ];
|
||||||
|
}
|
||||||
|
options = {};
|
||||||
|
options[ key ] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setOptions( options );
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
_setOptions: function( options ) {
|
||||||
|
var self = this;
|
||||||
|
$.each( options, function( key, value ) {
|
||||||
|
self._setOption( key, value );
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
_setOption: function( key, value ) {
|
||||||
|
this.options[ key ] = value;
|
||||||
|
|
||||||
|
if ( key === "disabled" ) {
|
||||||
|
this.widget()
|
||||||
|
[ value ? "addClass" : "removeClass"](
|
||||||
|
this.widgetBaseClass + "-disabled" + " " +
|
||||||
|
"ui-state-disabled" )
|
||||||
|
.attr( "aria-disabled", value );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function() {
|
||||||
|
return this._setOption( "disabled", false );
|
||||||
|
},
|
||||||
|
disable: function() {
|
||||||
|
return this._setOption( "disabled", true );
|
||||||
|
},
|
||||||
|
|
||||||
|
_trigger: function( type, event, data ) {
|
||||||
|
var callback = this.options[ type ];
|
||||||
|
|
||||||
|
event = $.Event( event );
|
||||||
|
event.type = ( type === this.widgetEventPrefix ?
|
||||||
|
type :
|
||||||
|
this.widgetEventPrefix + type ).toLowerCase();
|
||||||
|
data = data || {};
|
||||||
|
|
||||||
|
// copy original event properties over to the new event
|
||||||
|
// this would happen if we could call $.event.fix instead of $.Event
|
||||||
|
// but we don't have a way to force an event to be fixed multiple times
|
||||||
|
if ( event.originalEvent ) {
|
||||||
|
for ( var i = $.event.props.length, prop; i; ) {
|
||||||
|
prop = $.event.props[ --i ];
|
||||||
|
event[ prop ] = event.originalEvent[ prop ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element.trigger( event, data );
|
||||||
|
|
||||||
|
return !( $.isFunction(callback) &&
|
||||||
|
callback.call( this.element[0], event, data ) === false ||
|
||||||
|
event.isDefaultPrevented() );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})( jQuery );
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>AdminSortable Test Project</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.defaults import patterns, url, include
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
|
admin.autodiscover()
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
(r'^admin/', include(admin.site.urls)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.SERVE_STATIC_MEDIA:
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
(r'^' + settings.MEDIA_URL.lstrip('/'), include('appmedia.urls')),
|
||||||
|
) + urlpatterns
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,27 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
try:
|
||||||
|
README = open('README.rst').read()
|
||||||
|
except:
|
||||||
|
README = None
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='django-admin-sortable',
|
||||||
|
version=__import__('adminsortable').__version__,
|
||||||
|
description='Drag and drop sorting for models and tabular inline models in Django admin',
|
||||||
|
long_description=README,
|
||||||
|
author='Brandon Taylor',
|
||||||
|
author_email='btaylorweb@gmail.com',
|
||||||
|
url='http://btaylorweb.com/',
|
||||||
|
packages=find_packages(exclude=['sample_project']),
|
||||||
|
zip_safe=False,
|
||||||
|
include_package_data=True,
|
||||||
|
classifiers=['Development Status :: 4 - Beta',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Framework :: Django',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: Apache Software License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Topic :: Utilities'],
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue