Skip to content

Commit

Permalink
Merge pull request #527 from TelosLabs/feature/nonprofit-unsaved-chan…
Browse files Browse the repository at this point in the history
…ges-alert

Feature/nonprofit unsaved changes alert
  • Loading branch information
aliciapaz authored Mar 13, 2024
2 parents 4d4397d + 92a908d commit dd3ecf8
Show file tree
Hide file tree
Showing 22 changed files with 269 additions and 110 deletions.
11 changes: 10 additions & 1 deletion app/components/select_multiple/component.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<%= content_tag :div, options do %>
<div class="flex flex-wrap flex-shrink-0 max-w-full gap-2 p-2" data-select-multiple-target="badgesContainer">
</div>
<input id="<%= @required ? 'required':'' %>" type="text" data-select-multiple-target="input" class="w-48 h-full pt-3 text-sm align-middle border-0 border-transparent scroll-mt-25 focus:outline-none focus:border-transparent focus:ring-0 rounded-6px" data-action="select-multiple#search" placeholder="<%= @placeholder %>">

<input
id="<%= @required ? 'required':'' %>"
type="text"
data-select-multiple-target="input"
data-detect-form-changes-target="utilityInput"
class="w-48 h-full pt-3 text-sm align-middle border-0 border-transparent scroll-mt-25 focus:outline-none focus:border-transparent focus:ring-0 rounded-6px"
data-action="select-multiple#search"
placeholder="<%= @placeholder %>">

<div data-search-target="advancedFiltersCheckboxes" data-extend-dropdown-target="menu" class="absolute left-0 z-10 hidden w-full mt-2 top-full max-h-52" >
<div class="p-5 overflow-y-auto bg-white border rounded border-gray-5 max-h-52">
<div class="flex flex-col gap-y-2">
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def new

def edit
@organization = Organization.find(params[:id])
@form_presenter = OrganizationFormPresenter.new

authorize @organization
set_form_data
end
Expand Down
72 changes: 72 additions & 0 deletions app/javascript/controllers/detect_form_changes_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["form", "utilityInput"];
static values = {
inputTypes: String,
}

connect() {
const initialFormInputs = [...this.formTarget.querySelectorAll(this.inputTypesValue)];
this.formTarget.initialNumberOfInputs = this.validInputsLength(initialFormInputs);
this.formTarget.changed = false;
}

captureUserInput(event) {
const input = event.target;

if (this.utilityInputTargets.includes(input) || this.eventAndInputIncompatible(event, input)) {
return;
}

this.detectChanges(this.formTarget);
}

detectChanges(form, newInputAdded = false) {
const didFormChange = newInputAdded || this.changesInForm(form);
form.changed = didFormChange;
}

changesInForm(form) {
const currentFormInputs = [...form.querySelectorAll(this.inputTypesValue)];

return (this.validInputsLength(currentFormInputs) !== form.initialNumberOfInputs) ||
currentFormInputs.some(input => this.inputValueChanged(input));
}

inputValueChanged(input) {
if (input.type === "checkbox" || input.type === "radio") {
return input.checked !== input.defaultChecked
}

if (input.type === "select-one") {
const selectedOption = input.options[input.selectedIndex];
return selectedOption.selected !== selectedOption.defaultSelected;
}

return input.value.trim() !== input.defaultValue;
}

// Some inputs don't send any data
validInputsLength(currentFormInputs) {
const validInputs = currentFormInputs.filter(input => {
return !this.utilityInputTargets.includes(input);
});

return validInputs.length;
}

// `change` event is more suitable for input types that involve user selection or choice.
eventAndInputIncompatible(event, input) {
const inputIsSelectable =
["radio", "checkbox", "select-one", "select-multiple"].includes(input.type);

return (event.type === "input" && inputIsSelectable) ||
(event.type === "change" && !inputIsSelectable);
}

// users can add or remove inputs
captureDOMUpdate(event) {
this.detectChanges(event.currentTarget, event.detail.newInputAdded);
}
}
35 changes: 35 additions & 0 deletions app/javascript/controllers/halt_navigation_on_change_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["form", "modalTemplate"]
static values = {
modalContainerId: String,
discardOptionId: String,
}

// click-based
displayModalOnChange(event) {
if (this.formTarget.changed) {
event.preventDefault();
const modal = this.modal;
this.prepareDiscardOption(modal, event.detail.url);
this.addModalToDocument(modal);
}
}

// Modal is added out of this controller scope
prepareDiscardOption(modal, targetLocation) {
const discardOption = modal.querySelector(`#${this.discardOptionIdValue}`);
discardOption.setAttribute("href", targetLocation);
}

addModalToDocument(modal) {
const modalContainer = document.getElementById(this.modalContainerIdValue);
modalContainer.appendChild(modal);
}

get modal() {
const modalFragment = this.modalTemplateTarget.content;
return document.importNode(modalFragment, true);
}
}
15 changes: 15 additions & 0 deletions app/javascript/controllers/main_modal_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["dialog", "backdrop"]

close() {
this.element.remove();
}

closeAfterSubmit(event) {
if (event.detail.success) {
this.close();
}
}
}
14 changes: 10 additions & 4 deletions app/javascript/controllers/nested_form_controller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = [ "links", "template" ]
static targets = ["links", "template"]

connect() {
this.wrapperClass = this.data.get("wrapperClass") || "nested-fields"
Expand All @@ -12,6 +12,7 @@ export default class extends Controller {

var content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
this.linksTarget.insertAdjacentHTML('beforebegin', content)
this.dispatch("domupdate", { detail: { newInputAdded: true } })
}

remove_association(event) {
Expand All @@ -21,10 +22,15 @@ export default class extends Controller {
// New records are simply removed from the page
if (wrapper.dataset.newRecord == "true") {
wrapper.remove()

this.dispatch("domupdate")
}
// Existing records are hidden and flagged for deletion
} else {
wrapper.querySelector("input[name*='_destroy']").value = 1
else {
const deletionFlag = wrapper.querySelector("input[name*='_destroy']")
const inputEvent = new Event("input", { bubbles: true })

deletionFlag.value = 1
deletionFlag.dispatchEvent(inputEvent)
wrapper.style.display = 'none'
}
}
Expand Down
6 changes: 6 additions & 0 deletions app/javascript/controllers/select_multiple_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ export default class extends Controller {
}

updateCheckboxes() {
const changeEvent = new Event("change", { bubbles: true })

this.checkboxTargets.forEach(checkbox => {
if (this.store.has(checkbox.dataset.value)) {
checkbox.checked = true
} else {
checkbox.checked = false
}

if (checkbox.checked !== checkbox.defaultChecked) {
checkbox.dispatchEvent(changeEvent)
}
})
this.search()
}
Expand Down
6 changes: 3 additions & 3 deletions app/javascript/controllers/tags_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { Controller } from "@hotwired/stimulus"
import Tagify from '@yaireo/tagify';

export default class extends Controller {
static targets = [ "output" ]
static targets = ["output"]

connect() {
new Tagify(
this.outputTarget);
new Tagify(this.outputTarget); // internally changes `value`
this.outputTarget.defaultValue = this.outputTarget.value // keeps input consistent
}
}
43 changes: 0 additions & 43 deletions app/javascript/controllers/turbo_modal_controller.js

This file was deleted.

6 changes: 6 additions & 0 deletions app/javascript/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
.centered-flex {
@apply flex items-center justify-center;
}

.positioned-center {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}

.scroll-mt-25 {
Expand Down
21 changes: 21 additions & 0 deletions app/presenters/organization_form_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class OrganizationFormPresenter
# Make sure to assign to :data option
def change_detection_form_container_setup
{
controller: 'detect-form-changes halt-navigation-on-change',
detect_form_changes_input_types_value: 'input, textarea, select',
halt_navigation_on_change_modal_container_id_value: 'main-modal-container',
halt_navigation_on_change_discard_option_id_value: 'discard-changes-option',
action: 'turbo:before-visit@document->halt-navigation-on-change#displayModalOnChange'
}
end

def change_detection_form_setup
{
detect_form_changes_target: 'form',
halt_navigation_on_change_target: 'form',
action: "input->detect-form-changes#captureUserInput change->detect-form-changes#captureUserInput \
nested-form:domupdate->detect-form-changes#captureDOMUpdate"
}
end
end
10 changes: 0 additions & 10 deletions app/views/alerts/edit.html.erb

This file was deleted.

12 changes: 12 additions & 0 deletions app/views/alerts/edit.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<%= turbo_stream.update "main-modal-container" do %>
<%= render "shared/main_modal" do %>
<%= render(
"alerts/form",
alert: @alert,
url: alert_path(@alert),
method: :patch,
filters: @filters,
filters_list: @filters_list
) %>
<% end %>
<% end %>
10 changes: 0 additions & 10 deletions app/views/alerts/new.html.erb

This file was deleted.

12 changes: 12 additions & 0 deletions app/views/alerts/new.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<%= turbo_stream.update "main-modal-container" do %>
<%= render "shared/main_modal" do %>
<%= render(
"alerts/form",
alert: @alert,
url: alerts_path,
method: :post,
filters: @filters,
filters_list: @filters_list
) %>
<% end %>
<% end %>
3 changes: 1 addition & 2 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@
<%= render "shared/flash_messages" %>
</div>

<%= turbo_frame_tag "modal" %>

<%= yield %>
</main>
<%= render Footer::Component.new() %>
<div id="main-modal-container"></div>

<script src="https://cdn.jsdelivr.net/gh/stevenschobert/[email protected]/src/instafeed.min.js"></script>
<script> (function(){ var s = document.createElement('script'); var h = document.querySelector('head') || document.body; s.src = 'https://acsbapp.com/apps/app/dist/js/app.js'; s.async = true; s.onload = function(){ acsbJS.init({ statementLink : '', footerHtml : '', hideMobile : false, hideTrigger : false, disableBgProcess : false, language : 'en', position : 'right', leadColor : '#146FF8', triggerColor : '#146FF8', triggerRadius : '50%', triggerPositionX : 'right', triggerPositionY : 'bottom', triggerIcon : 'people', triggerSize : 'bottom', triggerOffsetX : 20, triggerOffsetY : 20, mobile : { triggerSize : 'small', triggerPositionX : 'right', triggerPositionY : 'bottom', triggerOffsetX : 20, triggerOffsetY : 20, triggerRadius : '20' } }); }; h.appendChild(s); })();</script>
Expand Down
2 changes: 1 addition & 1 deletion app/views/my_accounts/show.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ div class="w-full h-full bg-white"
div class="flex flex-row justify-between w-full"
span class="text-lg font-bold text-black capitalize"
| #{alert.decorate.title(index)}
= link_to "Edit", edit_alert_path(alert), class: "text-blue-medium", data: { turbo_frame: "modal" }
= link_to "Edit", edit_alert_path(alert), class: "text-blue-medium", data: { turbo_stream: true }
div class="flex flex-row text-sm font-normal text-gray-2"
| #{list_all_filters(alert).join(', ')}
- else
Expand Down
2 changes: 1 addition & 1 deletion app/views/organizations/_location_fields.html.slim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
div class="grid grid-cols-12 gap-6 mt-8 nested-fields" data-action="google-maps-callback@window->places#initMap" data-controller="places toggle" data-places-imageurl-value=("\#{asset_path 'markergc.png'}")
div class="grid grid-cols-12 gap-6 mt-8 nested-fields" data-new-record="true" data-action="google-maps-callback@window->places#initMap" data-controller="places toggle" data-places-imageurl-value=("\#{asset_path 'markergc.png'}")
div class="col-span-12 lg:col-span-6 md:col-span-7"
= location_form.label :name, "Location Name", class:"text-sm"
= location_form.text_field :name, class: "block mb-4 h-46px mt-1 h-full w-full py-0 px-4 rounded-6px border-gray-5 text-base text-gray-3 focus:ring-blue-medium focus:border-blue-medium"
Expand Down
Loading

0 comments on commit dd3ecf8

Please sign in to comment.