# 3.8. Select2
Biblioteką powrzechnie używaną do twrzenia selectów w akeneo jest select2. Jest to rozwiązanie wykorzystujące jQuerry do tworzenia skastomizowanych pól wyboru z różnymi opcjami jak np. pobranie danych ajaxem czy przygotowanie multiselecta. Całą dokumentację znajdziesz tutaj (opens new window).
Select2 jest bezpośrednio zapięty do akeneo w wersji dev w folderze lib. W związku z tym jest dostępny globalnie i żeby go wykorzystać w plikach js nie trzeba go includować.
Biblioteka ta w swoim działaniu wykorzystuje ukryty input z którego z atrybutu pobiera bądź dodaje elementy w zależności od wykonywanej czynności.

Konfigurację zaczyna się od odwołania się do tego elementu i użycia metody select2 do której przekazujemy obiekt konfiguracyjny:
this.el.select2({})
Możemy przekazać wiele właściwości, poniżej mamy kilka z nich wraz z opisem:
allowClear: true - możliwość usunięcia wybranych opcji
placeholder: this.props.placeholder - dodanie placeholdera dla selecta
multiple: this.props.multiple - właściwośc przyjmująca true/false w zależności czy można wybrać kilka opcji czy jedną (zwykle w akeneo stosuje sie multiselct)
dropdownCssClass i containerCssClass - pomocnicze klasy w zależności od stanu selecta
ajax: - jest to obiekt konfiguracyjny dla zapytań ajaxowych w którym mamy następujące właściwości:
url - url na który ma iść zapytanie, w tym przypadku używamy metody routing.generate do stowrzenie odpowiedniego adresu:
url: routing.generate("akeneo_reference_entities_record_index_rest", {
referenceEntityIdentifier:
this.props.referenceEntityIdentifier.stringValue(),
});
2
3
4
cache: true - cachowanie wyników raz już poprawnych, oczekuje true/false
delay: 250 - opóżnienie w milisekundach przed triggerowaniem requesta
data - przekazywana jest funkcja w której można modyfikować paramatr, które zostaną wysłane

# 3.9. Select2 - record select field
Najpowszechniejszym selectem w akeneo wykorzystującym bibliotekę select2 jest record select field. Jest to pole wyboru pobierający dane z api o określonej encji, która jest przekazywana propsem. Plik ten można znaleźć pod nazwą PackageRecordSelector a nim znajdują sie interfejsy, templatka renderRow odpowiadająca za wyniki wyszukiwania w selekcie oraz sam komponent PackageRecordSelector w którym znajduje się konfiguracja select2, metody pomocnicze oraz Lifecycle hooks.
Żeby użyć tego komponentu importujemy go w standardowy dla reacta sposób:
import PackageRecordSelector from "./PackageRecordSelector";
Przykład użycia w kodzie:
<PackageRecordSelector
id={`pim_reference_entity.record.enrich.${field.code}`}
value={null}
locale={LocaleReference.create(UserContext.get('catalogLocale'));}
channel={ChannelReference.create(UserContext.get('catalogScope'))}
placeholder={__('pim_reference_entity.record.selector.no_value')}
referenceEntityIdentifier={createIdentifier(field.code)}
readOnly={false}
onChange={(value: RecordSelect) => {
this.setState({non_standard_type: value.code});
}}
/>
2
3
4
5
6
7
8
9
10
11
12
Jako propsy przekazywane są następujące właściwości:
id - w tym przypadku kod pola, który przychodzi z api
value - domyślna wartość selecta
locale - w tym przypadku używamy wcześniej omówiony UserContext żeby pobrać język a następnie wykorzystujemy klasę LocaleReference i metodę create aby stworzyć referencję do obecnego języka
channel - analogiczna sytuacja jak w locale. Używamy UserContext żeby pobrać pobrać scope katalogu i następnie tworzymy referencje do obecnego scopa.
placeholder - wybieramy odpowiednie labelke wraz z jej tłumaczeniem
referenceEntityIdentifier - tworzymy identifier, który będzie informował do jakiego typu encji chcemy się odwołać, w tym przypadku paczek
initSelection - funkcja wywoływana w momencie wczytania selecta oraz zmiany wartości.
Sam plik PackageRecordSelector dzieli się na 2 główne części sam komponent oraz renderRow odpowiadający ze wyświetlenie pojedynczego rowa z wynikiem wyszukiwania.
W PackageRecordSelector mamy dostępne następujące metody:
InitialRecordCodes - pobiera z ukrytego inputa wartości paczek, które są już dodane i formatuje z nich kody paczek potrzebne do wykonania zapytań przez REST API.
initialRecordCodes() {
return this.el
.val()
.split(",")
.map((recordCode: string) => RecordCode.create(recordCode));
}
2
3
4
5
6
Wynik wykonanego zapytania jest następujący:

