Browse Source

Implement related popups

Denis K 8 years ago
parent
commit
31d67a578d

+ 0 - 0
jet/static/admin/js/admin/RelatedObjectLookups.js


+ 0 - 0
jet/static/admin/js/related-widget-wrapper.js


+ 46 - 20
jet/static/jet/css/_changeform.scss

@@ -566,26 +566,6 @@ body.popup .submit-row {
 
 /* RELATED FIELD ADD ONE / LOOKUP */
 
-.add-another, .related-lookup {
-    margin-left: 5px;
-    display: inline-block;
-    vertical-align: middle;
-    background-repeat: no-repeat;
-    background-size: 14px;
-}
-
-.add-another {
-    width: 16px;
-    height: 16px;
-    background-image: url(../img/icon-addlink.svg);
-}
-
-.related-lookup {
-    width: 16px;
-    height: 16px;
-    background-image: url(../img/search.svg);
-}
-
 form .related-widget-wrapper ul {
     display: inline-block;
     margin-left: 0;
@@ -606,6 +586,52 @@ form .related-widget-wrapper ul {
   }
 }
 
+.related-widget-wrapper-link {
+  opacity: 0.5;
+  @include transition(opacity $transitions-duration);
+
+  &:link {
+    opacity: 1;
+  }
+}
+
+.related-widget-wrapper-image {
+  &:before {
+    @include font-icon;
+    font-size: 20px;
+    vertical-align: middle;
+  }
+
+  &.add-related {
+    &:before {
+      content: $icon-add3;
+    }
+  }
+
+  &.change-related {
+    &:before {
+      content: $icon-edit;
+    }
+  }
+
+  &.delete-related {
+    &:before {
+      content: $icon-cross;
+    }
+  }
+}
+
+.related-lookup {
+  margin-left: 6px;
+
+  &:before {
+    @include font-icon;
+    font-size: 20px;
+    vertical-align: middle;
+    content: $icon-search;
+  }
+}
+
 /* TABS */
 
 .changeform-tabs {

+ 4 - 0
jet/static/jet/css/_helpers.scss

@@ -61,6 +61,10 @@
   transition: all $duration;
 }
 
+@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
+@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
+@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
+
 @function prefix($property, $prefixes: (webkit moz o ms)) {
   $vendor-prefixed-properties: transform background-clip background-size;
   $result: ();

+ 2 - 13
jet/static/jet/css/_relatedpopup.scss

@@ -2,18 +2,6 @@
 
 /* POPUP */
 
-//.popup #content {
-//    padding: 20px;
-//}
-//
-//.popup #container {
-//    min-width: 0;
-//}
-//
-//.popup #header {
-//    padding: 10px 20px;
-//}
-
 .related-popup {
   position: absolute;
   top: 0;
@@ -49,6 +37,7 @@
       left: 50%;
       margin-left: -48px;
       margin-top: -48px;
+      animation: spin 4s linear infinite;
     }
   }
 
@@ -71,7 +60,7 @@
       @include transition(background-color $transitions-duration, color $transitions-duration);
     }
 
