I have a form for a user create custom questions. For that the user needs to introduce the question (ex: Whats your phone?) and also the type of field (text, long text, checkbox, select menu, radio button). If the user selects a field of checkbox, select menu or radio button he also need to introduce the available options for the question.
In the database the questions are inserted in the questions and question_options tables like:
Questions Table:
id question type conference_id
1 Text text 1
2 Checkbox checkbox 1
3 Radio radio_btn 1
4 select select_menu 1
5 textarea long_text 1
6 file file 1
Registration_type_questions pivot table:
id registration_type_id question_id required
1 1 1 1
2 1 2 1
3 1 3 0
4 1 4 0
5 1 5 0
6 1 6 1
The options are stored in the questions_options table:
id question_id value
1 2 check1
2 2 check2
3 3 rad1
4 3 rad2
5 4 select1
6 4 select2
Then in the view I want to show properly in the view registration.blade.php the inputs (text, radio button, checkbox, select, textarea and input file) based on the type stored in the column "type" of the questions table. And also add the required attribute if the required column in the pivot table is "1".
When a question is of the type text, radio button, select, textarea or file it is working fine, the required attribute is added to the form field.
But it's not working properly with checkboxes because in the case of checkboxes if the question is of type checkbox and the question is required that should mean that the user needs to answer that question but should not mean that the user needs to check all checkboxes.
The issue is that with the function getHTMLInput() the generated html for checkboxes have required in each checkbox input and because of that the user needs to check all checkboxes:
<div class="form-group">
<label for="participant_question">Checkbox</label>
<div class="form-check">
<input type="checkbox" name="participant_question[]" value="check1" class="form-check-input" required="">
<label class="form-check-label" for="exampleCheck1">check1</label>
</div>
<div class="form-check">
<input type="checkbox" name="participant_question[]" value="check2" class="form-check-input" required="">
<label class="form-check-label" for="exampleCheck1">check2</label>
</div>
<input type="hidden" name="participant_question_required[]" value="1">
<input type="hidden" value="2" name="participant_question_id[]">
</div>
Do you know how to solve that issue? When a custom question is required that should mean that the question is required so the user should select at least 1 checkbox but should not mean that the user needs to check all checkboxes.
And also do you know how to, if the custom question is required add inside each question label this "<span class="text-primary">*</span>
"?
GetHtmlInput() in the Question model:
class Question extends Model
{
protected $fillable = [
'question', 'type', 'conference_id',
];
public static $typeHasOptions = [
'radio_btn',
'select_menu',
'checkbox'
];
public function registration_type()
{
return $this->belongsToMany('App\RegistrationType', 'registration_type_questions')
->withPivot('required');
}
public function options()
{
return $this->hasMany('App\QuestionOption');
}
public function hasOptions()
{
return in_array($this->type, self::$typeHasOptions);
}
public function getHtmlInput($name = "", $options = "", $required = false, $class = "", $customtype = false) {
$html = '';
$html .= $customtype == 'select_menu' ? "<select name='participant_question' class='form-control' " . ($required ? " required" : "")
. ">" : '';
if (empty($options)) {
switch ($customtype) {
case "text":
$html .= "
<input type='text' name='participant_question' class='form-control'" . ($required ? " required" : "")
. ">";
break;
case "file":
$html .= "
<input type='file' name='participant_question' class='form-control'" . ($required ? " required" : "") . ">";
break;
case "long_text":
$html .= "
<textarea name='participant_question' class='form-control' rows='3'" . ($required ? " required" : "") . ">"
. $name .
"</textarea>";
break;
}
} else {
foreach ($options as $option) {
switch ($customtype) {
case "checkbox":
$html .= "
<div class='form-check'>
<input type='checkbox' name='participant_question[]' value='" . $option->value . "' class='form-check-input'" . ($required ? " required" : "") . ">" .
' <label class="form-check-label" for="exampleCheck1">' . $option->value . '</label>' .
"</div>";
break;
case "radio_btn":
$html .= "
<div class='form-check'>
<input type='radio' name='participant_question[]' value='" . $option->value . "' class='form-check-input'" . ($required ? " required" : "") . ">" .
' <label class="form-check-label" for="exampleCheck1">' . $option->value . '</label>' .
"</div>";
break;
case "select_menu":
$html .= "<option value='" . $option->value . "'>" . $option->value . "</option>";
break;
}
}
}
$html .= $customtype == 'select_menu' ? "</select>" : '';
return $html;
}
}
Then the getHtmlInput() is used like:
@if ($allParticipants == 0)
@foreach($selectedRtype['questions'] as $customQuestion)
<div class="form-group">
<label for="participant_question">{{$customQuestion->question}}</label>
@if($customQuestion->hasOptions() && in_array($customQuestion->type, ['checkbox', 'radio_btn', 'select_menu']))
{!! $customQuestion->getHtmlInput(
$customQuestion->name,
$customQuestion->options,
($customQuestion->pivot->required == '1'),
'form-control',
$customQuestion->type)
!!}
@else
{!! $customQuestion->getHtmlInput(
$customQuestion->name,
[],
($customQuestion->pivot->required == '1'),
'form-control',
$customQuestion->type)
!!}
@endif
<input type="hidden"
name="participant_question_required[]"
value="{{ $customQuestion->pivot->required }}">
<input type="hidden"
value="{{ $customQuestion->id }}"
name="participant_question_id[]"/>
</div>
@endforeach
@endif
Generated HTML with getHTMLInput():
<form method="post" action="">
<div class="form-group">
<label for="participant_question">Text</label>
<input type="text" name="participant_question" class="form-control" required="">
<input type="hidden" name="participant_question_required[]" value="1">
<input type="hidden" value="1" name="participant_question_id[]">
</div>
<div class="form-group">
<label for="participant_question">Checkbox</label>
<div class="form-check">
<input type="checkbox" name="participant_question[]" value="check1" class="form-check-input" required="">
<label class="form-check-label" for="exampleCheck1">check1</label>
</div>
<div class="form-check">
<input type="checkbox" name="participant_question[]" value="check2" class="form-check-input" required="">
<label class="form-check-label" for="exampleCheck1">check2</label>
</div>
<input type="hidden" name="participant_question_required[]" value="1">
<input type="hidden" value="2" name="participant_question_id[]">
</div>
<div class="form-group">
<label for="participant_question">Radio</label>
<div class="form-check">
<input type="radio" name="participant_question[]" value="rad1" class="form-check-input">
<label class="form-check-label" for="exampleCheck1">rad1</label>
</div>
<div class="form-check">
<input type="radio" name="participant_question[]" value="rad2" class="form-check-input">
<label class="form-check-label" for="exampleCheck1">rad2</label>
</div>
<input type="hidden" name="participant_question_required[]" value="0">
<input type="hidden" value="3" name="participant_question_id[]">
</div>
<div class="form-group">
<label for="participant_question">select</label>
<select name="participant_question" class="form-control">
<option value="select1">select1</option>
<option value="select2">select2</option>
</select>
<input type="hidden" name="participant_question_required[]" value="0">
<input type="hidden" value="4" name="participant_question_id[]">
</div>
<div class="form-group">
<label for="participant_question">textarea</label>
<textarea name="participant_question" class="form-control" rows="3"></textarea>
<input type="hidden" name="participant_question_required[]" value="0">
<input type="hidden" value="5" name="participant_question_id[]">
</div>
<div class="form-group">
<label for="participant_question">file</label>
<input type="file" name="participant_question" class="form-control" required="">
<input type="hidden" name="participant_question_required[]" value="1">
<input type="hidden" value="6" name="participant_question_id[]">
</div>
<input type="submit" class="btn btn-primary" value="Store">
</form>
Also, checking this form in a HTML validator like w3c validator it appears some errors:
The <input type="checkbox"> defines a checkbox. The checkbox is shown as a square box that is ticked (checked) when activated. Checkboxes are used to let a user select one or more options of a limited number of choices. Tip: Always add the <label> tag for best accessibility practices!
It is because you have the checkbox group html inside your foreach
loop here:
foreach ($options as $option) {
switch ($customtype) {
case "checkbox":
$html .= "
<div class='checkbox-group' " . ($required ? " required" : "") . ">
You need to think about how you would get around this maybe using a variable like $checkboxesFound
and set it to 0 at the start of the function and when case is checkbox, increment the variable, and if $checkboxesFound == 0
then echo the group div.
Replace your getHtmlInput() with this
public function getHtmlInput($question_id, $index_position, $name = "", $options = "", $required = false, $class = "", $customtype = false) {
//dd($name);
$html = '';
$html .= $customtype == 'select_menu' ? "<select name='participant_question[".$question_id."][".$index_position."]' class='form-control' " . ($required ? " required" : "")
. ">" : '';
if (empty($options)) {
switch ($customtype) {
case "text":
$html .= "
<input type='text' name='participant_question[".$question_id."][".$index_position."]' class='form-control'" . ($required ? " required" : "")
. ">";
break;
case "file":
$html .= "
<input type='file' name='participant_question[".$question_id."][".$index_position."]' class='form-control'" . ($required ? " required" : "") . ">";
break;
case "long_text":
$html .= "
<textarea name='participant_question[".$question_id."][".$index_position."]' class='form-control' rows='3'" . ($required ? " required" : "") . ">"
. $name .
"</textarea>";
break;
}
} else {
foreach ($options as $option) {
switch ($customtype) {
case "checkbox":
$html .= "
<div class='form-check'>
<input type='checkbox' name='participant_question[".$question_id."][".$index_position."][]' value='" . $option->value . "' class='form-check-input'" . ($required ? " required" : "") . ">" .
' <label class="form-check-label" for="exampleCheck1">' . $option->value . '</label>' .
"</div>";
break;
case "radio_btn":
$html .= "
<div class='form-check'>
<input type='radio' name='participant_question[".$question_id."][".$index_position."][]' value='" . $option->value . "' class='form-check-input'" . ($required ? " required" : "") . ">" .
' <label class="form-check-label" for="exampleCheck1">' . $option->value . '</label>' .
"</div>";
break;
case "select_menu":
$html .= "<option value='" . $option->value . "'>" . $option->value . "</option>";
break;
}
}
}
$html .= $customtype == 'select_menu' ? "</select>" : '';
return $html;
}
and
blade code with this
@if ($allParticipants == 0)
{{ $index_position = 0 }}
@foreach($selectedRtype['questions'] as $customQuestion)
{{ $question_id = [[QUESTION_ID]] }}
<div class="form-group">
<label for="participant_question">{{$customQuestion->question}}</label>
@if($customQuestion->hasOptions() && in_array($customQuestion->type, ['checkbox', 'radio_btn', 'select_menu']))
{!! $customQuestion->getHtmlInput(
$question_id,
$index_position,
$customQuestion->name,
$customQuestion->options,
($customQuestion->pivot->required == '1'),
'form-control',
$customQuestion->type)
!!}
@else
{!! $customQuestion->getHtmlInput(
$question_id,
$index_position,
$customQuestion->name,
[],
($customQuestion->pivot->required == '1'),
'form-control',
$customQuestion->type)
!!}
@endif
</div>
{{ $index_position = $index_position+1 }}
@endforeach
@endif
you need to set the name attribute same of same group options in order to work required option properly.
But in your code all options including of different option groups are sharing same name attribute.
The above code is not tested. But I hope it will work for you
HTML5 doesn't have a solution to implement that a group of checkboxes should be required, so with some changes you could achieve it. First on your controller you will need to change it to achieve the same you are doing with your select menu.
// on top of your method:
$html .= $customtype == 'checkbox' ? "<div class='checkbox-group ".($required ? " required" : "")."'>" : '';
// at the bottom
$html .= $customtype == 'checkbox' ? "</div>" : '';
Then in your case 'checkbox' you would only need to print the options that will be inside your 'required group of checkboxs':
case "checkbox":
$html .= "
<div class='form-check'>
<input type='checkbox' name='participant_question[]' value='" . $option->value . "' class='form-check-input' ><label class='form-check-label' for='exampleCheck1'>" . $option->value . "</label>
</div>";
This should output the following html:
<div class="checkbox-group required">
<div class="form-check">
<input type="checkbox" name="participant_question[]" value="whatever" class="form-check-input"><label class="form-check-label" for="exampleCheck1">whatever</label>
</div>
<div class="form-check">
<input type="checkbox" name="participant_question[]" value="whatever2" class="form-check-input"><label class="form-check-label" for="exampleCheck1">whatever2</label>
</div>
</div>
Then on submit, I don't know if you do it with ajax or not but I'll assume you do not, so if you add an id to your form -> id="questionForm"
$('#questionForm').submit(function() {
if($('div.checkbox-group.required div :checkbox:checked').length > 0) {
return true;//submit the form
} else {
return false; // do not submit the form
}
});
Unfortunately there is no way to achieve what you are looking for using only html5, whatever solution you choose it will probably have to be done with js.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With