getgrav / getgrav/grav-plugin-form

Form cannot be sent if a "file" type field is required

Open
#418 4 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
PHP
Stars
64
Forks
80
Avg merge
10h 13m
Merged PRs (30d)
9

Description

Hi,

I have created a form that has a file type field and I need this field to be `required` but when I submit the form I get this error in console:

> An invalid form control with name='' is not focusable.

After performing searches, a `hidden` field may not be `required` (which makes sense in itself).

I looked at the `/plugins/form/app/fields/file.js` file and I think that no verification is done if `required` is `true` in the markdown file (where the form is defined).

So from what I understand, a `hidden` field of type hidden is added (by Dropzone ?).

To work around the problem, I would like to retrieve `this.dropzone` variable to check if there are files that have been added to the form, but I cannot access them (Using the given functions by Dropzone : https://stackoverflow.com/a/21221321).

Markdown:

```
form:
cv_lm:
label: false
type: file
display_label: false
destination: user/data/cv_lm
random_name: true
accept:
- application/pdf
- application/msword
- application/vnd.ms-fontobject
- application/vnd.oasis.opendocument.text
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
limit: 5
multiple: true
name: cv_lm
validate:
required: false
message: Vous devez ajouter un CV et une lettre de motivation
buttons:
submit:
type: submit
value: Envoyer
classes: btn-form
process:
save:
fileprefix: candidature-
dateformat: Ymd-His-u
extension: txt
body: '{% include ''forms/data.txt.twig'' %}'
email:
to: '{{ config.plugins.email.to }}'
from: '{{ config.plugins.email.from }}'
subject: '[Formulaire de candidature] {{ form.value.name|e }} {{ form.value.email|e }}'
body: '{% include ''forms/data.html.twig'' %}'
content_type: text/html
charset: UTF-8
attachments:
- cv_lm
- other_files
message: 'Votre demande a bien été envoyée'
display: thankyou
```

HTML:

```





Recrutement



Formulaire de candidature


Nous ferons notre possible pour répondre dans les plus brefs délais






{% include "forms/recrutement.html.twig" %}




```

Include:

```
{% if form is null %}
{% set form = grav.session.getFlashObject('form') %}
{% endif %}

{% include 'partials/form-messages.html.twig' %}

{% set scope = scope ?: form.scope is defined ? form.scope : 'data.' %}
{% set multipart = '' %}
{% set blueprints = blueprints ?? form.blueprint() %}
{% set method = form.method|upper|default('POST') %}
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}

{% set data = data ?? form.data %}
{% set context = context ?? data %}

{% for field in form.fields %}
{% if (method == 'POST' and field.type == 'file') %}
{% set multipart = ' enctype="multipart/form-data"' %}
{% endif %}
{% endfor %}

{% set action = form.action ?: page.route ~ uri.params %}

{% set action = (action starts with 'http') or (action starts with '#') ? action : base_url ~ action %}

{% if (action == base_url_relative) %}
{% set action = base_url_relative ~ '/' ~ page.slug %}
{% endif %}

{% if form.keep_alive %}
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
{% endif %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% endif %}

{% do assets.addInlineJs("
window.GravForm = window.GravForm || {};
window.GravForm.config = {
current_url: '" ~ uri.route(true) ~"',
base_url_relative: '" ~ base_url_relative ~ "',
param_sep: '"~ config.system.param_sep ~ "',
form_nonce: '" ~ form.getNonce() ~ "',
session_timeout: " ~ config.system.session.timeout ~ "
};
window.GravForm.translations = Object.assign({}, window.GravForm.translations || {}, { PLUGIN_FORM: {} });
", {'group': 'bottom', 'position': 'before'}) %}

{# Backwards Compatibility for block overrides #}
{% set override_form_classes %}
{% block form_classes -%}
{{ form_outer_classes }} {{ form.classes }}
{%- endblock %}
{% endset %}

{% set override_inner_markup_fields_start %}
{% block inner_markup_fields_start %}{% endblock %}
{% endset %}

{% set override_inner_markup_fields_end %}
{% block inner_markup_fields_end %}{% endblock %}
{% endset %}

{% set override_inner_markup_fields %}
{% block inner_markup_fields %}
{% for field_name, field in form.fields %}
{% set field_name = field.name ?? field_name %}
{% if field_name and not field.validate.ignore %}
{%- if field_name starts with '.' -%}
{% set field_name = field_name[1:] %}
{% set field = field|merge({ name: field_name }) %}
{% endif %}

{% set value = form ? form.value(field_name) : data.value(field_name) %}
{% block inner_markup_field_open %}{% endblock %}
{% block field %}
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" ignore missing %}
{% endblock %}
{% block inner_markup_field_close %}{% endblock %}
{% endif %}
{% endfor %}
{% endblock %}
{% endset %}

{% set override_inner_markup_buttons_start %}
{% block inner_markup_buttons_start %}



{% endblock %}
{% endset %}

{% set override_inner_markup_buttons_end %}
{% block inner_markup_buttons_end %}


{% endblock %}

{% endset %}

{# Embed for HTML layout #}
{% embed 'forms/layouts/form.html.twig' %}

{% block embed_form_core %}
name="{{ form.name }}"
action="{{ action | trim('/', 'right') }}"
method="{{ method }}"{{ multipart|raw }}
{% if form.id %}id="{{ form.id }}"{% endif %}
{% if form.novalidate %}novalidate{% endif %}
{% if form.keep_alive %}data-grav-keepalive="true"{% endif %}
{% endblock %}

{% block embed_form_classes -%}
class="{{ parent() }} {{ override_form_classes|trim }}"
{%- endblock %}

{% block embed_fields %}
{{ override_inner_markup_fields_start|raw }}
{{ override_inner_markup_fields|raw }}

{% include "forms/fields/formname/formname.html.twig" %}
{% include "forms/fields/formtask/formtask.html.twig" %}
{% include 'forms/fields/uniqueid/uniqueid.html.twig' %}
{{ nonce_field(form.getNonceAction() ?? 'form', form.getNonceName() ?? 'form-nonce')|raw }}

{{ override_inner_markup_fields_end|raw }}
{% endblock %}

{% block embed_buttons %}
{{ override_inner_markup_buttons_start|raw }}

{% for button in form.buttons %}
{% if button.outerclasses is defined %}

{% endif %}

{% if button.url %}
{% set button_url = button.url starts with 'http' ? button.url : base_url ~ button.url %}
{% endif %}

{% embed 'forms/layouts/button.html.twig' %}
{% block embed_button_core %}
{% if button.id %}id="{{ button.id }}"{% endif %}
{% if button.disabled %}disabled="disabled"{% endif %}
{% if button.task %}name="task" value="{{ button.task }}"{% endif %}
type="{{ button.type|default('submit') }}"
{% endblock %}

{% block embed_button_classes %}
{% block button_classes %}
class="{{ form_button_classes ?: 'button' }} {{ button.classes }}"
{% endblock %}
{% endblock %}

{% block embed_button_content -%}
{%- set button_value = button.value|t|default('Submit') -%}
{%- if button.html -%}
{{- button_value|trim|raw -}}
{%- else -%}
{{- button_value|trim|e -}}
{%- endif -%}
{%- endblock %}
{% endembed %}

{% if button.outerclasses is defined %}

{% endif %}
{% endfor %}

{{ override_inner_markup_buttons_end }}
{% endblock %}

{% endembed %}

{% if config.forms.dropzone.enabled %}


{% include 'forms/dropzone/template.html.twig' %}

{% endif %}
```

How can I get my form required?

Thank you

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with /plugins/form/app/fields/file.js and trace how the file field renders required validation alongside the Dropzone hidden field. Check the related form field templates and browser behavior, then verify that a required file field can be submitted with a file and does not trigger the non-focusable-control error.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, php
Domain
frontend, web-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.