-    &:hover {
+    &:hover, &:focus {
       background: $background-color;
       color: $text-color;
     }

+ 0 - 31
jet/static/jet/js/src/features/related-objects.js

@@ -1,31 +0,0 @@
-var $ = require('jquery');
-
-var initRelatedPopups = function() {
-    var closeRelatedPopup = function () {
-        var $popups = $('.related-popup');
-        var $container = $('.related-popup-container');
-        var $popup = $popups.last();
-
-        $popup.remove();
-
-        if ($popups.length == 1) {
-            $container.fadeOut(200, 'swing', function () {
-                $('.related-popup-back').hide();
-                $('body').removeClass('non-scrollable');
-            });
-        }
-    };
-
-    $('.related-popup-back').on('click', function (e) {
-        e.preventDefault();
-        closeRelatedPopup();
-    });
-
-    $(window).on('related-popup:close', function () {
-        closeRelatedPopup();
-    });
-};
-
-$(document).ready(function() {
-    initRelatedPopups();
-});

+ 4 - 0
jet/static/jet/js/src/features/side-menu.js

@@ -356,6 +356,10 @@ var initScrollbars = function() {
 };
 
 $(document).ready(function() {
+    if ($('.sidebar').length == 0) {
+        return;
+    }
+
     initSideMenu();
     initScrollbars();
 });

+ 243 - 0
jet/static/jet/js/src/layout-updaters/related-widget-wrapper.js

@@ -0,0 +1,243 @@
+var $ = require('jquery');
+
+var RelatedWidgetWrapperUpdater = function() { };
+
+RelatedWidgetWrapperUpdater.prototype = {
+    replaceLinkIcon: function(selector, className) {
+        var $img = $(selector);
+
+        $('<span>')
+            .addClass('related-widget-wrapper-image')
+            .addClass(className)
+            .insertAfter($img);
+        $img.remove();
+    },
+    updateLinkIcons: function() {
+        this.replaceLinkIcon(
+            'img[src*="admin/img/icon-addlink"], img[src*="admin/img/icon_addlink"]',
+            'add-related'
+        );
+        this.replaceLinkIcon(
+            'img[src*="admin/img/icon-changelink"]',
+            'change-related'
+        );
+        this.replaceLinkIcon(
+            'img[src*="admin/img/icon-deletelink"]',
+            'delete-related'
+        );
+        $('img[src*="admin/img/selector-search"]').remove();
+    },
+    updateLinks: function($select) {
+        $select.find('~ .change-related, ~ .delete-related, ~ .add-another').each(function() {
+            var $link = $(this);
+            var hrefTemplate = $link.data('href-template');
+            var value = $select.val();
+
+            if (hrefTemplate == undefined) {
+                return;
+            }
+
+            if (value) {
+                $link.attr('href', hrefTemplate.replace('__fk__', value))
+            } else {
+                $link.removeAttr('href');
+            }
+        });
+    },
+    initLinks: function() {
+        var obj = this;
+
+        $('.form-row select').each(function() {
+            var $select = $(this);
+
+            obj.updateLinks($select);
+
+            $select.find('~ .add-related, ~ .change-related, ~ .delete-related, ~ .add-another').each(function() {
+                var $link = $(this);
+
+                $link.on('click', function(e) {
+                    e.preventDefault();
+
+                    var href = $link.attr('href');
+
+                    if (href != undefined) {
+                        if (href.indexOf('_popup') == -1) {
+                            href += (href.indexOf('?') == -1) ? '?_popup=1' : '&_popup=1';
+                        }
+
+                        obj.showPopup($select, href);
+                    }
+                });
+            });
+        }).on('change', function() {
+            obj.updateLinks($(this));
+        });
+
+        $('.form-row input').each(function() {
+            var $input = $(this);
+
+            $input.find('~ .related-lookup').each(function() {
+                var $link = $(this);
+
+                $link.on('click', function(e) {
+                    e.preventDefault();
+
+                    var href = $link.attr('href');
+
+                    href += (href.indexOf('?') == -1) ? '?_popup=1' : '&_popup=1';
+
+                    obj.showPopup($input, href);
+                });
+            });
+        });
+    },
+    initPopupBackButton: function() {
+        var obj = this;
+
+        $('.related-popup-back').on('click', function(e) {
+            e.preventDefault();
+            obj.closePopup();
+        });
+    },
+    showPopup: function($input, href) {
+        var $document = $(window.top.document);
+        var $container = $document.find('.related-popup-container');
+        var $loading = $container.find('.loading-indicator');
+        var $body = $document.find('body').addClass('non-scrollable');
+        var $popup = $('<iframe>')
+            .attr('name', name)
+            .attr('src', href)
+            .data('input', $input)
+            .addClass('related-popup')
+            .on('load', function() {
+                $popup.add($document.find('.related-popup-back')).fadeIn(200, 'swing', function() {
+                    $loading.hide();
+                });
+            });
+
+        $loading.show();
+        $container.fadeIn(200, 'swing', function() {
+            $container.append($popup);
+        });
+        $body.addClass('non-scrollable');
+    },
+    closePopup: function(response) {
+        (function($) {
+            var $document = $(window.top.document);
+            var $popups = $document.find('.related-popup');
+            var $container = $document.find('.related-popup-container');
+            var $popup = $popups.last();
+
+            if (response != undefined) {
+                var $input = $popup.data('input');
+
+                switch (response.action) {
+                    case 'change':
+                        $input.find('option').each(function() {
+                            var $option = $(this);
+
+                            if ($option.val() == response.value) {
+                                $option.html(response.obj).val(response.new_value);
+                            }
+                        });
+
+                        $input.trigger('change').trigger('select:init');
+
+                        break;
+                    case 'delete':
+                        $input.find('option').each(function() {
+                            var $option = $(this);
+
+                            if ($option.val() == response.value) {
+                                $option.remove();
+                            }
+                        });
+
+                        $input.trigger('change').trigger('select:init');
+
+                        break;
+                    default:
+                        if ($input.is('select')) {
+                            var $option = $('<option>')
+                                .val(response.value)
+                                .html(response.obj);
+
+                            $input.append($option);
+                            $option.attr('selected', true);
+
+                            $input
+                                .trigger('change')
+                                .trigger('select:init');
+                        } else if ($input.is('input.vManyToManyRawIdAdminField') && $input.val()) {
+                            $input.val($input.val() + ',' + response.value);
+                        } else if ($input.is('input')) {
+                            $input.val(response.value);
+                        }
+
+                        break;
+                }
+            }
+
+            if ($popups.length == 1) {
+                $container.fadeOut(200, 'swing', function() {
+                    $document.find('.related-popup-back').hide();
+                    $document.find('body').removeClass('non-scrollable');
+                    $popup.remove();
+                });
+            } else {
+                $popup.remove();
+            }
+        })(window.parent.jet.jQuery);
+    },
+    processPopupResponse: function() {
+        var obj = this;
+
+        $('#django-admin-popup-response-constants').each(function() {
+            var $constants = $(this);
+            var response = $constants.data('popup-response');
+
+            obj.closePopup(response);
+        });
+    },
+    overrideRelatedGlobals: function() {
+        var obj = this;
+
+        window.showRelatedObjectLookupPopup
+            = window.showAddAnotherPopup
+            = window.showRelatedObjectPopup
+            = function() { };
+
+        window.opener = window.parent;
+        window.dismissRelatedLookupPopup = function(win, chosenId) {
+            obj.closePopup({
+                action: 'lookup',
+                value: chosenId
+            });
+        };
+    },
+    initDeleteRelatedCancellation: function() {
+        var obj = this;
+
+        $('.popup.delete-confirmation .cancel-link').on('click', function(e) {
+            e.preventDefault();
+            obj.closePopup();
+        }).removeAttr('onclick');
+    },
+    run: function() {
+        try {
+            this.updateLinkIcons();
+            this.initLinks();
+            this.initPopupBackButton();
+            this.processPopupResponse();
+            this.overrideRelatedGlobals();
+            this.initDeleteRelatedCancellation();
+            this.overrideRelatedGlobals();
+        } catch (e) {
+            console.error(e);
+        }
+    }
+};
+
+$(document).ready(function() {
+    new RelatedWidgetWrapperUpdater().run();
+});

+ 7 - 1
jet/static/jet/js/src/main.js

@@ -1,3 +1,9 @@
+var $ = require('jquery');
+
+jet = {
+    jQuery: $
+};
+
 require('./layout-updaters/actions');
 require('./layout-updaters/breadcrumbs');
 require('./layout-updaters/paginator');
@@ -6,6 +12,7 @@ require('./layout-updaters/user-tools');
 require('./layout-updaters/changeform-tabs');
 require('./layout-updaters/tabular-inline');
 require('./layout-updaters/stacked-inline');
+require('./layout-updaters/related-widget-wrapper');
 require('./features/side-menu');
 require('./features/filters');
 require('./features/changeform-tabs');
@@ -18,6 +25,5 @@ require('./features/tooltips');
 require('./features/dashboard');
 require('./features/changeform');
 require('./features/themes');
-require('./features/related-objects');
 require('./features/siblings');
 require('./features/selects');

+ 13 - 0
jet/templates/admin/popup_response.html

@@ -0,0 +1,13 @@
+{% load i18n static jet_tags %}<!DOCTYPE html>
+<html>
+  <head>
+      <title>{% trans 'Popup closing...' %}</title>
+      <script src="{% static "jet/js/build/bundle.min.js" %}"></script>
+  </head>
+  <body>
+    {% popup_response_data as data %}
+    <div id="django-admin-popup-response-constants"
+         data-popup-response="{{ data|escape }}">
+    </div>
+  </body>
+</html>

+ 14 - 0
jet/templatetags/jet_tags.py

@@ -1,4 +1,5 @@
 from __future__ import unicode_literals
+import json
 import django
 from django import template
 from django.core.urlresolvers import reverse
@@ -364,3 +365,16 @@ def jet_previous_object_url(context):
 @register.assignment_tag(takes_context=True)
 def jet_next_object_url(context):
     return jet_sibling_object_url(context, True)
+
+
+@register.assignment_tag(takes_context=True)
+def popup_response_data(context):
+    if context.get('popup_response_data'):
+        return context['popup_response_data']
+
+    return json.dumps({
+        'action': context.get('action'),
+        'value': context.get('value') or context.get('pk_value'),
+        'obj': str(context.get('obj')),
+        'new_value': context.get('new_value')
+    })