Skip to content

Commit 8d88773

Browse files
Document "Dictionaries" feature (#1405)
* Document dictionaries fieldtype * update fieldtype page and move custom dictionaries to its own page. * mention csv * Add full example --------- Co-authored-by: Jason Varga <[email protected]>
1 parent 2809e2f commit 8d88773

File tree

5 files changed

+470
-0
lines changed

5 files changed

+470
-0
lines changed

Diff for: content/collections/extending-docs/dictionaries.md

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
---
2+
id: d0668b6e-915b-46da-863e-51fec54b02e2
3+
blueprint: page
4+
title: Dictionaries
5+
template: page
6+
intro: 'Dictionaries add options to the [Dictionary](/fieldtypes/dictionary) fieldtype.'
7+
---
8+
## Overview
9+
10+
Dictionaries come in two "flavors" depending on which class you extend from.
11+
12+
With a `BasicDictionary`, you only really need to define the items. The options, searching, and GraphQL behavior is all handled automatically.
13+
14+
If you need more control, you can either override methods, or extend the base `Dictionary` class and do it yourself.
15+
16+
:::tip
17+
You might not even need a custom dictionary. The native [File dictionary](/fieldtypes/dictionary#file) allows you simply provide a JSON, YAML, or CSV file to use a source of options.
18+
:::
19+
20+
21+
## Basic Dictionaries
22+
23+
You may create a dictionary using the following command, which will generate a class in the `App\Dictionaries` namespace.
24+
25+
```shell
26+
php please make:dictionary
27+
```
28+
29+
You may generate it into an addon using the `--addon=vendor/package` option, which will generate it into your addon's `Dictionaries` namespace.
30+
31+
```php
32+
<?php
33+
34+
namespace App\Dictionaries;
35+
36+
use Statamic\Dictionaries\BasicDictionary;
37+
38+
class States extends BasicDictionary
39+
{
40+
protected function getItems(): array
41+
{
42+
return [
43+
['label' => 'Alabama', 'value' => 'AL', 'capital' => 'Montgomery'],
44+
['label' => 'Alaska', 'value' => 'AK', 'capital' => 'Juneau'],
45+
['label' => 'Arizona', 'value' => 'AZ', 'capital' => 'Phoenix'],
46+
// ...
47+
];
48+
}
49+
}
50+
```
51+
52+
### Item data
53+
54+
In the example above, you can see that each item has a `label` and `value`. These will be used in the dropdown field. Any additional keys will be available within templates.
55+
56+
Here we are returning a hardcoded array. But in reality you may be getting options from somewhere like a file, database, or an API:
57+
58+
```php
59+
protected function getItems(): array
60+
{
61+
return Product::all()->toArray();
62+
}
63+
```
64+
65+
66+
### Values and Labels
67+
68+
By default, the `value` and `label` keys will be used. However, you may remap them:
69+
70+
```php
71+
protected function getItems(): array
72+
{
73+
protected string $valueKey = 'abbr';
74+
protected string $labelKey = 'name';
75+
76+
return [
77+
['name' => 'Alabama', 'abbr' => 'AL', 'capital' => 'Montgomery'],
78+
// ...
79+
];
80+
}
81+
```
82+
83+
84+
If you require more logic, you can override the `getItemValue` and/or `getItemLabel` methods:
85+
86+
```php
87+
protected function getItemLabel(array $item): string
88+
{
89+
return $item['name'] . ' (' . $item['label'] . ')'; // "Alabama (AL)"
90+
}
91+
```
92+
93+
### Basic Search
94+
95+
By default, when a user searches the field, a basic search will be performed by checking against each item's values.
96+
97+
You may use the `searchable` property to narrow down which fields should be searched.
98+
99+
```php
100+
protected array $searchable = ['name', 'abbr'];
101+
```
102+
103+
Alternatively, you may customize how the match is performed by overriding the `matchesSearchQuery` method:
104+
105+
```php
106+
protected function matchesSearchQuery(string $query, Item $item): bool
107+
{
108+
return str_contains($item['name'], $query);
109+
}
110+
```
111+
112+
## Options
113+
114+
The `options` method controls what is selectable within the fieldtype. This method should return an array of value/label pairs.
115+
116+
```php
117+
public function options(?string $search = null): array
118+
{
119+
return [
120+
'one' => 'Option One',
121+
'two' => 'Option Two',
122+
];
123+
}
124+
```
125+
126+
This array's keys define what will be stored in the content.
127+
128+
### Search
129+
130+
The `options` method will be passed a `$search` string if the user is searching within the fieldtype. You should filter your options based on this search term.
131+
132+
## Items
133+
134+
The `get` method accepts a value (one of the option's keys) and should return an `Item` instance.
135+
136+
An `Item` requires the value, label, and optionally an array of any additional data.
137+
138+
In the following example we assume a product ID was saved to the content, the product name is the label, and price/sku is extra.
139+
140+
```php
141+
public function get(string $key): ?Item
142+
{
143+
$product = Product::find($key);
144+
145+
return new Item($key, $product->name, [
146+
'price' => $product->price,
147+
'sku' => $product->sku,
148+
]);
149+
}
150+
```
151+
152+
## Config
153+
154+
You may define config fields in order for the user to customize functionality of your dictionary. For example, if you are providing products, you may want to allow the user to select a category to narrow down the options.
155+
156+
```php
157+
protected function fieldItems()
158+
{
159+
return [
160+
'category' => [
161+
'type' => 'select',
162+
'options' => ['clothing', 'accessories']
163+
]
164+
];
165+
}
166+
```
167+
168+
The user's configuration values will be available in your class within the `config` property.
169+
170+
```php
171+
$this->config['category'];
172+
```
173+
174+
## GraphQL
175+
176+
A dictionary will automatically get a GraphQL type named `Dictionary_YourClass`. Within it, you're able to query the item's fields, like so:
177+
178+
```graphql
179+
your_dictionary_field {
180+
id
181+
price
182+
}
183+
```
184+
185+
By default, the base `Dictionary` class will provide the GraphQL schema for nested fields automatically. It does this by looking up the first item. You may wish to override this and provide your own schema.
186+
187+
```php
188+
protected function getGqlFields(): array
189+
{
190+
return [
191+
'id' => [
192+
'type' => GraphQL::nonNull(GraphQL::string()),
193+
'resolve' => fn (Item $item, $args, $context, $info) => $item['id'];
194+
],
195+
'price' => [
196+
'type' => GraphQL::nonNull(GraphQL::int()),
197+
'resolve' => fn (Item $item, $args, $context, $info) => $item['price'];
198+
],
199+
// ...
200+
];
201+
}
202+
```
203+
204+
## Full Example
205+
206+
Here is an example dictionary class that will use the [MusicBrainz](https://musicbrainz.org/) API to create a dictionary of musicians/artists.
207+
208+
```php
209+
<?php
210+
211+
namespace App\Dictionaries;
212+
213+
use Illuminate\Support\Facades\Cache;
214+
use Illuminate\Support\Facades\Http;
215+
use Statamic\Dictionaries\Dictionary;
216+
use Statamic\Dictionaries\Item;
217+
218+
class Artists extends Dictionary
219+
{
220+
public function options(?string $search = null): array
221+
{
222+
if (! $search) {
223+
return [];
224+
}
225+
226+
$response = Http::get('https://musicbrainz.org/ws/2/artist/?fmt=json&query=artist:'.urlencode($search))->json();
227+
228+
return collect($response['artists'])->mapWithKeys(function ($artist) {
229+
$label = $artist['name'];
230+
231+
if ($disambiguation = $artist['disambiguation'] ?? null) {
232+
$label .= ' ('.$disambiguation.')';
233+
}
234+
235+
return [$artist['id'] => $label];
236+
})->all();
237+
}
238+
239+
public function get(string $key): ?Item
240+
{
241+
return Cache::rememberForever('artist-'.$key, function () use ($key) {
242+
$response = Http::get('https://musicbrainz.org/ws/2/artist/'.$key.'?fmt=json')->json();
243+
244+
return new Item($key, $response['name'], [
245+
'name' => $response['name'],
246+
'disambiguation' => $response['disambiguation'] ?? null,
247+
'type' => $response['type'],
248+
'country' => $response['country'],
249+
]);
250+
});
251+
}
252+
}
253+
```

0 commit comments

Comments
 (0)