Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature: MultiCheckbox (checkboxes with multiple choices). #42

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/custom-fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
'select' => \Givebutter\LaravelCustomFields\FieldTypes\SelectFieldType::class,
'textarea' => \Givebutter\LaravelCustomFields\FieldTypes\TextareaFieldType::class,
'text' => \Givebutter\LaravelCustomFields\FieldTypes\TextFieldType::class,
'multicheckbox' => \Givebutter\LaravelCustomFields\FieldTypes\MultiCheckboxFieldType::class,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename "multicheckbox" to "multiselect". Checkbox is overly-specific and the input doesn't always have to be a checkbox. (i.e. <select multiple>)

],

/*
Expand All @@ -47,6 +48,7 @@
'select' => \Givebutter\LaravelCustomFields\ResponseTypes\SelectResponseType::class,
'textarea' => \Givebutter\LaravelCustomFields\ResponseTypes\TextareaResponseType::class,
'text' => \Givebutter\LaravelCustomFields\ResponseTypes\TextResponseType::class,
'multicheckbox' => \Givebutter\LaravelCustomFields\ResponseTypes\MultiCheckboxResponseType::class,
],

/*
Expand Down
10 changes: 10 additions & 0 deletions database/factories/CustomFieldFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ public function withTypeTextArea()
* @param $defaultValue
* @return $this
*/
public function withTypeMultiCheckbox()
{
$this->model->type = CustomFieldType::MULTICHECKBOX;

return $this;
}

/**
* @return $this
*/
public function withDefaultValue($defaultValue)
{
$this->model->default_value = $defaultValue;
Expand Down
2 changes: 2 additions & 0 deletions src/Enums/CustomFieldType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ enum CustomFieldType: string
case SELECT = 'select';
case TEXT = 'text';
case TEXTAREA = 'textarea';
case MULTICHECKBOX = 'multicheckbox';

public function requiresAnswers(): bool
{
return in_array($this, [
self::RADIO,
self::SELECT,
self::MULTICHECKBOX,
]);
}
}
25 changes: 25 additions & 0 deletions src/FieldTypes/MultiCheckboxFieldType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Givebutter\LaravelCustomFields\FieldTypes;

use Illuminate\Validation\Rule;

class MultiCheckboxFieldType extends FieldType
{
public function validationRules(array $attributes): array
{
return [
$this->validationPrefix.$this->field->id => array_filter([
$this->requiredRule($attributes['required']),
'array',
$attributes['required'] ? 'min:1' : null
]),
Comment on lines +12 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required rule implies that there will be at least 1 value, so you can drop the min:1 ternary and the array_filter wrapper.

$this->validationPrefix.$this->field->id.'.*' => [
'required',
'string',
'max:255',
Rule::in($this->field->answers),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add distinct here just to make sure there are no duplicates.

],
];
}
}
43 changes: 43 additions & 0 deletions src/ResponseTypes/MultiCheckboxResponseType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Givebutter\LaravelCustomFields\ResponseTypes;

class MultiCheckboxResponseType extends ResponseType
{
const VALUE_FIELD = 'value_json';

public function formatValue(mixed $value): mixed
{
if (! is_array($value)) {
return [$value];
}

return $value;
}
Comment on lines +9 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can drop this too. I think it's overly cautious to not trust the data in our DB to not be an array as that's the format we require in the validation.


public function getValue(): mixed
{
return $this->formatValue(
$this->response->getAttribute($this::VALUE_FIELD)
);
}
Comment on lines +18 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can drop this as it doesn't modify the behavior of the parent class.


public function getValueFriendly(): mixed
{
$answers = $this->response->field->answers;
$values = $this->response->value;
$list = [];

if(! is_array($values)) {
$values = [$values];
}

foreach ($values as $value) {
if (isset($answers[$value])) {
$list[] = $answers[$value];
}
}

return implode(', ', $list);
Comment on lines +27 to +41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all we need is a guard clause for null because as I said above it'll either be an array or null. We also shouldn't need to check isset on the available answers. Try this:

if (is_null($this->getValue())) {
    return '';
}

return Arr::join($this->getValue(), ', ');

}
}
93 changes: 93 additions & 0 deletions tests/Feature/CustomFieldControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,99 @@ public function checkbox_can_pass_validation(mixed $value, callable $assert)
$assert($this, $surveyResponse);
}

/**
* @test
*/
public function multi_checkbox_can_pass_validation()
{
$survey = Survey::create();
$survey->customfields()->save(
CustomField::factory()->make([
'title' => 'Favorite Album',
'type' => 'multicheckbox',
'answers' => ['Tha Carter', 'Tha Carter II', 'Tha Carter III'],
])
);

Route::post("/surveys/{$survey->id}/responses", function (Request $request) use ($survey) {
$validator = $survey->validateCustomFields($request);

if ($validator->fails()) {
return ['errors' => $validator->errors()];
}

return response('All good', 200);
});

$fieldId = CustomField::where('title', 'Favorite Album')->value('id');

$this
->post("/surveys/{$survey->id}/responses", [
'custom_fields' => [
$fieldId => [
'Tha Carter',
'Tha Carter III'
],
],
])->assertSee('All good');
}

/**
* @test
*/
public function multi_checkbox_can_overwrite_values()
{
$survey = Survey::create();
$surveyResponse = SurveyResponse::create();
$field = $survey->customfields()->save(
CustomField::factory()->make([
'title' => 'Favorite Album',
'type' => 'multicheckbox',
'answers' => ['Tha Carter', 'Tha Carter II', 'Tha Carter III'],
])
);

Route::post("/surveys/{$survey->id}/responses", function (Request $request) use ($survey, $surveyResponse) {
$survey->validateCustomFields($request);

$surveyResponse->saveCustomFields($request->custom_fields);

return response('All good', 200);
});

// first time
$this
->post("/surveys/{$survey->id}/responses", [
'custom_fields' => [
$field->id => [
'Tha Carter II',
'Tha Carter III'
],
],
])->assertOk();

$this->assertSame(1, $field->responses()->count());
$this->assertSame([
'Tha Carter II',
'Tha Carter III'
], $field->responses()->first()->value);

// second time
$this
->post("/surveys/{$survey->id}/responses", [
'custom_fields' => [
$field->id => [
'Tha Carter I'
],
],
])->assertOk();

$this->assertSame(1, $field->responses()->count());
$this->assertSame([
'Tha Carter I'
], $field->responses()->first()->value);
}

public function checkboxChoices(): iterable
{
yield 'true' => [
Expand Down
10 changes: 7 additions & 3 deletions tests/Feature/CustomFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ public function test_collection_validation_rules()
{
$survey = Survey::create();

$cfs = CustomField::factory()->count(3)->sequence(
['title' => 'My Text Field', 'type' => 'text'],
['title' => 'My Select Field', 'type' => 'select', 'answers' => ['foo', 'bar']],
$cfs = CustomField::factory()->count(6)->sequence(
['title' => 'My Checkbox Field', 'type' => 'checkbox'],
['title' => 'My Integer Field', 'type' => 'number'],
['title' => 'My Radio Field', 'type' => 'radio'],
['title' => 'My Select Field', 'type' => 'select', 'answers' => ['foo', 'bar']],
['title' => 'My Text Field', 'type' => 'text'],
['title' => 'My Textarea Field', 'type' => 'textarea'],
['title' => 'My Multi Checkbox Field', 'type' => 'multicheckbox'],
)->create([
'model_id' => $survey->id,
'model_type' => $survey->getMorphClass(),
Expand Down