From da943bf6a86e6ec53ad7ad382b71af84285ff15b Mon Sep 17 00:00:00 2001 From: Zain Memon Date: Sun, 5 Jul 2009 07:07:49 +0000 Subject: [PATCH] [soc2009/admin-ui] Reordering selector inlines. happy 4th! git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/admin-ui@11179 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/css/base.css | 10 + django/contrib/admin/media/css/forms.css | 19 +- .../js/jquery-disable.text.select.pack.js | 19 ++ .../admin/media/js/jquery-listreorder.js | 289 ++++++++++++++++++ django/contrib/admin/options.py | 3 +- .../templates/admin/edit_inline/selector.html | 34 ++- .../templates/admin/edit_inline/tabular.html | 2 +- 7 files changed, 354 insertions(+), 22 deletions(-) create mode 100755 django/contrib/admin/media/js/jquery-disable.text.select.pack.js create mode 100755 django/contrib/admin/media/js/jquery-listreorder.js diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css index d7fc72cb5f..13e5fe1b1d 100644 --- a/django/contrib/admin/media/css/base.css +++ b/django/contrib/admin/media/css/base.css @@ -350,6 +350,16 @@ table.orderable-initalized .order-cell, body>tr>td.order-cell { background-color: #F6F6F6; } +.dragHandle { + height: 20px; + width: 20px; + float: right; + cursor: move; + background-color: #fff; + border: 1px solid #f00; + display: block; +} + .empty_form { display: none; } diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css index 8bd062662f..836848cf46 100644 --- a/django/contrib/admin/media/css/forms.css +++ b/django/contrib/admin/media/css/forms.css @@ -233,10 +233,6 @@ fieldset.monospace textarea { width: 8em; } -.inline-related { - position: relative; -} - .inline-related h3 { margin: 0; color: #666; @@ -247,10 +243,7 @@ fieldset.monospace textarea { } .inline-related h3 span.delete { - padding-left: 20px; - position: absolute; - top: 2px; - right: 10px; + float: right; } .inline-related h3 span.delete label { @@ -329,24 +322,26 @@ fieldset.monospace textarea { display: none; } -.inline-selector { +ul.inline-selector { float: left; width: 19%; + padding: 0; } .inline-selector a, .inline-selector a:visited { color: #000; } -.inline-selector-item { +li.inline-selector-item { background-color: #F6F6F6; border: 1px solid #E7E7E7; padding: 5px; margin: 7px 0px 7px 5px; font-size: 0.9em; + list-style-type: none; } -.inline-selector-item:hover { +li.inline-selector-item:hover { background-color: #C1DBFD; } @@ -360,7 +355,7 @@ fieldset.monospace textarea { float: right; } -.inline-selected { +li.inline-selected { background-color: #9EB7D5; } diff --git a/django/contrib/admin/media/js/jquery-disable.text.select.pack.js b/django/contrib/admin/media/js/jquery-disable.text.select.pack.js new file mode 100755 index 0000000000..30d391c440 --- /dev/null +++ b/django/contrib/admin/media/js/jquery-disable.text.select.pack.js @@ -0,0 +1,19 @@ +/** + * .disableTextSelect - Disable Text Select Plugin + * + * Version: 1.1 + * Updated: 2007-11-28 + * + * Used to stop users from selecting text + * + * Copyright (c) 2007 James Dempster (letssurf@gmail.com, http://www.jdempster.com/category/jquery/disabletextselect/) + * + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + **/ + +/** + * Requirements: + * - jQuery (John Resig, http://www.jquery.com/) + **/ +(function($){if($.browser.mozilla){$.fn.disableTextSelect=function(){return this.each(function(){$(this).css({"MozUserSelect":"none"})})};$.fn.enableTextSelect=function(){return this.each(function(){$(this).css({"MozUserSelect":""})})}}else{if($.browser.msie){$.fn.disableTextSelect=function(){return this.each(function(){$(this).bind("selectstart.disableTextSelect",function(){return false})})};$.fn.enableTextSelect=function(){return this.each(function(){$(this).unbind("selectstart.disableTextSelect")})}}else{$.fn.disableTextSelect=function(){return this.each(function(){$(this).bind("mousedown.disableTextSelect",function(){return false})})};$.fn.enableTextSelect=function(){return this.each(function(){$(this).unbind("mousedown.disableTextSelect")})}}}})(jQuery) diff --git a/django/contrib/admin/media/js/jquery-listreorder.js b/django/contrib/admin/media/js/jquery-listreorder.js new file mode 100755 index 0000000000..fb6128592b --- /dev/null +++ b/django/contrib/admin/media/js/jquery-listreorder.js @@ -0,0 +1,289 @@ +/* +Copyright (c) 2009 Jordan Bach, http://www.utdallas.edu/~jrb048000/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* +List Reorder + +Enables drag-and-drop reordering of list items for any simple ordered
    or unordered
  1. list. + +Author: Jordan Bach +Version: 0.1 +Created: January 1, 2009 +License: MIT License + +Constructor + $(expr).ListOrder(options) + +Methods: + makeDefaultOrder - sets the current list order to [0, 1, 2, ...] + restoreOrder - returns the list to its original order + +Events: + listorderchanged - Fired after a list item is dropped. The 2nd argument + is a jQuery object representing the list that fired the event. + The 3rd argument is an array where the values represent + the original index of each list item. + +Options: + itemHoverClass : 'itemHover', + dragTargetClass : 'dragTarget', + dropTargetClass : 'dropTarget', + dragHandleClass : 'dragHandle' + +*/ + +(function($){ + +$.fn.ListReorder = function (options) { + + $.fn.ListReorder.defaults = { + itemHoverClass : 'itemHover', + dragTargetClass : 'dragTarget', + dropTargetClass : 'dropTarget', + dragHandleClass : 'dragHandle', + useDefaultDragHandle : false + }; + + var opts = $.extend({}, $.fn.ListReorder.defaults, options); + + return this.each(function() { + var theList = $(this), // The list (
      |
        ) + theItems = $('li', theList), // All
      1. elements in the list + dragActive = false, // Are we currently dragging an item? + dropTarget = null, // The list placeholder + dragTarget = null, // The element currently being dragged + dropIndex = -1, // The list index of the dropTarget + offset = {}, // Positions the mouse in the dragTarget + listOrder = [], // Keeps track of order relative to original order + ref = this; + + theList.mouseout(ul_mouseout); + + // Create the drag target + dragTarget = $('
        '); + dragTarget.insertAfter(theList); + dragTarget.hide(); + dragTarget.css('position', 'absolute'); + dragTarget.addClass(opts.dragTargetClass); + + for (var i = 0; i < theItems.length; i++) + listOrder.push(i); + + resetList(); + + function resetList() { + theItems = $('li', theList), + + // For each
      2. in the list + theItems.each(function() { + var li = $(this); + + var dragHandle = $(''); + dragHandle.addClass(opts.dragHandleClass) + .mouseover(li_mouseover) + .mousedown(dragHandle_mousedown); + + if (opts.useDefaultDragHandle) + dragHandle.css({ + 'display' : 'block', + 'float' : 'right', + 'width' : '10px', + 'height' : '10px', + 'border' : '2px solid #333', + 'background' : '#ccc', + 'cursor' : 'move' + }); + + $('.' + opts.dragHandleClass, li).remove(); + li.prepend(dragHandle); + }); + + clearListItemStyles(); + } + + // Return all list items to their default state + function clearListItemStyles() { + theItems.each(function() { + var li = $(this); + li.removeClass(opts.itemHoverClass); + li.removeClass(opts.dropTargetClass); + }); + } + + // Handle any cleanup when the mouse leaves the list + function ul_mouseout() { + if (!dragActive) + clearListItemStyles(); + } + + // Add a hover class to a list item on mouseover + function li_mouseover() { + if (!dragActive) { + clearListItemStyles(); + $(this).parent().addClass(opts.itemHoverClass); + } + } + + // Prepare the list for dragging an item + function dragHandle_mousedown(e) { + var li = $(this).parent(); + + dragActive = true; + dropIndex = theItems.index(li); + + // Show the drag target + dragTarget.html(li.html()); + dragTarget.css('display', 'block'); + offset.top = e.pageY - li.offset().top; + offset.left = e.pageX - li.offset().left; + updateDragTargetPos(e); + + // Insert the placeholder + dropTarget = li; + dropTarget.html(''); + dropTarget.css('height', dragTarget.css('height')); + dragTarget.css('width', dropTarget.width() + 'px'); + dropTarget.addClass(opts.dropTargetClass); + + // Disable Text and DOM selection + $(document).disableTextSelect(); + + $(document).mouseup(dragHandle_mouseup); + $(document).mousemove(document_mousemove); + } + + // If this were on the element, we could lose the drag on the element + // if we move the mouse too fast + function document_mousemove(e) { + if (dragActive) { + // drag target follows mouse cursor + updateDragTargetPos(e); + + // Don't do mess with drop index if we are above or below the list + if (y_mid(dragTarget) > y_bot(theList) + || y_mid(dragTarget) < y_top(theList)) { + return; + } + + // detect position of drag target relative to list items + // and swap drop target and neighboring item if necessary + if (y_mid(dragTarget) + 5 < y_top(dropTarget)) { + swapListItems(dropIndex, --dropIndex); + } else if (y_mid(dragTarget) - 5 > y_bot(dropTarget)) { + swapListItems(dropIndex, ++dropIndex); + } + } + } + + function dragHandle_mouseup() { + // Restore the drop target + dropTarget.html(dragTarget.html()); + dropTarget.removeClass(opts.dragTargetClass); + dropTarget = null; + + // Hide the drag target + dragTarget.css('display', 'none'); + + dragActive = false; + dragTarget.unbind('mouseup', dragHandle_mouseup); + $(document).unbind('mousemove', document_mousemove); + resetList(); + + theList.trigger('listorderchanged', [theList, listOrder]); + + // Re-enable text selection + $(document).enableTextSelect(); + $(document).unbind('mouseup', dragHandle_mouseup); + } + + function updateDragTargetPos(e) { + dragTarget.css({ + 'top' : e.pageY - offset.top + 'px', + 'left' : e.pageX - offset.left + 'px' + }); + } + + // Change the order of two list items + function swapListItems(oldDropIndex, newDropIndex) { + // keep indices in bounds + if (dropIndex < 0) { + dropIndex = 0; + return; + } else if (dropIndex >= theItems.length) { + dropIndex = theItems.length - 1; + return; + } + + var t = listOrder[oldDropIndex]; + listOrder[oldDropIndex] = listOrder[newDropIndex]; + listOrder[newDropIndex] = t; + + // swap list items + var oldDropTarget = theItems.get(oldDropIndex), + newDropTarget = theItems.get(newDropIndex), + temp1 = $(oldDropTarget).clone(true); + temp2 = $(newDropTarget).clone(true); + + $(oldDropTarget).replaceWith(temp2) + .mouseover(li_mouseover) + .mousedown(dragHandle_mousedown); + $(newDropTarget).replaceWith(temp1) + .mouseover(li_mouseover) + .mousedown(dragHandle_mousedown); + + // reset so it is valid on next use + theItems = $('li', theList); + dropTarget = $(theItems.get(newDropIndex)); + } + + function y_top(jq) { + return jq.offset().top; + } + + function y_mid(jq) { + return (y_top(jq) + y_bot(jq)) / 2 + } + + function y_bot(jq) { + return jq.offset().top + jq.outerHeight(); + } + + this.makeDefaultOrder = function() { + for (var i = 0; i < listOrder.length; i++) + listOrder[i] = i; + } + + this.restoreOrder = function() { + for (var i = 0; i < theItems.length; i++) { + if (i != listOrder[i]) { + var k = 0; + for (; k < listOrder.length; k++) + if (listOrder[k] == i) + break; + swapListItems(i, k); + } + } + theList.trigger('listorderchanged', [theList, listOrder]); + } + }); +} +})(jQuery); diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c97ef588fd..9d8c862615 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1129,7 +1129,8 @@ class InlineModelAdmin(BaseModelAdmin): if self.filter_vertical or self.filter_horizontal: js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) if self.order_field: - js.append('js/jquery-tablednd.js') + js.extend(['js/jquery-tablednd.js', 'js/jquery-disable.text.select.pack.js', + 'js/jquery-listreorder.js']) return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) media = property(_media) diff --git a/django/contrib/admin/templates/admin/edit_inline/selector.html b/django/contrib/admin/templates/admin/edit_inline/selector.html index 5da586ff1d..82fa4bbc1f 100644 --- a/django/contrib/admin/templates/admin/edit_inline/selector.html +++ b/django/contrib/admin/templates/admin/edit_inline/selector.html @@ -1,25 +1,26 @@ {% load i18n %} -
        -
    {% for inline_admin_form in inline_admin_formset %} @@ -29,7 +30,7 @@ {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} {% else %} - [New Inline] + {{ inline_admin_formset.opts.verbose_name|title}}: #{{ forloop.counter }} {% endif %} {% if inline_admin_formset.formset.can_delete %} @@ -104,5 +105,22 @@ $(function() { return false; }); + + {% if inline_admin_formset.opts.order_field %} + /* Reordering Inlines */ + var list = $('ul.inline-selector').ListReorder({ useDefaultDragHandle: true}); + var id_prefix = '#{{ inline_admin_formset.opts.verbose_name}}'; + + list.bind('listorderchanged', function(evt, jq_list, list_order) { + counter = 1; + $(id_prefix + '-group a.inline-select').each(function (i) { + if (counter < list_order.length) { + $('.inline-detail #' + $(this).attr('title') + + ' input[id$="{{ inline_admin_formset.opts.order_field }}"]').val(counter++); + } + }); + }); + + {% endif %} }); \ No newline at end of file diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 760d9918ca..716a008e1c 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -132,7 +132,7 @@ $(function() { $("table.orderable tbody tr").unbind('mouseenter').unbind('mouseleave'); }, onDrop: function(table, row_obj) { - var counter = 0; + var counter = 1; $(row_obj).parent().find('input[id$="{{ inline_admin_formset.opts.order_field }}"]') .each(function (i) {