mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-03 09:43:33 +02:00
Merge pull request #1943 from cisagov/rjm/1854-sticky-submit-bar
Issues #1854: Sticky submit bar on domain request change_form - (rjm)
This commit is contained in:
commit
4e9ab57a59
12 changed files with 262 additions and 27 deletions
|
@ -979,6 +979,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
"""Custom domain requests admin class."""
|
"""Custom domain requests admin class."""
|
||||||
|
|
||||||
form = DomainRequestAdminForm
|
form = DomainRequestAdminForm
|
||||||
|
change_form_template = "django/admin/domain_request_change_form.html"
|
||||||
|
|
||||||
class InvestigatorFilter(admin.SimpleListFilter):
|
class InvestigatorFilter(admin.SimpleListFilter):
|
||||||
"""Custom investigator filter that only displays users with the manager role"""
|
"""Custom investigator filter that only displays users with the manager role"""
|
||||||
|
@ -1040,8 +1041,6 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
if self.value() == "0":
|
if self.value() == "0":
|
||||||
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
|
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
|
||||||
|
|
||||||
change_form_template = "django/admin/domain_request_change_form.html"
|
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
|
|
@ -410,3 +410,60 @@ function enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, elementPk,
|
||||||
});
|
});
|
||||||
observer.observe({ type: "navigation" });
|
observer.observe({ type: "navigation" });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/** An IIFE for toggling the submit bar on domain request forms
|
||||||
|
*/
|
||||||
|
(function (){
|
||||||
|
// Get a reference to the button element
|
||||||
|
const toggleButton = document.getElementById('submitRowToggle');
|
||||||
|
const submitRowWrapper = document.querySelector('.submit-row-wrapper');
|
||||||
|
|
||||||
|
if (toggleButton) {
|
||||||
|
// Add event listener to toggle the class and update content on click
|
||||||
|
toggleButton.addEventListener('click', function() {
|
||||||
|
// Toggle the 'collapsed' class on the bar
|
||||||
|
submitRowWrapper.classList.toggle('submit-row-wrapper--collapsed');
|
||||||
|
|
||||||
|
// Get a reference to the span element inside the button
|
||||||
|
const spanElement = this.querySelector('span');
|
||||||
|
|
||||||
|
// Get a reference to the use element inside the button
|
||||||
|
const useElement = this.querySelector('use');
|
||||||
|
|
||||||
|
// Check if the span element text is 'Hide'
|
||||||
|
if (spanElement.textContent.trim() === 'Hide') {
|
||||||
|
// Update the span element text to 'Show'
|
||||||
|
spanElement.textContent = 'Show';
|
||||||
|
|
||||||
|
// Update the xlink:href attribute to expand_more
|
||||||
|
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_less');
|
||||||
|
} else {
|
||||||
|
// Update the span element text to 'Hide'
|
||||||
|
spanElement.textContent = 'Hide';
|
||||||
|
|
||||||
|
// Update the xlink:href attribute to expand_less
|
||||||
|
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_more');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// We have a scroll indicator at the end of the page.
|
||||||
|
// Observe it. Once it gets on screen, test to see if the row is collapsed.
|
||||||
|
// If it is, expand it.
|
||||||
|
const targetElement = document.querySelector(".scroll-indicator");
|
||||||
|
const options = {
|
||||||
|
threshold: 1
|
||||||
|
};
|
||||||
|
// Create a new Intersection Observer
|
||||||
|
const observer = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
// Refresh reference to submit row wrapper and check if it's collapsed
|
||||||
|
if (document.querySelector('.submit-row-wrapper').classList.contains('submit-row-wrapper--collapsed')) {
|
||||||
|
toggleButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, options);
|
||||||
|
observer.observe(targetElement);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -422,6 +422,84 @@ address.dja-address-contact-list {
|
||||||
border: 1px solid var(--error-fg) !important;
|
border: 1px solid var(--error-fg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let's define this block of code once and use it for analysts over a certain screen size,
|
||||||
|
// superusers over another screen size.
|
||||||
|
@mixin submit-row-wrapper--collapsed-one-line(){
|
||||||
|
&.submit-row-wrapper--collapsed {
|
||||||
|
transform: translate3d(0, 42px, 0);
|
||||||
|
}
|
||||||
|
.submit-row {
|
||||||
|
clear: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky submit bar for domain requests on desktop
|
||||||
|
@media screen and (min-width:768px) {
|
||||||
|
.submit-row-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 338px;
|
||||||
|
background: var(--darkened-bg);
|
||||||
|
border-top-left-radius: 6px;
|
||||||
|
transition: transform .2s ease-out;
|
||||||
|
.submit-row {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submit-row-wrapper--collapsed {
|
||||||
|
// translate3d is more performant than translateY
|
||||||
|
// https://stackoverflow.com/questions/22111256/translate3d-vs-translate-performance
|
||||||
|
transform: translate3d(0, 88px, 0);
|
||||||
|
}
|
||||||
|
.submit-row-wrapper--collapsed-one-line {
|
||||||
|
@include submit-row-wrapper--collapsed-one-line();
|
||||||
|
}
|
||||||
|
.submit-row {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.submit-row-toggle{
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
right: 0;
|
||||||
|
background: var(--darkened-bg);
|
||||||
|
}
|
||||||
|
#submitRowToggle {
|
||||||
|
color: var(--body-fg);
|
||||||
|
}
|
||||||
|
.requested-domain-sticky {
|
||||||
|
max-width: 325px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible-768 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:768px) {
|
||||||
|
.visible-768 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:935px) {
|
||||||
|
// Analyst only class
|
||||||
|
.submit-row-wrapper--analyst-view {
|
||||||
|
@include submit-row-wrapper--collapsed-one-line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:1256px) {
|
||||||
|
.submit-row-wrapper {
|
||||||
|
@include submit-row-wrapper--collapsed-one-line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collapse button styles for fieldsets
|
||||||
.module.collapse {
|
.module.collapse {
|
||||||
margin-top: -35px;
|
margin-top: -35px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
|
@ -117,6 +117,10 @@ abbr[title] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visible-desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@include at-media(desktop) {
|
@include at-media(desktop) {
|
||||||
.float-right-desktop {
|
.float-right-desktop {
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -124,33 +128,15 @@ abbr[title] {
|
||||||
.float-left-desktop {
|
.float-left-desktop {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
.visible-desktop {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-end {
|
.flex-end {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only apply this custom wrapping to desktop
|
.cursor-pointer {
|
||||||
@include at-media(desktop) {
|
cursor: pointer;
|
||||||
.usa-tooltip__body {
|
|
||||||
width: 350px;
|
|
||||||
white-space: normal;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include at-media(tablet) {
|
|
||||||
.usa-tooltip__body {
|
|
||||||
width: 250px !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include at-media(mobile) {
|
|
||||||
.usa-tooltip__body {
|
|
||||||
width: 250px !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,13 @@ a.usa-button--unstyled:visited {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usa-button--unstyled--white,
|
||||||
|
.usa-button--unstyled--white:hover,
|
||||||
|
.usa-button--unstyled--white:focus,
|
||||||
|
.usa-button--unstyled--white:active {
|
||||||
|
color: color('white');
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel button used on the
|
// Cancel button used on the
|
||||||
// DNSSEC main page
|
// DNSSEC main page
|
||||||
// We want to center this button on mobile
|
// We want to center this button on mobile
|
||||||
|
|
26
src/registrar/assets/sass/_theme/_tooltips.scss
Normal file
26
src/registrar/assets/sass/_theme/_tooltips.scss
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@use "uswds-core" as *;
|
||||||
|
|
||||||
|
// Only apply this custom wrapping to desktop
|
||||||
|
@include at-media(desktop) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 350px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(tablet) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(mobile) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
@forward "lists";
|
@forward "lists";
|
||||||
@forward "buttons";
|
@forward "buttons";
|
||||||
@forward "forms";
|
@forward "forms";
|
||||||
|
@forward "tooltips";
|
||||||
@forward "fieldsets";
|
@forward "fieldsets";
|
||||||
@forward "alerts";
|
@forward "alerts";
|
||||||
@forward "tables";
|
@forward "tables";
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends 'admin/change_form.html' %}
|
{% extends 'admin/change_form.html' %}
|
||||||
|
{% load custom_filters %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% block field_sets %}
|
{% block field_sets %}
|
||||||
|
@ -102,5 +103,25 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# submit-row-wrapper--analyst-view is a class that manages layout on certain screens for analysts only #}
|
||||||
|
<div class="submit-row-wrapper{% if not request.user|has_permission:'registrar.full_access_permission' %} submit-row-wrapper--analyst-view{% endif %}">
|
||||||
|
|
||||||
|
<span class="submit-row-toggle padding-1 padding-right-2 visible-desktop">
|
||||||
|
<button type="button" class="usa-button usa-button--unstyled" id="submitRowToggle">
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
|
<use xlink:href="{%static 'img/sprite.svg'%}#expand_more"></use>
|
||||||
|
</svg>
|
||||||
|
<span>Hide</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p class="text-right margin-top-2 padding-right-2 margin-bottom-0 requested-domain-sticky float-right visible-768">
|
||||||
|
<strong>Requested domain:</strong> {{ original.requested_domain.name }}
|
||||||
|
</p>
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="scroll-indicator"></span>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
<td>
|
<td>
|
||||||
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
|
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
|
||||||
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
|
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
|
||||||
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline" value="Cancel">
|
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -62,3 +62,8 @@ def get_organization_long_name(generic_org_type):
|
||||||
return "Error"
|
return "Error"
|
||||||
|
|
||||||
return long_form_type
|
return long_form_type
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="has_permission")
|
||||||
|
def has_permission(user, permission):
|
||||||
|
return user.has_perm(permission)
|
||||||
|
|
|
@ -1213,6 +1213,61 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
self.assertEqual(domain_request.requested_domain.name, domain_request.approved_domain.name)
|
self.assertEqual(domain_request.requested_domain.name, domain_request.approved_domain.name)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
def test_sticky_submit_row(self):
|
||||||
|
"""Test that the change_form template contains strings indicative of the customization
|
||||||
|
of the sticky submit bar.
|
||||||
|
|
||||||
|
Also test that it does NOT contain a CSS class meant for analysts only when logged in as superuser."""
|
||||||
|
|
||||||
|
# make sure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
|
||||||
|
# Create a sample domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Create a mock request
|
||||||
|
request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
|
||||||
|
|
||||||
|
# Since we're using client to mock the request, we can only test against
|
||||||
|
# non-interpolated values
|
||||||
|
expected_content = "<strong>Requested domain:</strong>"
|
||||||
|
expected_content2 = '<span class="scroll-indicator"></span>'
|
||||||
|
expected_content3 = '<div class="submit-row-wrapper">'
|
||||||
|
not_expected_content = "submit-row-wrapper--analyst-view>"
|
||||||
|
self.assertContains(request, expected_content)
|
||||||
|
self.assertContains(request, expected_content2)
|
||||||
|
self.assertContains(request, expected_content3)
|
||||||
|
self.assertNotContains(request, not_expected_content)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_sticky_submit_row_has_extra_class_for_analysts(self):
|
||||||
|
"""Test that the change_form template contains strings indicative of the customization
|
||||||
|
of the sticky submit bar.
|
||||||
|
|
||||||
|
Also test that it DOES contain a CSS class meant for analysts only when logged in as analyst."""
|
||||||
|
|
||||||
|
# make sure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
|
||||||
|
# Create a sample domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Create a mock request
|
||||||
|
request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
|
||||||
|
|
||||||
|
# Since we're using client to mock the request, we can only test against
|
||||||
|
# non-interpolated values
|
||||||
|
expected_content = "<strong>Requested domain:</strong>"
|
||||||
|
expected_content2 = '<span class="scroll-indicator"></span>'
|
||||||
|
expected_content3 = '<div class="submit-row-wrapper submit-row-wrapper--analyst-view">'
|
||||||
|
self.assertContains(request, expected_content)
|
||||||
|
self.assertContains(request, expected_content2)
|
||||||
|
self.assertContains(request, expected_content3)
|
||||||
|
|
||||||
def test_other_contacts_has_readonly_link(self):
|
def test_other_contacts_has_readonly_link(self):
|
||||||
"""Tests if the readonly other_contacts field has links"""
|
"""Tests if the readonly other_contacts field has links"""
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue