Skip to content

Commit

Permalink
Merge branch '6.0/search-result-inline-edit-in-htmx'
Browse files Browse the repository at this point in the history
  • Loading branch information
cbrandtbuffalo committed Jan 23, 2025
2 parents c16db2e + c48a750 commit 8f63c92
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 101 deletions.
2 changes: 1 addition & 1 deletion share/html/Elements/CollectionAsTable/Row
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ foreach my $column (@Format) {

if ( $attrs{edit} ) {
my $helper_name = $record->isa('RT::Ticket') ? 'TicketUpdate' : 'AssetUpdate';
$row_content[$item][$inner_row] .= '<form hx-boost="false" method="POST" action="' . RT->Config->Get('WebPath') . "/Helpers/$helper_name?id=" . $record->id . '" class="editor" autocomplete="off">';
$row_content[$item][$inner_row] .= '<form hx-swap="none" hx-post="' . RT->Config->Get('WebPath') . "/Helpers/$helper_name?id=" . $record->id . '" class="editor" autocomplete="off">';
$row_content[$item][$inner_row] .= $attrs{edit};
$row_content[$item][$inner_row]
.= GetSVGImage( Name => 'close-circle', ExtraClasses => 'cancel text-danger', Title => loc('Cancel') );
Expand Down
2 changes: 1 addition & 1 deletion share/html/Helpers/AssetUpdate
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ $r->headers_out->{'HX-Trigger'} = JSON(
actionsChanged => { messages => \@results, isWarning => $checks_failure },
$checks_failure
? ( validationFailed => [ GetInvalidFields( Object => $asset ) ] )
: ( requestSucceeded => 1 ),
: ( requestSucceeded => 1, @results ? ( collectionsChanged => { class => 'RT::Asset', id => $asset->Id } ) : () ),
map { $_ => '' } @events
},
ascii => 1,
Expand Down
2 changes: 1 addition & 1 deletion share/html/Helpers/EditTicketDescription
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
</a>
</div>
<div class="modal-body">
<form hx-boost="false" action="<% RT->Config->Get('WebPath') %>/Helpers/TicketUpdate" enctype="multipart/form-data">
<form hx-swap="none" hx-post="<% RT->Config->Get('WebPath') %>/Helpers/TicketUpdate" enctype="multipart/form-data">
<input type="hidden" class="hidden" name="id" value="<% $Ticket->id %>" />
<& /Ticket/Elements/EditDescription, TicketObj => $Ticket, InTable => 1 &>
<div class="row mt-2">
Expand Down
2 changes: 1 addition & 1 deletion share/html/Helpers/TicketUpdate
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ $r->headers_out->{'HX-Trigger'} = JSON(
actionsChanged => { messages => \@Actions, isWarning => $checks_failure },
$checks_failure
? ( validationFailed => [ GetInvalidFields( Object => $TicketObj ) ] )
: ( requestSucceeded => 1 ),
: ( requestSucceeded => 1, @Actions ? ( collectionsChanged => { class => 'RT::Ticket', id => $TicketObj->Id } ) : () ),
map { $_ => '' } @events
},
ascii => 1,
Expand Down
157 changes: 60 additions & 97 deletions share/static/js/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,38 +616,6 @@ function addprincipal_onchange(ev, ui) {
}
}

function refreshCollectionListRow(tr, table, success, error) {
var params = {
DisplayFormat : table.data('display-format'),
ObjectClass : table.data('class'),
MaxItems : table.data('max-items'),
InlineEdit : table.hasClass('inline-edit'),

i : tr.data('index'),
ObjectId : tr.data('record-id'),
Warning : tr.data('warning')
};

tr.addClass('refreshing');

jQuery.ajax({
url : RT.Config.WebHomePath + '/Helpers/CollectionListRow',
method : 'GET',
data : params,
success: function (response) {
var index = tr.data('index');
tr.replaceWith(response);
// Get the new replaced tr
tr = table.find('tr[data-index=' + index + ']');
initDatePicker(tr);
RT.Autocomplete.bind(tr);
initializeSelectElements(tr.get(0));
if (success) { success(response) }
},
error: error
});
}

// disable submit on enter in autocomplete boxes
htmx.onLoad(function() {
jQuery('input[data-autocomplete], input.ui-autocomplete-input').each(function() {
Expand Down Expand Up @@ -834,10 +802,44 @@ jQuery(function() {
}
});

document.body.addEventListener('collectionsChanged', function(evt) {
document.querySelectorAll('table.collection-as-table[data-display-format][data-class="' + evt.detail.class + '"]').forEach(table => {
const tr = table.querySelector('tr[data-record-id="' + evt.detail.id + '"]');
if ( tr ) {
tr.classList.add('refreshing');
htmx.ajax(
'POST', RT.Config.WebHomePath + '/Helpers/CollectionListRow',
{
target: tr,
swap: 'outerHTML',
values: {
DisplayFormat : table.getAttribute('data-display-format'),
ObjectClass : table.getAttribute('data-class'),
MaxItems : table.getAttribute('data-max-items') || 0,
InlineEdit : table.classList.contains('inline-edit') ? 1 : 0,
i : tr.getAttribute('data-index'),
ObjectId : tr.getAttribute('data-record-id'),
Warning : tr.getAttribute('data-warning') || 0
}
}
);
}
});
});

document.body.addEventListener('requestSucceeded', function(evt) {
if ( evt.detail.elt.classList.contains('inline-edit') ) {
toggleInlineEdit(jQuery(evt.detail.elt.closest('.titlebox')).find('.inline-edit-toggle:visible'));
}
else if ( evt.detail.elt.classList.contains('editor') ) {
const cell = evt.detail.elt.closest('.editable');
if ( cell ) {
const tr = cell.closest('tr.collection-as-table');
cell.classList.remove('loading');
cell.classList.remove('editing');
document.querySelector('body').classList.remove('inline-editing');
}
}

const history_container = document.querySelector('.history-container');
if ( history_container ) {
Expand Down Expand Up @@ -889,6 +891,15 @@ jQuery(function() {
hintSpan.classList.add('invalid-feedback');
}
}

if ( evt.detail.elt.classList.contains('editor') ) {
const cell = evt.detail.elt.closest('.editable');
if ( cell ) {
cell.classList.remove('loading');
cell.classList.add('editing');
cell.closest('tr').classList.remove('refreshing');
}
}
}
});

Expand Down Expand Up @@ -1624,7 +1635,7 @@ jQuery(function () {

var escapeKeyHandler = null;

var beginInlineEdit = function (cell) {
const beginInlineEdit = function (cell) {
if (!inlineEditEnabled) {
return;
}
Expand Down Expand Up @@ -1672,7 +1683,7 @@ jQuery(function () {
jQuery(document).keyup(escapeKeyHandler);
};

var cancelInlineEdit = function (editor) {
const cancelInlineEdit = function (editor) {
var cell = editor.closest('div');

cell.removeClass('editing');
Expand All @@ -1685,7 +1696,7 @@ jQuery(function () {
}
};

var submitInlineEdit = function (editor, cell) {
const submitInlineEdit = function (editor, cell) {
cell ||= editor.closest('div');

if (!inlineEditEnabled) {
Expand All @@ -1700,67 +1711,14 @@ jQuery(function () {
return;
}

var tr = cell.closest('tr');
var table = tr.closest('table');

if (!cell.hasClass('editing')) {
return;
}

var params = editor.serialize();

editor.find(':input').attr('disabled', 'disabled');
cell.removeClass('editing').addClass('loading');
jQuery('body').removeClass('inline-editing');
tr.addClass('refreshing');

var renderError = function (error) {
jQuery.jGrowl(error, { sticky: true, themeState: 'none' });
cell.removeClass('loading');
tr.removeClass('refreshing');
editor.find(':input').removeAttr('disabled');
var errorMessage = jQuery('<div>'+loc_key('error')+'</div>')
.addClass('error text-danger').hide();
var fadeTime = 250;
cell.find('div.value').fadeOut(fadeTime,function () {
cell.append(errorMessage);
errorMessage.fadeIn(fadeTime, function () {
setTimeout(function () {
errorMessage.fadeOut(fadeTime, function () {
errorMessage.remove();
cell.find('div.value').fadeIn(fadeTime);
});
}, 2000);
});
});
jQuery(document).off('keyup', escapeKeyHandler);
};
jQuery.ajax({
url : editor.attr('action'),
method : 'POST',
data : params,
dataType: "json",
success : function (results) {
jQuery.each(results.actions, function (i, action) {
jQuery.jGrowl(action, { themeState: 'none' });
});

refreshCollectionListRow(
tr,
table,
function () {
jQuery(document).off('keyup', escapeKeyHandler);
clipContent(table);
},
function (xhr, error) {
renderError(error);
}
);
},
error : function (xhr, error) {
renderError(error);
}
});
cell.get(0).classList.add('loading');
cell.get(0).classList.remove('editing');
cell.get(0).closest('tr').classList.add('refreshing');
htmx.trigger(editor.get(0), 'submit');
};

jQuery(document).on('click', 'table.inline-edit div.editable .edit-icon', function (e) {
Expand Down Expand Up @@ -1808,11 +1766,6 @@ jQuery(function () {
jQuery(this).closest('form').data('changed', true);
});

jQuery(document).on('submit', 'div.editable.editing form', function (e) {
e.preventDefault();
submitInlineEdit(jQuery(this));
});

jQuery(document).on('click', 'div.editable .cancel', function (e) {
cancelInlineEdit(jQuery(this).closest('form'));
});
Expand All @@ -1821,6 +1774,16 @@ jQuery(function () {
submitInlineEdit(jQuery(this).closest('form'));
});

// We want to call submitInlineEdit to do some pre-checks and massage
// css classes before making htmx requests. Can't bind it to form.submit
// event as preventDefault() there can't stop htmx actions.
jQuery(document).on('keydown', 'div.editable.editing form input[type=text]', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
submitInlineEdit(jQuery(this).closest('form'));
}
});

jQuery(document).on('change', 'div.editable.editing form select', function () {
submitInlineEdit(jQuery(this).closest('form'));
});
Expand Down

0 comments on commit 8f63c92

Please sign in to comment.