mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-29 01:22:07 +01:00
Adopt Stimulus SwapController for task-chooser-modal use case
- Builds on #9952 - Create a new method `submit` and `submitLazy` to serialise a form's inputs and submit (GET) async to replace content - Create a lazy version of `replace` and add unit tests for it - Partial progress on #9950
This commit is contained in:
parent
a6c9409e03
commit
4afe01104b
@ -612,4 +612,391 @@ describe('SwapController', () => {
|
|||||||
document.removeEventListener('w-swap:begin', beginEventHandler);
|
document.removeEventListener('w-swap:begin', beginEventHandler);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('performing a content update via actions on a controlled form using form values', () => {
|
||||||
|
let beginEventHandler;
|
||||||
|
let formElement;
|
||||||
|
let onSuccess;
|
||||||
|
const results = getMockResults({ total: 2 });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<main>
|
||||||
|
<form
|
||||||
|
id="form"
|
||||||
|
action="/path/to/form/action/"
|
||||||
|
method="get"
|
||||||
|
data-controller="w-swap"
|
||||||
|
data-action="custom:event->w-swap#replaceLazy submit:prevent->w-swap#replace"
|
||||||
|
data-w-swap-target-value="#content"
|
||||||
|
>
|
||||||
|
<button type="submit">Submit<button>
|
||||||
|
</form>
|
||||||
|
<div id="content"></div>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
|
||||||
|
window.history.replaceState(null, '', '?');
|
||||||
|
|
||||||
|
formElement = document.getElementById('form');
|
||||||
|
|
||||||
|
onSuccess = new Promise((resolve) => {
|
||||||
|
document.addEventListener('w-swap:success', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
beginEventHandler = jest.fn();
|
||||||
|
document.addEventListener('w-swap:begin', beginEventHandler);
|
||||||
|
|
||||||
|
fetch.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
text: () => Promise.resolve(results),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for actions to call the replace method directly, defaulting to the form action url', async () => {
|
||||||
|
const expectedRequestUrl = '/path/to/form/action/';
|
||||||
|
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
formElement.dispatchEvent(
|
||||||
|
new CustomEvent('custom:event', { bubbles: false }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.runAllTimers(); // search is debounced
|
||||||
|
|
||||||
|
// should fire a begin event before the request is made
|
||||||
|
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// visual loading state should be active
|
||||||
|
await Promise.resolve(); // trigger next rendering
|
||||||
|
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
expectedRequestUrl,
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
|
||||||
|
const successEvent = await onSuccess;
|
||||||
|
|
||||||
|
// should dispatch success event
|
||||||
|
expect(successEvent.detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
results: expect.any(String),
|
||||||
|
});
|
||||||
|
|
||||||
|
// should update HTML
|
||||||
|
expect(
|
||||||
|
document.getElementById('content').querySelectorAll('li'),
|
||||||
|
).toHaveLength(5);
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// should NOT update the current URL
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support replace with a src value', async () => {
|
||||||
|
const expectedRequestUrl = '/path/to-src-value/';
|
||||||
|
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
formElement.setAttribute('data-w-swap-src-value', expectedRequestUrl);
|
||||||
|
|
||||||
|
formElement.dispatchEvent(
|
||||||
|
new CustomEvent('custom:event', { bubbles: false }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.runAllTimers(); // search is debounced
|
||||||
|
|
||||||
|
// should fire a begin event before the request is made
|
||||||
|
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// visual loading state should be active
|
||||||
|
await Promise.resolve(); // trigger next rendering
|
||||||
|
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
expectedRequestUrl,
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
|
||||||
|
const successEvent = await onSuccess;
|
||||||
|
|
||||||
|
// should dispatch success event
|
||||||
|
expect(successEvent.detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
results: expect.any(String),
|
||||||
|
});
|
||||||
|
|
||||||
|
// should update HTML
|
||||||
|
expect(
|
||||||
|
document.getElementById('content').querySelectorAll('li'),
|
||||||
|
).toHaveLength(2);
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// should NOT update the current URL
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support replace with a url value provided via the Custom event detail', async () => {
|
||||||
|
const expectedRequestUrl = '/path/to/url-in-event-detail/?q=alpha';
|
||||||
|
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
formElement.dispatchEvent(
|
||||||
|
new CustomEvent('custom:event', {
|
||||||
|
bubbles: false,
|
||||||
|
detail: { url: expectedRequestUrl },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.runAllTimers(); // search is debounced
|
||||||
|
|
||||||
|
// should fire a begin event before the request is made
|
||||||
|
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// visual loading state should be active
|
||||||
|
await Promise.resolve(); // trigger next rendering
|
||||||
|
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
expectedRequestUrl,
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
|
||||||
|
const successEvent = await onSuccess;
|
||||||
|
|
||||||
|
// should dispatch success event
|
||||||
|
expect(successEvent.detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
results: expect.any(String),
|
||||||
|
});
|
||||||
|
|
||||||
|
// should update HTML
|
||||||
|
expect(
|
||||||
|
document.getElementById('content').querySelectorAll('li'),
|
||||||
|
).toHaveLength(2);
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// should NOT update the current URL
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support replace with a url value provided via an action param', async () => {
|
||||||
|
const expectedRequestUrl = '/path/to/url-in-action-param/#hash';
|
||||||
|
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
formElement.setAttribute('data-w-swap-url-param', expectedRequestUrl);
|
||||||
|
|
||||||
|
formElement.dispatchEvent(
|
||||||
|
new CustomEvent('custom:event', { bubbles: false }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.runAllTimers(); // search is debounced
|
||||||
|
|
||||||
|
// should fire a begin event before the request is made
|
||||||
|
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// visual loading state should be active
|
||||||
|
await Promise.resolve(); // trigger next rendering
|
||||||
|
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
expectedRequestUrl,
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
|
||||||
|
const successEvent = await onSuccess;
|
||||||
|
|
||||||
|
// should dispatch success event
|
||||||
|
expect(successEvent.detail).toEqual({
|
||||||
|
requestUrl: expectedRequestUrl,
|
||||||
|
results: expect.any(String),
|
||||||
|
});
|
||||||
|
|
||||||
|
// should update HTML
|
||||||
|
expect(
|
||||||
|
document.getElementById('content').querySelectorAll('li'),
|
||||||
|
).toHaveLength(2);
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// should NOT update the current URL
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performing a content update via actions on a controlled form using form values', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// intentionally testing without target input (icon not needed & should work without this)
|
||||||
|
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<main>
|
||||||
|
<form
|
||||||
|
class="search-form"
|
||||||
|
action="/path/to/form/action/"
|
||||||
|
method="get"
|
||||||
|
role="search"
|
||||||
|
data-controller="w-swap"
|
||||||
|
data-action="change->w-swap#submitLazy submit:prevent->w-swap#submitLazy"
|
||||||
|
data-w-swap-target-value="#task-results"
|
||||||
|
>
|
||||||
|
<input id="search" type="text" name="q"/>
|
||||||
|
<input name="type" type="hidden" value="some-type" />
|
||||||
|
<input name="other" type="text" />
|
||||||
|
<button type="submit">Submit<button>
|
||||||
|
</form>
|
||||||
|
<div id="task-results"></div>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
|
||||||
|
window.history.replaceState(null, '', '?');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for searching via a declared action on input changes', async () => {
|
||||||
|
const input = document.getElementById('search');
|
||||||
|
|
||||||
|
const results = getMockResults({ total: 5 });
|
||||||
|
|
||||||
|
const onSuccess = new Promise((resolve) => {
|
||||||
|
document.addEventListener('w-swap:success', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
const beginEventHandler = jest.fn();
|
||||||
|
document.addEventListener('w-swap:begin', beginEventHandler);
|
||||||
|
|
||||||
|
fetch.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
text: () => Promise.resolve(results),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
input.value = 'alpha';
|
||||||
|
document.querySelector('[name="other"]').value = 'something on other';
|
||||||
|
input.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||||
|
|
||||||
|
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.runAllTimers(); // search is debounced
|
||||||
|
|
||||||
|
// should fire a begin event before the request is made
|
||||||
|
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||||
|
requestUrl:
|
||||||
|
'/path/to/form/action/?q=alpha&type=some-type&other=something+on+other',
|
||||||
|
});
|
||||||
|
|
||||||
|
// visual loading state should be active
|
||||||
|
await Promise.resolve(); // trigger next rendering
|
||||||
|
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
'/path/to/form/action/?q=alpha&type=some-type&other=something+on+other',
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
|
||||||
|
const successEvent = await onSuccess;
|
||||||
|
|
||||||
|
// should dispatch success event
|
||||||
|
expect(successEvent.detail).toEqual({
|
||||||
|
requestUrl:
|
||||||
|
'/path/to/form/action/?q=alpha&type=some-type&other=something+on+other',
|
||||||
|
results: expect.any(String),
|
||||||
|
});
|
||||||
|
|
||||||
|
// should update HTML
|
||||||
|
expect(
|
||||||
|
document.getElementById('task-results').querySelectorAll('li').length,
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// should NOT update the current URL
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for blocking the request with custom events', async () => {
|
||||||
|
const input = document.getElementById('search');
|
||||||
|
|
||||||
|
const results = getMockResults({ total: 5 });
|
||||||
|
|
||||||
|
const beginEventHandler = jest.fn((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('w-swap:begin', beginEventHandler);
|
||||||
|
|
||||||
|
fetch.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
text: () => Promise.resolve(results),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(window.location.search).toEqual('');
|
||||||
|
expect(handleError).not.toHaveBeenCalled();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
input.value = 'alpha';
|
||||||
|
document.querySelector('[name="other"]').value = 'something on other';
|
||||||
|
input.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||||
|
|
||||||
|
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.runAllTimers(); // search is debounced
|
||||||
|
await Promise.resolve(requestAnimationFrame);
|
||||||
|
|
||||||
|
// should fire a begin event before the request is made
|
||||||
|
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||||
|
requestUrl:
|
||||||
|
'/path/to/form/action/?q=alpha&type=some-type&other=something+on+other',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
document.removeEventListener('w-swap:begin', beginEventHandler);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,9 +24,22 @@ const getGlobalHeaderSearchOptions = (): {
|
|||||||
* patch the results into a results DOM container. The query
|
* patch the results into a results DOM container. The query
|
||||||
* input can be the controlled element or the containing form.
|
* input can be the controlled element or the containing form.
|
||||||
* It supports the ability to update the URL with the query
|
* It supports the ability to update the URL with the query
|
||||||
* when processed.
|
* when processed or simply make a query based on a form's
|
||||||
|
* values.
|
||||||
*
|
*
|
||||||
* @example
|
* @example - A form that will update the results based on the form's input
|
||||||
|
* <div id="results"></div>
|
||||||
|
* <form
|
||||||
|
* data-controller="w-swap"
|
||||||
|
* data-action="input->w-swap#submitLazy"
|
||||||
|
* data-w-swap-src-value="path/to/search"
|
||||||
|
* data-w-swap-target-value="#results"
|
||||||
|
* >
|
||||||
|
* <input id="search" type="text" name="query" />
|
||||||
|
* <input id="filter" type="text" name="filter" />
|
||||||
|
* </form>
|
||||||
|
*
|
||||||
|
* @example - A single input that will update the results & the URL
|
||||||
* <div id="results"></div>
|
* <div id="results"></div>
|
||||||
* <input
|
* <input
|
||||||
* id="search"
|
* id="search"
|
||||||
@ -37,6 +50,7 @@ const getGlobalHeaderSearchOptions = (): {
|
|||||||
* data-w-swap-src-value="path/to/search"
|
* data-w-swap-src-value="path/to/search"
|
||||||
* data-w-swap-target-value="#results"
|
* data-w-swap-target-value="#results"
|
||||||
* />
|
* />
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
export class SwapController extends Controller<
|
export class SwapController extends Controller<
|
||||||
HTMLFormElement | HTMLInputElement
|
HTMLFormElement | HTMLInputElement
|
||||||
@ -68,8 +82,12 @@ export class SwapController extends Controller<
|
|||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
/** The related icon element to attach the spinner to */
|
/** The related icon element to attach the spinner to */
|
||||||
iconElement?: SVGUseElement | null;
|
iconElement?: SVGUseElement | null;
|
||||||
|
/** Debounced function to request a URL and then replace the DOM with the results */
|
||||||
|
replaceLazy?: { (...args: any[]): void; cancel(): void };
|
||||||
/** Debounced function to search results and then replace the DOM */
|
/** Debounced function to search results and then replace the DOM */
|
||||||
searchLazy?: { (...args: any[]): void; cancel(): void };
|
searchLazy?: { (...args: any[]): void; cancel(): void };
|
||||||
|
/** Debounced function to submit the serialised form and then replace the DOM */
|
||||||
|
submitLazy?: { (...args: any[]): void; cancel(): void };
|
||||||
/** Element that receives the fetch result HTML output */
|
/** Element that receives the fetch result HTML output */
|
||||||
targetElement?: HTMLElement;
|
targetElement?: HTMLElement;
|
||||||
|
|
||||||
@ -131,8 +149,13 @@ export class SwapController extends Controller<
|
|||||||
// set up initial loading state (if set originally in the HTML)
|
// set up initial loading state (if set originally in the HTML)
|
||||||
this.loadingValue = false;
|
this.loadingValue = false;
|
||||||
|
|
||||||
// set up debounced method
|
// set up debounced methods
|
||||||
|
this.replaceLazy = debounce(this.replace.bind(this), this.waitValue);
|
||||||
this.searchLazy = debounce(this.search.bind(this), this.waitValue);
|
this.searchLazy = debounce(this.search.bind(this), this.waitValue);
|
||||||
|
this.submitLazy = debounce(this.submit.bind(this), this.waitValue);
|
||||||
|
|
||||||
|
// dispatch event for any initial action usage
|
||||||
|
this.dispatch('ready', { cancelable: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
getTarget(targetValue = this.targetValue) {
|
getTarget(targetValue = this.targetValue) {
|
||||||
@ -173,10 +196,14 @@ export class SwapController extends Controller<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a search based on a single input query, and only if that query's value
|
* Perform a URL search param update based on the input's value with a comparison against the
|
||||||
* differs from the current matching URL param. Once complete, update the URL param.
|
* matching URL search params. Will replace the target element's content with the results
|
||||||
* Additionally, clear the `'p'` pagination param in the URL if present, can be overridden
|
* of the async search request based on the query.
|
||||||
* via action params if needed.
|
*
|
||||||
|
* Search will only be performed with the URL param value is different to the input value.
|
||||||
|
* Cleared params will be removed from the URL if present.
|
||||||
|
*
|
||||||
|
* `clear` can be provided as Event detail or action param to override the default of 'p'.
|
||||||
*/
|
*/
|
||||||
search(
|
search(
|
||||||
data?: CustomEvent<{ clear: string }> & {
|
data?: CustomEvent<{ clear: string }> & {
|
||||||
@ -220,6 +247,26 @@ export class SwapController extends Controller<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the target element's content with the response from a request based on the input's form
|
||||||
|
* values serialised. Do not account for anything in the main location/URL, simply replace the content within
|
||||||
|
* the target element.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const form = (
|
||||||
|
this.hasInputTarget ? this.inputTarget.form : this.element
|
||||||
|
) as HTMLFormElement;
|
||||||
|
|
||||||
|
// serialise the form to a query string
|
||||||
|
// https://github.com/microsoft/TypeScript/issues/43797
|
||||||
|
const searchParams = new URLSearchParams(new FormData(form) as any);
|
||||||
|
|
||||||
|
const queryString = '?' + searchParams.toString();
|
||||||
|
const url = this.srcValue;
|
||||||
|
|
||||||
|
this.replace(url + queryString);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abort any existing requests & set up new abort controller, then fetch and replace
|
* Abort any existing requests & set up new abort controller, then fetch and replace
|
||||||
* the HTML target with the new results.
|
* the HTML target with the new results.
|
||||||
@ -227,15 +274,15 @@ export class SwapController extends Controller<
|
|||||||
* a faster response does not replace an in flight request.
|
* a faster response does not replace an in flight request.
|
||||||
*/
|
*/
|
||||||
async replace(
|
async replace(
|
||||||
data:
|
data?:
|
||||||
| string
|
| string
|
||||||
| (CustomEvent<{ url: string }> & { params?: { url?: string } }),
|
| (CustomEvent<{ url: string }> & { params?: { url?: string } }),
|
||||||
) {
|
) {
|
||||||
/** Parse a request URL from the supplied param, as a string or inside a custom event */
|
/** Parse a request URL from the supplied param, as a string or inside a custom event */
|
||||||
const requestUrl =
|
const requestUrl =
|
||||||
typeof data === 'string'
|
(typeof data === 'string'
|
||||||
? data
|
? data
|
||||||
: data.detail.url || data.params?.url || '';
|
: data?.detail?.url || data?.params?.url || '') || this.srcValue;
|
||||||
|
|
||||||
if (this.abortController) this.abortController.abort();
|
if (this.abortController) this.abortController.abort();
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
@ -297,6 +344,8 @@ export class SwapController extends Controller<
|
|||||||
*/
|
*/
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.loadingValue = false;
|
this.loadingValue = false;
|
||||||
|
this.replaceLazy?.cancel();
|
||||||
this.searchLazy?.cancel();
|
this.searchLazy?.cancel();
|
||||||
|
this.submitLazy?.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { initTabs } from '../../includes/tabs';
|
import { initTabs } from '../../includes/tabs';
|
||||||
import {
|
import { submitCreationForm } from '../../includes/chooserModal';
|
||||||
submitCreationForm,
|
|
||||||
SearchController,
|
|
||||||
} from '../../includes/chooserModal';
|
|
||||||
|
|
||||||
const ajaxifyTaskCreateTab = (modal) => {
|
const ajaxifyTaskCreateTab = (modal) => {
|
||||||
$(
|
$(
|
||||||
@ -24,36 +21,30 @@ const ajaxifyTaskCreateTab = (modal) => {
|
|||||||
|
|
||||||
const TASK_CHOOSER_MODAL_ONLOAD_HANDLERS = {
|
const TASK_CHOOSER_MODAL_ONLOAD_HANDLERS = {
|
||||||
chooser(modal, jsonData) {
|
chooser(modal, jsonData) {
|
||||||
function ajaxifyLinks(context) {
|
const form = $('form.task-search', modal.body)[0];
|
||||||
$('a.task-choice', context)
|
|
||||||
// eslint-disable-next-line func-names
|
|
||||||
.on('click', function () {
|
|
||||||
modal.loadUrl(this.href);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line func-names
|
function ajaxifyLinks(context) {
|
||||||
$('.pagination a', context).on('click', function () {
|
$('a.task-choice', context).on('click', function handleClick() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
modal.loadUrl(this.href);
|
||||||
searchController.fetchResults(this.href);
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.pagination a', context).on('click', function handleClick() {
|
||||||
|
const url = this.href;
|
||||||
|
form.dispatchEvent(new CustomEvent('navigate', { detail: { url } }));
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reinitialize tabs to hook up tab event listeners in the modal
|
// Reinitialize tabs to hook up tab event listeners in the modal
|
||||||
initTabs();
|
initTabs();
|
||||||
}
|
|
||||||
|
|
||||||
const searchController = new SearchController({
|
// set up success handling when new results are returned for next search
|
||||||
form: $('form.task-search', modal.body),
|
modal.body[0].addEventListener(
|
||||||
containerElement: modal.body,
|
'w-swap:success',
|
||||||
resultsContainerSelector: '#search-results',
|
({ srcElement }) => ajaxifyLinks($(srcElement)),
|
||||||
onLoadResults: (context) => {
|
{ once: true },
|
||||||
ajaxifyLinks(context);
|
);
|
||||||
},
|
}
|
||||||
inputDelay: 50,
|
|
||||||
});
|
|
||||||
searchController.attachSearchInput('#id_q');
|
|
||||||
searchController.attachSearchFilter('#id_task_type');
|
|
||||||
|
|
||||||
ajaxifyLinks(modal.body);
|
ajaxifyLinks(modal.body);
|
||||||
ajaxifyTaskCreateTab(modal, jsonData);
|
ajaxifyTaskCreateTab(modal, jsonData);
|
||||||
|
@ -30,7 +30,16 @@
|
|||||||
aria-labelledby="tab-label-existing"
|
aria-labelledby="tab-label-existing"
|
||||||
hidden
|
hidden
|
||||||
>
|
>
|
||||||
<form class="task-search" action="{% url 'wagtailadmin_workflows:task_chooser_results' %}" method="GET" novalidate>
|
<form
|
||||||
|
class="task-search"
|
||||||
|
action="{% url 'wagtailadmin_workflows:task_chooser_results' %}"
|
||||||
|
method="GET"
|
||||||
|
novalidate
|
||||||
|
data-controller="w-swap"
|
||||||
|
data-action="navigate->w-swap#replace submit->w-swap#submit:stop:prevent input->w-swap#submitLazy"
|
||||||
|
data-w-swap-target-value="#search-results"
|
||||||
|
data-w-swap-wait-value="50"
|
||||||
|
>
|
||||||
<ul class="fields">
|
<ul class="fields">
|
||||||
{% for field in search_form %}
|
{% for field in search_form %}
|
||||||
<li>
|
<li>
|
||||||
|
Loading…
Reference in New Issue
Block a user