formatItem - metoda ta formatuje na temat poszczególnej paczce wyświetlonej w selekcie z danych które otrzymuje z API.
formatItem(normalizedRecord: NormalizedItemRecord): Select2Item {
return {
id: normalizedRecord.code,
text: getLabel(
normalizedRecord.labels,
this.props.locale.stringValue(),
normalizedRecord.code
),
original: normalizedRecord,
};
}
2
3
4
5
6
7
8
9
10
11
Dodatkowo zajduje się kilka innych metod takich jak getSelectedRecordCode czy normalizeValue. W zależności od użycia logika tych funkcji nieco się różni ale zasada działania jest w zasadzie taka sama. Dane są odczytywane z ukrytego inputa a następnie są formatowane z wykorzystaniem dostępnych metod w akeneo jak RecordCode.create aby można było w odpowiednim formacie wysłać zapytanie do backendu o zwrócenie wymaganych paczek. Analogicznie dane otrzymane z bakcendu są odpowiednie formatowane przed ich wyświetleniem.
Poza przedstawionymi metodami mamy jeszcze initSelectField w którym wywoływany jest select2 wraz z obiektem konfiguracyjnym:
this.el.select2({
allowClear: true,
placeholderOption: "",
placeholder: {
id: "-1",
text: this.props.placeholder,
},
multiple: this.props.multiple,
dropdownCssClass,
containerCssClass,
ajax: {
url: routing.generate("akeneo_reference_entities_record_index_rest", {
referenceEntityIdentifier:
this.props.referenceEntityIdentifier.stringValue(),
}),
quietMillis: 250,
cache: true,
type: "PUT",
params: { contentType: "application/json;charset=utf-8" },
data: (term: string, page: number): string => {
const selectedRecords = this.getSelectedRecordCode(
this.props.value,
this.props.multiple as boolean
);
const searchQuery = {
channel: this.props.channel.stringValue(),
locale: this.props.locale.stringValue(),
size: this.PAGE_SIZE,
page: page - 1,
filters: [
{
field: "reference_entity",
operator: "=",
value: this.props.referenceEntityIdentifier.stringValue(),
},
{
field: "code_label",
operator: "=",
value: term,
},
{
field: "code",
operator: "NOT IN",
value: selectedRecords,
},
],
};
return JSON.stringify(searchQuery);
},
results: (result: {
items: NormalizedRecord[];
matchesCount: number;
}) => {
const items = result.items.map(this.formatItem.bind(this));
return {
more: this.PAGE_SIZE === items.length,
results: items,
};
},
},
initSelection: async (
element: any,
callback: (item: Select2Item | Select2Item[]) => void
) => {
if (this.props.multiple) {
const initialRecordCodes = this.initialRecordCodes();
const result = await recordFetcher.fetchByCodes(
this.props.referenceEntityIdentifier,
initialRecordCodes,
{
channel: this.props.channel.stringValue(),
locale: this.props.locale.stringValue(),
},
false
);
callback(result.map(this.formatItem.bind(this)));
} else {
const initialValue = element.val();
recordFetcher
.fetchByCodes(
this.props.referenceEntityIdentifier,
[RecordCode.create(initialValue)],
{
channel: this.props.channel.stringValue(),
locale: this.props.locale.stringValue(),
}
)
.then((records: NormalizedItemRecord[]) => {
callback(this.formatItem(records[0]));
});
}
},
formatSelection: (record: Select2Item, container: any) => {
if (Array.isArray(record) && 0 === record.length) {
return;
}
container
.addClass("select2-search-choice-value")
.append(
$(
renderRow(
record.text,
record.original,
false,
this.props.compact
)
)
);
},
formatResult: (record: Select2Item, container: any) => {
container
.addClass("select2-search-choice-value")
.append(
$(
renderRow(record.text, record.original, true, this.props.compact)
)
);
},
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
W nim znajdują się już wcześniej omówione właściwości takie jak: cache, PUT, url czy data. Dodatkowo znajdują się w nim metody formatResult i formatSelection odpowiedzialne za odpowiednie formatowanie oraz initSelection odpowiedzialne za zainicjalizowanie wyboru przy wczytaniu strony. W tym celu wykorzystywany jest recordFetcher, który jest omówiony w następnych rozdziałach.
# 3.10. Select Field
Drugim często używanym selectem w akeneo to Select Field. Jest to prosty reactowy komponent wyświetlający te dane, które zostaną przekazane jako props. Informacja o wybranej opcji przekazywana jest do rodzica przez callbacka przekazywanego jako props do dziecka.
<SelectField
id={`s2id_pim_reference_entity.record.enrich.${field.code}`}
className="AknSelectField"
name={field.code}
value={this.state[field.code]}
data={field.values}
multiple={false}
readOnly={false}
configuration={{
allowClear: true,
placeholder: __("pim_reference_entity.attribute.options.no_value"),
}}
onChange={(event: onChangeEvent) => {
this.selectHandler(event, field.code);
}}
/>;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Do komponentu przekazywane są standardowe wartości takie jak: id, name, value oraz:
data - obiekt klucz-wartość dla kolejnych opcji w selekcie
przykład obiektu data:
non_standard: "non_standard"
standard: "standard"
2
onChange - callback przekazywany jako props do dziecka
W samym kodzie komponentu mamy Lifecycle hook componentDidMount() odpowiedzialny za przekazanie do rodzica wartości wybranego pola:
componentDidMount() {
if (null === this.select.current) {
return;
}
const $el = $(this.select.current) as any;
if (undefined !== $el.select2) {
$el.val(this.props.value).select2(this.props.configuration);
$el.on('change', (event: any, type: string) => {
this.props.onChange(event.val, type);
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13