0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-11-25 05:02:57 +01:00

Eliminate jQuery from telepath widget classes

This commit is contained in:
Matt Westcott 2024-04-12 11:47:13 +01:00 committed by Sage Abdullah
parent 6d1fe98f6a
commit 9b74c83195
No known key found for this signature in database
GPG Key ID: EB1A33CC51CC0217
4 changed files with 81 additions and 25 deletions

View File

@ -1,7 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`telepath: wagtail.widgets.CheckboxInput it renders correctly 1`] = `"<input type="checkbox" name="sugar" id="id-sugar" checked="checked">"`;
exports[`telepath: wagtail.widgets.RadioSelect it renders correctly 1`] = `
"<ul id="the-id">
<li>

View File

@ -1,4 +1,3 @@
/* global $ */
import { gettext } from '../../../utils/gettext';
class BoundWidget {
@ -10,8 +9,11 @@ class BoundWidget {
parentCapabilities,
options,
) {
var selector = ':input[name="' + name + '"]';
this.input = element.find(selector).addBack(selector); // find, including element itself
var selector = ':is(input,select,textarea,button)[name="' + name + '"]';
// find, including element itself
this.input = element.matches(selector)
? element
: element.querySelector(selector);
this.idForLabel = idForLabel;
this.setState(initialState);
this.parentCapabilities = parentCapabilities || new Map();
@ -19,15 +21,15 @@ class BoundWidget {
}
getValue() {
return this.input.val();
return this.input.value;
}
getState() {
return this.input.val();
return this.input.value;
}
setState(state) {
this.input.val(state);
this.input.value = state;
}
getTextLabel(opts) {
@ -65,17 +67,31 @@ class Widget {
parentCapabilities,
options = {},
) {
var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
var idForLabel = this.idPattern.replace(/__ID__/g, id);
var dom = $(html);
const html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
const idForLabel = this.idPattern.replace(/__ID__/g, id);
/* write the HTML into a temp container to parse it into an element */
const tempContainer = document.createElement('div');
tempContainer.innerHTML = html.trim();
const dom = tempContainer.firstChild;
/* replace the placeholder with the new element */
placeholder.replaceWith(dom);
/* execute any scripts in the new element */
dom.querySelectorAll('script').forEach((script) => {
const newScript = document.createElement('script');
newScript.text = script.text;
script.replaceWith(newScript);
});
// Add any extra attributes we received to the HTML of the widget
if (typeof options?.attributes === 'object') {
Object.entries(options.attributes).forEach(([key, value]) => {
dom.attr(key, value);
dom.setAttribute(key, value);
});
}
$(placeholder).replaceWith(dom);
// eslint-disable-next-line new-cap
return new this.boundWidgetClass(
dom,
@ -91,16 +107,15 @@ window.telepath.register('wagtail.widgets.Widget', Widget);
class BoundCheckboxInput extends BoundWidget {
getValue() {
return this.input.is(':checked');
return this.input.checked;
}
getState() {
return this.input.is(':checked');
return this.input.checked;
}
setState(state) {
// if false, set attribute value to null to remove it
this.input.attr('checked', state || null);
this.input.checked = state;
}
}
@ -119,19 +134,24 @@ class BoundRadioSelect {
}
getValue() {
return this.element.find(this.selector).val();
return this.element.querySelector(this.selector)?.value;
}
getState() {
return this.element.find(this.selector).val();
return this.element.querySelector(this.selector)?.value;
}
setState(state) {
this.element.find('input[name="' + this.name + '"]').val([state]);
const inputs = this.element.querySelectorAll(
'input[name="' + this.name + '"]',
);
for (let i = 0; i < inputs.length; i += 1) {
inputs[i].checked = inputs[i].value === state;
}
}
focus() {
this.element.find('input[name="' + this.name + '"]').focus();
this.element.querySelector('input[name="' + this.name + '"]')?.focus();
}
}
@ -142,7 +162,8 @@ window.telepath.register('wagtail.widgets.RadioSelect', RadioSelect);
class BoundSelect extends BoundWidget {
getTextLabel() {
return this.input.find(':selected').text();
const selectedOption = this.input.selectedOptions[0];
return selectedOption ? selectedOption.text : '';
}
}

View File

@ -46,10 +46,14 @@ describe('telepath: wagtail.widgets.Widget', () => {
test('getValue() returns the current value', () => {
expect(boundWidget.getValue()).toBe('The Value');
document.querySelector('input').value = 'New Value';
expect(boundWidget.getValue()).toBe('New Value');
});
test('getState() returns the current state', () => {
expect(boundWidget.getState()).toBe('The Value');
document.querySelector('input').value = 'New Value';
expect(boundWidget.getState()).toBe('New Value');
});
test('setState() changes the current state', () => {
@ -87,6 +91,37 @@ describe('telepath: wagtail.widgets.Widget', () => {
});
});
describe('telepath: wagtail.widgets.Widget with inline JS', () => {
let boundWidget;
let widgetDef;
beforeEach(() => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.Widget',
_args: [
'<div><input type="text" name="__NAME__" maxlength="255" id="__ID__"><script>document.getElementById("__ID__").className = "custom-class";</script></div>',
'__ID__',
],
});
boundWidget = widgetDef.render(
document.getElementById('placeholder'),
'the-name',
'the-id',
'The Value',
);
});
test('it renders correctly', () => {
expect(document.body.querySelector('input').outerHTML).toBe(
'<input type="text" name="the-name" maxlength="255" id="the-id" class="custom-class">',
);
expect(document.querySelector('input').value).toBe('The Value');
});
});
describe('telepath: wagtail.widgets.RadioSelect', () => {
let boundWidget;
@ -141,9 +176,8 @@ describe('telepath: wagtail.widgets.RadioSelect', () => {
test('focus() focuses the text input', () => {
boundWidget.focus();
// Note: This widget always focuses the last element
expect(document.activeElement).toBe(
document.querySelector('input[value="coffee"]'),
document.querySelector('input[value="tea"]'),
);
});
});
@ -168,7 +202,6 @@ describe('telepath: wagtail.widgets.CheckboxInput', () => {
});
test('it renders correctly', () => {
expect(document.body.innerHTML).toMatchSnapshot();
expect(document.querySelector('input[id="id-sugar"]').checked).toBe(true);
});

View File

@ -276,3 +276,7 @@ In the new approach, we no longer need to attach an inline script but instead us
data-w-block-arguments-value="[{value_json},{error_json}]"
></div>
```
### Removal of jQuery from base client-side Widget and BoundWidget classes
The JavaScript base classes `Widget` and `BoundWidget` that provide client-side access to form widgets (see [](streamfield_widget_api)) no longer use jQuery. The `element` argument passed to the `BoundWidget` constructor, and the `input` property of `BoundWidget`, are now native DOM elements rather than jQuery collections. User code that extends these classes should be updated accordingly.