Skip to content

Commit 2e9a36c

Browse files
Copilotcodingjoe
andcommitted
Implement autonomous custom element that replicates HTMLInputElement interface
The custom element now: - Creates its own file picker UI with a button and file name display - Implements all HTMLInputElement properties (files, name, value, validity, etc.) - Uses ElementInternals for form participation when available - Provides validation methods (setCustomValidity, reportValidity, checkValidity) - Shows current file value for ClearableFileInput compatibility - All JavaScript and non-Selenium Python tests pass Note: Selenium tests need updating as the hidden input is now created dynamically by the custom element's JavaScript. Co-authored-by: codingjoe <1772890+codingjoe@users.noreply.github.com>
1 parent d215e06 commit 2e9a36c

File tree

4 files changed

+249
-73
lines changed

4 files changed

+249
-73
lines changed

s3file/forms.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -97,45 +97,51 @@ def build_attrs(self, *args, **kwargs):
9797
return defaults
9898

9999
def render(self, name, value, attrs=None, renderer=None):
100-
"""Render the widget wrapped in a custom element for Safari compatibility."""
100+
"""Render the widget as a custom element for Safari compatibility."""
101101
# Build attributes for the render - this includes data-* attributes
102102
if attrs is None:
103103
attrs = {}
104104

105+
# Ensure name is in attrs
106+
attrs = attrs.copy() if attrs else {}
107+
attrs['name'] = name
108+
105109
# Get all the attributes including data-* attributes from build_attrs
106110
final_attrs = self.build_attrs(self.attrs, attrs)
107111

108-
# Separate data-* attributes for the wrapper from other attributes for the input
109-
wrapper_attrs = {k: v for k, v in final_attrs.items() if k.startswith("data-")}
110-
input_attrs = {k: v for k, v in final_attrs.items() if not k.startswith("data-")}
111-
112-
# Temporarily override build_attrs to return only non-data attributes for the input
113-
original_build_attrs = self.build_attrs
114-
def build_attrs_without_data(*args, **kwargs):
115-
attrs_dict = original_build_attrs(*args, **kwargs)
116-
return {k: v for k, v in attrs_dict.items() if not k.startswith("data-")}
117-
118-
self.build_attrs = build_attrs_without_data
119-
try:
120-
# Call parent's render which will use our modified build_attrs
121-
input_html = super().render(name, value, input_attrs, renderer)
122-
finally:
123-
# Restore original build_attrs
124-
self.build_attrs = original_build_attrs
125-
126-
# Build wrapper attributes string
112+
# Build attributes string for the s3-file element
127113
from django.utils.html import format_html_join
128-
wrapper_attrs_html = format_html_join(
114+
from django.utils.safestring import mark_safe
115+
attrs_html = format_html_join(
129116
' ',
130117
'{}="{}"',
131-
wrapper_attrs.items()
118+
final_attrs.items()
132119
)
133120

134-
# Wrap the input in the s3-file custom element
135-
if wrapper_attrs_html:
136-
return format_html('<s3-file {}>{}</s3-file>', wrapper_attrs_html, input_html)
137-
else:
138-
return format_html('<s3-file>{}</s3-file>', input_html)
121+
# Render the s3-file custom element
122+
# For ClearableFileInput, we also need to show the current value and clear checkbox
123+
output = []
124+
if value and hasattr(value, 'url'):
125+
# Show currently uploaded file (similar to ClearableFileInput)
126+
output.append(format_html(
127+
'<div>Currently: <a href="{}">{}</a></div>',
128+
value.url,
129+
value
130+
))
131+
# Add clear checkbox
132+
clear_checkbox_name = self.clear_checkbox_name(name)
133+
clear_checkbox_id = self.clear_checkbox_id(clear_checkbox_name)
134+
output.append(format_html(
135+
'<div><input type="checkbox" name="{}" id="{}"><label for="{}"> Clear</label></div>',
136+
clear_checkbox_name,
137+
clear_checkbox_id,
138+
clear_checkbox_id
139+
))
140+
141+
# Add the s3-file custom element
142+
output.append(format_html('<s3-file {}></s3-file>', attrs_html))
143+
144+
return mark_safe(''.join(str(part) for part in output))
139145

140146
def get_conditions(self, accept):
141147
conditions = [

0 commit comments

Comments
 (0)