Skip to content

Commit

Permalink
added raw html field type; added module to docassemble.demo demonstra…
Browse files Browse the repository at this point in the history
…ting how raw html can be used to insert Bootstrap accordion HTML
  • Loading branch information
jhpyle committed Jan 24, 2024
1 parent 416985a commit 0163933
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 16 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## [1.4.94] - 2024-01-23

### Added
- The `raw html` special field type under `fields` and `review`. This
is similar to `html` but allows modification of the structure of the
HTML in the list as a whole.

## [1.4.93] - 2024-01-20

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
metadata:
title: Raw HTML
documentation: "https://docassemble.org/docs/fields.html#raw html"
---
question: |
What is your favorite fruit?
fields:
- raw html: |
<div class="p-4 mb-3 rounded bg-danger">
- Favorite fruit: favorite_fruit
- raw html: |
</div>
---
mandatory: True
question: |
Your favorite fruit is ${ favorite_fruit }.
22 changes: 13 additions & 9 deletions docassemble_base/docassemble/base/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,8 @@ def as_data(self, the_user_dict, encode=True):
the_field['note'] = docassemble.base.filter.markdown_to_html(self.extras['note'][field.number], status=self, verbatim=(not encode))
if 'html' in self.extras and field.number in self.extras['html']:
the_field['html'] = self.extras['html'][field.number]
if 'raw html' in self.extras and field.number in self.extras['raw html']:
the_field['raw html'] = self.extras['raw html'][field.number]
if field.number in self.hints:
the_field['hint'] = self.hints[field.number]
if debug:
Expand Down Expand Up @@ -3950,8 +3952,8 @@ def __init__(self, orig_data, caller, **kwargs):
continue
if 'object labeler' in field and ('datatype' not in field or not field['datatype'].startswith('object')):
raise DAError("An object labeler can only be used with an object data type")
if 'note' in field and 'html' in field:
raise DAError("You cannot include both note and html in a field." + self.idebug(data))
if ('note' in field and 'html' in field) or ('note' in field and 'raw html' in field) or ('html' in field and 'raw html' in field):
raise DAError("You cannot combine note, html, and/or raw html in a single field." + self.idebug(data))
for key in field:
if key == 'default' and 'datatype' in field and field['datatype'] in ('object', 'object_radio', 'object_multiselect', 'object_checkboxes'):
continue
Expand Down Expand Up @@ -4401,7 +4403,7 @@ def __init__(self, orig_data, caller, **kwargs):
for x in field['exclude']:
self.find_fields_in(x)
field_info['selections']['exclude'].append(compile(x, '<expression>', 'eval'))
elif key in ('note', 'html'):
elif key in ('note', 'html', 'raw html'):
if 'extras' not in field_info:
field_info['extras'] = {}
field_info['extras'][key] = TextObject(definitions + str(field[key]), question=self)
Expand Down Expand Up @@ -4553,9 +4555,11 @@ def __init__(self, orig_data, caller, **kwargs):
self.fields_used.add(field_info['saveas'])
else:
self.other_fields_used.add(field_info['saveas'])
elif 'note' in field or 'html' in field:
elif 'note' in field or 'html' or 'raw html' in field:
if 'note' in field:
field_info['type'] = 'note'
elif 'raw html' in field:
field_info['type'] = 'raw html'
else:
field_info['type'] = 'html'
self.fields.append(Field(field_info))
Expand Down Expand Up @@ -4595,7 +4599,7 @@ def __init__(self, orig_data, caller, **kwargs):
if not isinstance(field[key], dict) and not isinstance(field[key], list):
field_info['help'] = TextObject(definitions + str(field[key]), question=self)
field_info['type'] = 'button'
elif key in ('note', 'html'):
elif key in ('note', 'html', 'raw html'):
if 'type' not in field_info:
field_info['type'] = key
if 'extras' not in field_info:
Expand Down Expand Up @@ -4769,7 +4773,7 @@ def __init__(self, orig_data, caller, **kwargs):
self.find_fields_in(the_saveas)
if 'action' in field:
field_info['action'] = {'action': field['action'], 'arguments': {}}
if 'type' in field_info and field_info['type'] in ('note', 'html') and 'label' in field_info:
if 'type' in field_info and field_info['type'] in ('note', 'html', 'raw html') and 'label' in field_info:
del field_info['type']
if len(field_info['data']) > 0:
if 'saveas_code' not in field_info:
Expand All @@ -4780,7 +4784,7 @@ def __init__(self, orig_data, caller, **kwargs):
field_info['action'] = {'action': field_info['data'][0], 'arguments': {}}
else:
field_info['action'] = {'action': "_da_force_ask", 'arguments': {'variables': field_info['data']}}
if len(field_info['data']) > 0 or ('type' in field_info and field_info['type'] in ('note', 'html')):
if len(field_info['data']) > 0 or ('type' in field_info and field_info['type'] in ('note', 'html', 'raw html')):
self.fields.append(Field(field_info))
else:
raise DAError("A field in a review list was listed without indicating a label or a variable name, and the field was not a note or raw HTML." + self.idebug(field_info))
Expand Down Expand Up @@ -5988,7 +5992,7 @@ def ask(self, user_dict, old_user_dict, the_x, iterators, sought, orig_sought, p
continue
else:
extras['field metadata'][field.number] = recursive_eval_textobject_or_primitive(field.extras['field metadata'], user_dict)
for key in ('note', 'html', 'min', 'max', 'minlength', 'maxlength', 'step', 'scale', 'inline', 'inline width', 'currency symbol', 'pen color', 'file css class'): # 'script', 'css',
for key in ('note', 'html', 'raw html', 'min', 'max', 'minlength', 'maxlength', 'step', 'scale', 'inline', 'inline width', 'currency symbol', 'pen color', 'file css class'): # 'script', 'css',
if key in field.extras:
if key not in extras:
extras[key] = {}
Expand Down Expand Up @@ -6577,7 +6581,7 @@ def ask(self, user_dict, old_user_dict, the_x, iterators, sought, orig_sought, p
if 'field metadata' not in extras:
extras['field metadata'] = {}
extras['field metadata'][field.number] = recursive_eval_textobject_or_primitive(field.extras['field metadata'], user_dict)
for key in ('note', 'html', 'min', 'max', 'minlength', 'maxlength', 'show_if_val', 'step', 'scale', 'inline', 'inline width', 'ml_group', 'currency symbol', 'css class', 'pen color', 'file css class'): # , 'textresponse', 'content_type' # 'script', 'css',
for key in ('note', 'html', 'raw html', 'min', 'max', 'minlength', 'maxlength', 'show_if_val', 'step', 'scale', 'inline', 'inline width', 'ml_group', 'currency symbol', 'css class', 'pen color', 'file css class'): # , 'textresponse', 'content_type' # 'script', 'css',
if key in field.extras:
if key not in extras:
extras[key] = {}
Expand Down
30 changes: 23 additions & 7 deletions docassemble_base/docassemble/base/standardformatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,17 @@ def as_sms(status, the_user_dict, links=None, menu_items=None):
if hasattr(the_field, 'datatype'):
if the_field.datatype in ['script', 'css']: # why did I ever comment this out?
continue
if the_field.datatype in ['html', 'note'] and field is not None:
if the_field.datatype in ['html', 'raw html', 'note'] and field is not None:
continue
if the_field.datatype == 'note':
info_message = to_text(markdown_to_html(status.extras['note'][the_field.number], status=status), terms, links)
continue
if the_field.datatype == 'html':
info_message = to_text(process_target(status.extras['html'][the_field.number].rstrip()), terms, links)
continue
if the_field.datatype == 'raw html':
info_message = to_text(process_target(status.extras['raw html'][the_field.number].rstrip()), terms, links)
continue
# logmessage("field number is " + str(the_field.number))
if not hasattr(the_field, 'saveas'):
logmessage("as_sms: field has no saveas")
Expand Down Expand Up @@ -328,7 +331,7 @@ def as_sms(status, the_user_dict, links=None, menu_items=None):
elif hasattr(immediate_next_field, 'datatype'):
if immediate_next_field.datatype in ['note']:
next_label = ' (' + word("Next will be") + ' ' + to_text(markdown_to_html(status.extras['note'][immediate_next_field.number], trim=False, status=status, strip_newlines=True), terms, links) + ')'
elif immediate_next_field.datatype in ['html']:
elif immediate_next_field.datatype in ['html', 'raw html']:
next_label = ' (' + word("Next will be") + ' ' + to_text(status.extras['html'][immediate_next_field.number].rstrip(), terms, links) + ')'
if hasattr(field, 'label') and status.labels[field.number] != "no label":
label = to_text(markdown_to_html(status.labels[field.number], trim=False, status=status, strip_newlines=True), terms, links)
Expand Down Expand Up @@ -1011,6 +1014,8 @@ def as_html(status, debug, root, validation_rules, field_error, the_progress_bar
for field in status.get_field_list():
if 'html' in status.extras and field.number in status.extras['html']:
side_note_content = status.extras['html'][field.number].rstrip()
elif 'raw html' in status.extras and field.number in status.extras['raw html']:
side_note_content = status.extras['raw html'][field.number].rstrip()
elif 'note' in status.extras and field.number in status.extras['note']:
side_note_content = markdown_to_html(status.extras['note'][field.number], status=status, strip_newlines=True)
else:
Expand Down Expand Up @@ -1044,6 +1049,9 @@ def as_html(status, debug, root, validation_rules, field_error, the_progress_bar
else:
fieldlist.append(' <div class="da-form-group row da-field-container da-field-container-note da-review"><div class="col"><div>' + side_note_content + '</div></div></div>\n')
continue
if field.datatype == 'raw html' and 'raw html' in status.extras and field.number in status.extras['raw html'] and side_note_content:
fieldlist.append(' ' + side_note_content + '\n')
continue
if field.datatype == 'note' and 'note' in status.extras and field.number in status.extras['note']:
if field.number in status.helptexts:
if tabular:
Expand Down Expand Up @@ -1175,6 +1183,8 @@ def as_html(status, debug, root, validation_rules, field_error, the_progress_bar
for field in field_list:
if 'html' in status.extras and field.number in status.extras['html']:
note_fields[field.number] = process_target(status.extras['html'][field.number].rstrip())
elif 'raw html' in status.extras and field.number in status.extras['raw html']:
note_fields[field.number] = process_target(status.extras['raw html'][field.number].rstrip())
elif 'note' in status.extras and field.number in status.extras['note']:
note_fields[field.number] = markdown_to_html(status.extras['note'][field.number], status=status, embedder=embed_input)
if hasattr(field, 'saveas'):
Expand Down Expand Up @@ -1339,13 +1349,19 @@ def as_html(status, debug, root, validation_rules, field_error, the_progress_bar
fieldlist.append(' <div ' + style_def + data_def + 'class="da-form-group row' + class_def + '"><div class="col"><hr><span class="dacollectnum">' + list_message + '</span><span class="dacollectremoved text-danger dainvisible"> ' + word("(Deleted)") + '</span><button type="button" class="btn btn-sm ' + BUTTON_STYLE + BUTTON_COLOR_UNDELETE + ' float-end dainvisible dacollectunremove"><i class="fas fa-trash-restore"></i> ' + word("Undelete") + '</button>' + da_remove_existing + '</div></div>\n')
else:
if field.number in note_fields:
if field.number in status.helptexts:
fieldlist.append(field_item(field, grid_info, pre=style_def + data_def, classes='da-field-container da-field-container-note' + class_def + extra_container_class, content_classes='col', content=help_wrap(note_fields[field.number], status.helptexts[field.number], status), under_text=under_text))
# fieldlist.append(' <div ' + style_def + data_def + 'class="da-form-group row da-field-container da-field-container-note' + class_def + extra_container_class + '"><div class="col">' + help_wrap(note_fields[field.number], status.helptexts[field.number], status) + '</div></div>\n')
if field.datatype == 'raw html':
fieldlist.append(note_fields[field.number])
else:
fieldlist.append(field_item(field, grid_info, pre=style_def + data_def, classes='da-field-container da-field-container-note' + class_def + extra_container_class, content_classes='col', content='<div>' + note_fields[field.number] + '</div>', under_text=under_text))
# fieldlist.append(' <div ' + style_def + data_def + 'class="da-form-group row da-field-container da-field-container-note' + class_def + extra_container_class + '"><div class="col"><div>' + note_fields[field.number] + '</div></div></div>\n')
if field.number in status.helptexts:
fieldlist.append(field_item(field, grid_info, pre=style_def + data_def, classes='da-field-container da-field-container-note' + class_def + extra_container_class, content_classes='col', content=help_wrap(note_fields[field.number], status.helptexts[field.number], status), under_text=under_text))
# fieldlist.append(' <div ' + style_def + data_def + 'class="da-form-group row da-field-container da-field-container-note' + class_def + extra_container_class + '"><div class="col">' + help_wrap(note_fields[field.number], status.helptexts[field.number], status) + '</div></div>\n')
else:
fieldlist.append(field_item(field, grid_info, pre=style_def + data_def, classes='da-field-container da-field-container-note' + class_def + extra_container_class, content_classes='col', content='<div>' + note_fields[field.number] + '</div>', under_text=under_text))
# fieldlist.append(' <div ' + style_def + data_def + 'class="da-form-group row da-field-container da-field-container-note' + class_def + extra_container_class + '"><div class="col"><div>' + note_fields[field.number] + '</div></div></div>\n')
# continue
elif field.datatype == 'raw html':
if field.number in note_fields:
fieldlist.append(note_fields[field.number])
elif field.datatype == 'note':
if field.number in note_fields:
if field.number in status.helptexts:
Expand Down
92 changes: 92 additions & 0 deletions docassemble_demo/docassemble/demo/accordion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# do not pre-load
import random
import re
import string

__all__ = ['start_accordion', 'next_accordion', 'end_accordion']

r = random.SystemRandom()


def _get_section_id(section):
section_id = re.sub(r'[^a-z0-9]+', '-', section.lower())
if len(section_id) > 0:
section_id += '-'
section_id += (''.join(r.choice(string.ascii_lowercase) for i in range(5)))
return section_id


def _header(section_id):
return f"""\
<div class="row mb-3">
<div class="col">
<div class="accordion" id="{section_id}">
"""


def _section_start(section_id, section, showing):
if showing:
show = ' show'
expanded = 'true'
collapsed = ''
else:
show = ''
expanded = 'false'
collapsed = ' collapsed'
return f"""\
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button{collapsed}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-{section_id}" aria-expanded="{expanded}" aria-controls="collapse-{section_id}">
{section}
</button>
</h2>
<div id="collapse-{section_id}" class="accordion-collapse collapse{show}" data-bs-parent="#{section_id}">
<div class="accordion-body">
"""


def _section_end():
return """\
</div>
</div>
</div>
"""


def _footer():
return """\
</div>
</div>
</div>
"""


def start_accordion(section, showing=False):
"""Returns HTML for the start of a series of accordion
sections. The end_accordion() function must be called at some
point later in the page, or else the HTML will be corrupted. If
you want the section to be open when the page loads, set
showing=True.
"""
section_id = _get_section_id(section)
return _header(section_id) + _section_start(section_id, section, showing)


def next_accordion(section, showing=False):
"""Returns HTML for ending a previous according section and
starting a new accordion section. Can only be used after
start_accordion(). If you want the section to be open when the
page loads, set showing=True.
"""
section_id = _get_section_id(section)
return _section_end() + _section_start(section_id, section, showing)


def end_accordion():
"""Returns HTML for ending a series of accordion sections. Can
only be used after next_accordion or start_accordion().
"""
return _section_end() + _footer()
Loading

0 comments on commit 0163933

Please sign in to comment.