Skip to content

Commit

Permalink
[5.x] Adjust behavior of array fields (#10467)
Browse files Browse the repository at this point in the history
Co-authored-by: duncanmcclean <duncanmcclean@users.noreply.github.com>
Co-authored-by: Jason Varga <jason@pixelfear.com>
  • Loading branch information
3 people authored Aug 9, 2024
1 parent 416e970 commit 0b85403
Show file tree
Hide file tree
Showing 23 changed files with 1,030 additions and 34 deletions.
8 changes: 4 additions & 4 deletions resources/js/components/fieldtypes/ArrayFieldtype.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<select class="bg-transparent appearance-none shadow-none outline-none border-0 text-sm" @input="setKey($event.target.value)">
<option
v-for="(element, index) in keyedData"
v-text="config.keys[element.key] || element.key"
v-text="meta.keys[element.key] || element.key"
:key="element._id"
:value="element.key"
:selected="element.key === selectedKey" />
Expand All @@ -29,7 +29,7 @@
<table class="array-table">
<tbody>
<tr v-if="data" v-for="(element, index) in keyedData" :key="element._id">
<th class="w-1/4"><label :for="fieldId+'__'+element.key">{{ config.keys[element.key] || element.key }}</label></th>
<th class="w-1/4"><label :for="fieldId+'__'+element.key">{{ meta.keys[element.key] || element.key }}</label></th>
<td>
<input type="text" class="input-text-minimal" :id="fieldId+'__'+element.key" v-model="data[index].value" :readonly="isReadOnly" />
</td>
Expand Down Expand Up @@ -130,7 +130,7 @@ export default {
computed: {
isKeyed() {
return Boolean(Object.keys(this.config.keys).length);
return Boolean(Object.keys(this.meta.keys).length);
},
isDynamic() {
Expand All @@ -142,7 +142,7 @@ export default {
},
keyedData() {
return this.data.filter(element => this.config.keys.hasOwnProperty(element.key));
return this.data.filter(element => this.meta.keys.hasOwnProperty(element.key));
},
maxItems() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default {
computed: {
options() {
return this.normalizeInputOptions(this.config.options);
return this.normalizeInputOptions(this.meta.options);
},
replicatorPreview() {
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/fieldtypes/CheckboxesFieldtype.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default {
computed: {
options() {
return this.normalizeInputOptions(this.config.options);
return this.normalizeInputOptions(this.meta.options);
},
replicatorPreview() {
Expand Down
22 changes: 19 additions & 3 deletions resources/js/components/fieldtypes/HasInputOptions.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
export default {
methods: {
normalizeInputOptions(options) {
return _.map(options, (value, key) => {
if (! Array.isArray(options)) {
return _.map(options, (value, key) => {
return {
'value': Array.isArray(options) ? value : key,
'label': __(value) || key
};
});
}

return _.map(options, (option) => {
if (typeof option === 'object') {
return {
'value': option.value,
'label': __(option.label) || option.value
};
}

return {
'value': Array.isArray(options) ? value : key,
'label': __(value) || key
'value': option,
'label': __(option) || option
};
});
}
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/fieldtypes/RadioFieldtype.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default {
computed: {
options() {
return this.normalizeInputOptions(this.config.options);
return this.normalizeInputOptions(this.meta.options);
},
replicatorPreview() {
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/fieldtypes/SelectFieldtype.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default {
computed: {
selectedOptions() {
let selections = this.value || [];
let selections = this.value === null ? [] : this.value;
if (typeof selections === 'string' || typeof selections === 'number') {
selections = [selections];
}
Expand All @@ -106,7 +106,7 @@ export default {
},
options() {
return this.normalizeInputOptions(this.config.options);
return this.normalizeInputOptions(this.meta.options);
},
replicatorPreview() {
Expand Down
57 changes: 57 additions & 0 deletions resources/js/tests/NormalizeInputOptions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import _ from 'underscore';
import { __ } from '../bootstrap/globals';
import hasInputOptions from "../components/fieldtypes/HasInputOptions";
const normalizeInputOptions = hasInputOptions.methods.normalizeInputOptions;

window._ = _;
window.__ = __;

const config = {
translations: {
'*.One': 'Uno'
}
}

window.Statamic = {
$config: {
get: (key) => config[key]
}
}

it('normalizes input options with simple array', () => {
expect(normalizeInputOptions([
'one',
'two'
])).toEqual([
{value: 'one', label: 'one'},
{value: 'two', label: 'two'}
]);

expect(normalizeInputOptions([
'One',
'Two'
])).toEqual([
{value: 'One', label: 'Uno'},
{value: 'Two', label: 'Two'}
]);
});

it('normalizes input options with object', () => {
expect(normalizeInputOptions({
one: 'One',
two: 'Two'
})).toEqual([
{value: 'one', label: 'Uno'},
{value: 'two', label: 'Two'}
]);
});

it('normalizes input options with array of objects', () => {
expect(normalizeInputOptions([
{value: 'one', label: 'One'},
{value: 'two', label: 'Two'}
])).toEqual([
{value: 'one', label: 'Uno'},
{value: 'two', label: 'Two'}
]);
});
1 change: 1 addition & 0 deletions resources/lang/en/fieldtypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
'any.config.antlers' => "Enable Antlers parsing in this field's content.",
'any.config.cast_booleans' => 'Options with values of true and false will be saved as booleans.',
'any.config.mode' => 'Choose your preferred UI style.',
'array.config.expand' => 'Whether to save the array in the expanded format. Use this if you intend to have numeric values.',
'array.config.keys' => 'Set the array keys (variables) and optional labels.',
'array.config.mode' => 'The **dynamic** mode gives the user free control of the data, while **keyed** and **single** modes enforce strict keys.',
'array.title' => 'Array',
Expand Down
84 changes: 69 additions & 15 deletions src/Fieldtypes/Arr.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Statamic\Facades\GraphQL;
use Statamic\Fields\Fieldtype;
use Statamic\GraphQL\Types\ArrayType;
use Statamic\Support\Arr as SupportArr;

class Arr extends Fieldtype
{
Expand Down Expand Up @@ -33,21 +34,65 @@ protected function configFieldItems(): array
'display' => __('Keys'),
'instructions' => __('statamic::fieldtypes.array.config.keys'),
'type' => 'array',
'expand' => true,
'key_header' => __('Key'),
'value_header' => __('Label').' ('.__('Optional').')',
'add_button' => __('Add Key'),
'unless' => [
'mode' => 'dynamic',
],
],
'expand' => [
'type' => 'toggle',
'display' => __('Expand'),
'instructions' => __('statamic::fieldtypes.array.config.expand'),
],
],
],
];
}

public function preload(): array
{
return [
'keys' => $this->keys()->mapWithKeys(fn ($item) => [$item['key'] => $item['value']]),
];
}

private function keys()
{
return collect($this->config('keys'))->map(fn ($value, $key) => [
'key' => is_array($value) ? $value['key'] : $key,
'value' => is_array($value) ? $value['value'] : $value,
])->values();
}

public function preProcess($data)
{
return array_replace($this->blankKeyed(), $data ?? []);
if ($this->isKeyed()) {
$isMulti = is_array(SupportArr::first($data));

return $this->keys()->mapWithKeys(function ($item) use ($isMulti, $data) {
$key = $item['key'];

$value = $isMulti
? collect($data)->where('key', $key)->pluck('value')->first()
: $data[$key] ?? null;

return [$key => $value];
})->all();
}

// When using the legacy format, return the data as is.
if (! is_array(SupportArr::first($data))) {
return $data ?? [];
}

return collect($data)
->mapWithKeys(fn ($item) => [
(string) $item['key'] => $item['value'],
])
->all();
}

public function preProcessConfig($data)
Expand All @@ -57,27 +102,25 @@ public function preProcessConfig($data)

public function process($data)
{
return collect($data)
->when($this->isKeyed(), function ($data) {
return $data->filter();
})
->all();
if (empty($data)) {
return null;
}

if ($this->config('expand')) {
return collect($data)
->map(fn ($value, $key) => ['key' => $key, 'value' => $value])
->values()
->all();
}

return $data;
}

protected function isKeyed()
{
return (bool) $this->config('keys');
}

protected function blankKeyed()
{
return collect($this->config('keys'))
->map(function () {
return null;
})
->all();
}

public function toGqlType()
{
return GraphQL::type(ArrayType::NAME);
Expand All @@ -97,4 +140,15 @@ public function rules(): array
}
}];
}

public function augment($value)
{
if (is_array(SupportArr::first($value))) {
return collect($value)
->mapWithKeys(fn ($item) => [$item['key'] => $item['value']])
->all();
}

return $value;
}
}
1 change: 1 addition & 0 deletions src/Fieldtypes/ButtonGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ protected function configFieldItems(): array
'display' => __('Options'),
'instructions' => __('statamic::fieldtypes.radio.config.options'),
'type' => 'array',
'expand' => true,
'value_header' => __('Label').' ('.__('Optional').')',
'add_button' => __('Add Option'),
],
Expand Down
1 change: 1 addition & 0 deletions src/Fieldtypes/Checkboxes.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protected function configFieldItems(): array
'display' => __('Options'),
'instructions' => __('statamic::fieldtypes.checkboxes.config.options'),
'type' => 'array',
'expand' => true,
'field' => [
'type' => 'text',
],
Expand Down
29 changes: 29 additions & 0 deletions src/Fieldtypes/HasSelectOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ protected function multiple()
return $this->config('multiple');
}

public function preload(): array
{
return [
'options' => $this->getOptions(),
];
}

protected function getOptions(): array
{
$options = $this->config('options') ?? [];

if (array_is_list($options) && ! is_array(Arr::first($options))) {
$options = collect($options)
->map(fn ($value) => ['key' => $value, 'value' => $value])
->all();
}

if (Arr::isAssoc($options)) {
$options = collect($options)
->map(fn ($value, $key) => ['key' => $key, 'value' => $value])
->all();
}

return collect($options)
->map(fn ($item) => ['value' => $item['key'], 'label' => $item['value']])
->values()
->all();
}

public function preProcessIndex($value)
{
$values = $this->preProcess($value);
Expand Down
1 change: 1 addition & 0 deletions src/Fieldtypes/Radio.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protected function configFieldItems(): array
'display' => __('Options'),
'instructions' => __('statamic::fieldtypes.radio.config.options'),
'type' => 'array',
'expand' => true,
'field' => [
'type' => 'text',
],
Expand Down
1 change: 1 addition & 0 deletions src/Fieldtypes/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protected function configFieldItems(): array
'display' => __('Options'),
'instructions' => __('statamic::fieldtypes.select.config.options'),
'type' => 'array',
'expand' => true,
'key_header' => __('Key'),
'value_header' => __('Label').' ('.__('Optional').')',
'add_button' => __('Add Option'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ private function expandedGlidePresetOptions()
return collect(config('statamic.assets.image_manipulation.presets'))
->mapWithKeys(function ($params, $handle) {
return [$handle => $this->expandedGlidePresetLabel($handle, $params)];
});
})->all();
}

private function expandedGlidePresetLabel($handle, $params)
Expand Down
Loading

0 comments on commit 0b85403

Please sign in to comment.