Merge pull request #333 from SchrodingersGat/secondary-modals

Provide an after_render callback
This commit is contained in:
Oliver 2019-05-14 23:52:54 +10:00 committed by GitHub
commit 35d32fd2ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 213 additions and 83 deletions

View File

@ -245,6 +245,7 @@ class AjaxCreateView(AjaxMixin, CreateView):
# Return the PK of the newly-created object
data['pk'] = obj.pk
data['text'] = str(obj)
try:
data['url'] = obj.get_absolute_url()

View File

@ -34,7 +34,6 @@ InvenTree | Allocate Parts
{% block js_load %}
{{ block.super }}
<script src="{% static 'script/inventree/api.js' %}"></script>
<script src="{% static 'script/inventree/part.js' %}"></script>
<script src="{% static 'script/inventree/build.js' %}"></script>
{% endblock %}

View File

@ -670,6 +670,7 @@ class BomItem(models.Model):
# A link to the parent part
# Each part will get a reverse lookup field 'bom_items'
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
help_text='Select parent part',
limit_choices_to={
'buildable': True,
'active': True,
@ -678,16 +679,17 @@ class BomItem(models.Model):
# A link to the child item (sub-part)
# Each part will get a reverse lookup field 'used_in'
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in',
help_text='Select part to be used in BOM',
limit_choices_to={
'consumable': True,
'active': True
})
# Quantity required
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)])
quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0)], help_text='BOM quantity for this BOM item')
# Note attached to this BOM line item
note = models.CharField(max_length=100, blank=True, help_text='Item notes')
note = models.CharField(max_length=100, blank=True, help_text='BOM item notes')
def clean(self):
""" Check validity of the BomItem model.
@ -767,7 +769,7 @@ class SupplierPart(models.Model):
MPN = models.CharField(max_length=100, blank=True, help_text='Manufacturer part number')
URL = models.URLField(blank=True)
URL = models.URLField(blank=True, help_text='URL for external supplier part link')
description = models.CharField(max_length=250, blank=True, help_text='Supplier part description')

View File

@ -56,8 +56,6 @@
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/part.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/bom.js' %}"></script>
{% endblock %}
@ -83,7 +81,15 @@
{
success: function() {
$("#bom-table").bootstrapTable('refresh');
}
},
secondary: [
{
field: 'sub_part',
label: 'New Part',
title: 'Create New Part',
url: "{% url 'part-create' %}",
},
]
}
);
});

View File

@ -61,8 +61,6 @@
{% endblock %}
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javacript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}
{% block js_ready %}
{{ block.super }}
@ -82,15 +80,30 @@
$("#part-create").click(function() {
launchModalForm(
"{% url 'part-create' %}",
{
follow: true,
data: {
{% if category %}
category: {{ category.id }}
{% endif %}
}
});
"{% url 'part-create' %}",
{
follow: true,
data: {
{% if category %}
category: {{ category.id }}
{% endif %}
},
secondary: [
{
field: 'category',
label: 'New Category',
title: 'Create new Part Category',
url: "{% url 'category-create' %}",
},
{
field: 'default_location',
label: 'New Location',
title: 'Create new Stock Location',
url: "{% url 'stock-location-create' %}",
}
]
}
);
});
{% if category %}

View File

@ -20,6 +20,7 @@
<li><a href="#" id='deactivate-part' title='Deactivate part'>Deactivate</a></li>
{% else %}
<li><a href="#" id='activate-part' title='Activate part'>Activate</a></li>
<li><a href='#' id='delete-part' title='Delete part'>Delete</a></li>
{% endif %}
</ul>
</div>
@ -128,8 +129,6 @@
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}
{% block js_ready %}
@ -200,10 +199,10 @@
$('#delete-part').click(function() {
launchModalForm(
"{% url 'part-delete' part.id %}",
{
redirect: {% if part.category %}"{% url 'category-detail' part.category.id %}"{% else %}"{% url 'part-index' %}"{% endif %}
});
"{% url 'part-delete' part.id %}",
{
redirect: {% if part.category %}"{% url 'category-detail' part.category.id %}"{% else %}"{% url 'part-index' %}"{% endif %}
});
});
{% endblock %}

View File

@ -29,6 +29,7 @@ InvenTree | Part List
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/part.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}
{% block js_ready %}

View File

@ -1,6 +1,11 @@
{% extends "modal_form.html" %}
{% block pre_form_content %}
Are you sure you want to delete part '{{ part.full_name }}'?
{% if part.used_in_count %}
<hr>
<p>This part is used in BOMs for {{ part.used_in_count }} other parts. If you delete this part, the BOMs for the following parts will be updated:
<ul class="list-group">
{% for child in part.used_in.all %}
@ -10,6 +15,7 @@ Are you sure you want to delete part '{{ part.full_name }}'?
{% endif %}
{% if part.locations.all|length > 0 %}
<hr>
<p>There are {{ part.locations.all|length }} stock entries defined for this part. If you delete this part, the following stock entries will also be deleted:
<ul class='list-group'>
{% for stock in part.locations.all %}
@ -20,6 +26,7 @@ Are you sure you want to delete part '{{ part.full_name }}'?
{% endif %}
{% if part.supplier_parts.all|length > 0 %}
<hr>
<p>There are {{ part.supplier_parts.all|length }} suppliers defined for this part. If you delete this part, the following supplier parts will also be deleted.
<ul class='list-group'>
{% for spart in part.supplier_parts.all %}
@ -30,5 +37,8 @@ Are you sure you want to delete part '{{ part.full_name }}'?
{% endif %}
{% if part.serials.all|length > 0 %}
<hr>
<p>There are {{ part.serials.all|length }} unique parts tracked for '{{ part.full_name }}'. Deleting this part will permanently remove this tracking information.</p>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -37,8 +37,6 @@
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}
{% block js_ready %}
{{ block.super }}

View File

@ -202,6 +202,7 @@ class PartDuplicate(AjaxCreateView):
part = form.save()
data['pk'] = part.pk
data['text'] = str(part)
deep_copy = str2bool(request.POST.get('deep_copy', False))
@ -321,6 +322,7 @@ class PartCreate(AjaxCreateView):
part = form.save()
data['pk'] = part.pk
data['text'] = str(part)
try:
data['url'] = part.get_absolute_url()

View File

@ -51,7 +51,7 @@
background: #eee;
display: none;
position: absolute;
z-index: 999;
z-index: 400;
border: 1px solid #555;
max-width: 250px;
}
@ -159,7 +159,15 @@
.modal {
overflow: hidden;
z-index: 99999999;
z-index: 9999;
}
.modal-primary {
z-index: 10000;
}
.modal-secondary {
z-index: 11000;
}
.js-modal-form .checkbox {
@ -170,6 +178,11 @@
width: 45%;
}
.modal-secondary .modal-dialog {
width: 40%;
padding-top: 15px;
}
.modal-content h3 {
margin-top: 3px;
margin-bottom: 3px;
@ -192,6 +205,16 @@
width: 100%;
}
/* Force a control-label div to be 100% width */
.modal .control-label {
width: 100%;
}
.modal .control-label .btn {
padding-top: 3px;
padding-bottom: 3px;
}
/* The side navigation menu */
.sidenav {
height: 100%; /* 100% Full-height */
@ -217,7 +240,7 @@
margin-top: 20px;
width: 100%;
padding: 20px;
z-index: 999999;
z-index: 5000;
pointer-events: none; // Prevent this div from blocking links underneath
}

View File

@ -102,7 +102,7 @@ function afterForm(response, options) {
// Was a callback provided?
if (options.success) {
options.success();
options.success(response);
}
else if (options.follow && response.url) {
window.location.href = response.url;
@ -354,6 +354,81 @@ function injectModalForm(modal, form_html) {
}
function insertNewItemButton(modal, options) {
/* Insert a button into a modal form, after a field label.
* Looks for a <label> tag inside the form with the attribute "for='id_<field>'"
* Inserts a button at the end of this lael element.
*/
var html = "<span style='float: right;'>";
html += "<div type='button' class='btn btn-primary'";
if (options.title) {
html += " title='" + options.title + "'";
}
html += " id='btn-new-" + options.field + "'>" + options.label + "</div>";
html += "</span>";
$(modal).find('label[for="id_'+ options.field + '"]').append(html);
}
function attachSecondaryModal(modal, options) {
/* Attach a secondary modal form to the primary modal form.
* Inserts a button into the primary form which, when clicked,
* will launch the secondary modal to do /something/ and then return a result.
*
* options:
* field: Name of the field to attach to
* label: Button text
* title: Hover text to display over button (optional)
* url: URL for the secondary modal
* query: Query params for the secondary modal
*/
// Insert the button
insertNewItemButton(modal, options);
// Add a callback to the button
$(modal).find("#btn-new-" + options.field).on('click', function() {
// Launch the secondary modal
launchModalForm(
options.url,
{
modal: '#modal-form-secondary',
success: function(response) {
/* A successful object creation event should return a response which contains:
* - pk: ID of the newly created object
* - text: String descriptor of the newly created object
*
* So it is simply a matter of appending and selecting the new object!
*/
var select = '#id_' + options.field;
var option = new Option(response.text, response.pk, true, true)
$(modal).find(select).append(option).trigger('change');
}
}
);
});
}
function attachSecondaries(modal, secondaries) {
/* Attach a provided list of secondary modals */
for (var i = 0; i < secondaries.length; i++) {
attachSecondaryModal(modal, secondaries[i]);
}
}
function handleModalForm(url, options) {
/* Update a modal form after data are received from the server.
* Manages POST requests until the form is successfully submitted.
@ -401,6 +476,14 @@ function handleModalForm(url, options) {
else {
if (response.html_form) {
injectModalForm(modal, response.html_form);
if (options.after_render) {
options.after_render(modal, response);
}
if (options.secondary) {
attachSecondaries(modal, options.secondary);
}
}
else {
$(modal).modal('hide');
@ -443,6 +526,8 @@ function launchModalForm(url, options = {}) {
* submit_text - Text for the submit button (default = 'Submit')
* close_text - Text for the close button (default = 'Close')
* no_post - If true, only display form data, hide submit button, and disallow POST
* after_render - Callback function to run after form is rendered
* secondary - List of secondary modals to attach
*/
var modal = options.modal || '#modal-form';
@ -475,6 +560,14 @@ function launchModalForm(url, options = {}) {
if (response.html_form) {
injectModalForm(modal, response.html_form);
if (options.after_render) {
options.after_render(modal, response);
}
if (options.secondary) {
attachSecondaries(modal, options.secondary);
}
if (options.no_post) {
modalShowSubmitButton(modal, false);
} else {

View File

@ -345,32 +345,6 @@ function moveStockItems(items, options) {
});
}
function deleteStockItems(items, options) {
var modal = '#modal-delete';
if ('modal' in options) {
modal = options.modal;
}
if (items.length == 0) {
alert('No stock items selected');
return;
}
function doDelete(parts) {
//TODO
}
openModal({
modal: modal,
title: 'Delete ' + items.length + ' stock items'
});
}
function loadStockTable(table, options) {
table.bootstrapTable({

View File

@ -32,8 +32,8 @@ class CreateStockItemForm(HelperForm):
'part',
'supplier_part',
'location',
'batch',
'quantity',
'batch',
'delete_on_deplete',
'status',
'notes',

View File

@ -69,8 +69,6 @@
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}
{% block js_ready %}
{{ block.super }}
@ -123,9 +121,24 @@
{% if location %}
location: {{ location.id }}
{% endif %}
}
},
secondary: [
{
field: 'part',
label: 'New Part',
title: 'Create New Part',
url: "{% url 'part-create' %}",
},
{
field: 'location',
label: 'New Location',
title: 'Create New Location',
url: "{% url 'stock-location-create' %}",
}
]
});
return false;
});

View File

@ -25,7 +25,6 @@ InvenTree | Stock
{% block js_load %}
{{ block.super }}
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/stock.js' %}"></script>
{% endblock %}

View File

@ -95,10 +95,9 @@ InvenTree
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/api.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/part.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/tables.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/modals.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
{% block js_load %}
{% endblock %}

View File

@ -1,4 +1,4 @@
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-form'>
<div class='modal fade modal-fixed-footer modal-primary' tabindex='-1' role='dialog' id='modal-form'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
@ -17,27 +17,25 @@
</div>
</div>
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-delete'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h3 id='modal-title'>Confirm Item Deletion</h3>
</div>
<form action='' method='post' id='delete-form'>
{% csrf_token %}
</form>
<div class='modal-form-content'>
</div>
<div class='modal-footer'>
<button type='button' class='btn btn-default' data-dismiss='modal'>Cancel</button>
<button type='button' class='btn btn-danger' id='modal-form-delete'>Delete</button>
<div class='modal fade modal-fixed-footer modal-secondary' tabindex='-1' role='dialog' id='modal-form-secondary'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h3 id='modal-title'>Form Title Here</h3>
</div>
<div class='modal-form-content'>
</div>
<div class='modal-footer'>
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>Close</button>
<button type='button' class='btn btn-primary' id='modal-form-submit'>Submit</button>
</div>
</div>
</div>
</div>
</div>
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-question-dialog'>
<div class='modal-dialog'>