# 4.1. Wykorzystanie Fetchera do wykonywania zapytań

Jednym z najczęściej wykorzystywanych sposobów do pobierania danych w akeneo Fetcher. Jest to metoda wykonująca zapytania przez rest api oraz zwracająca wynik zapytania.

Fetcher jest dostępny pod kluczem: pim/fetcher-registry.

Podstawową metodą fetchera jest getFetcher, która przyjmuje parametr entityType mówiąca o jaki typ encji chcemy odpytać back-end.

Kod źródłowy metody getFetcher:

getFetcher: function (entityType) {
    var fetcher = this.fetchers[entityType] || this.fetchers.default;
    return fetcher.loadedModule;
},
1
2
3
4

Poniżej znajduje się przykładowe zapytanie z wykorzystanie fetchera oraz metody getFetcher dla encji typu product:

console.log(FetcherRegistry.getFetcher('product'));
1

W konsoli jako odpowiedź zostanie zwrócony object posiadający pewne wartości taki jak atrybuty czy opcje.

image info

Odwołując się do prototypu tego obiektu możemy znaleźć metodę fetch za pomoca której można pobrać informację na temat interesującego nas produktu. Argumentem tej funkcji jest id produktu, których dane chcemy otrzymać.

FetcherRegistry.getFetcher('product').fetch(product_id).then(product => {
    console.log(product);
});
1
2
3

W odpowiedzi dostajemy obszerny obiekt z wieloma informacjami takimi jak kategoria, rodzina, rodzic czy wartości poszczególnych atrybutów, czyli wszystkie niezbędne informacje do wyrenderowania produktu na stronie.

image info

Jest to tylko jeden z wielu zastosowań fetcherów. W zależności jaki typ encji będziemy obsłużyć dostaniemy różne metody do wykorzystanie dziedziczone po prototypie.

Wykonujemy zapytanie dla attribute-group:

FetcherRegistry.getFetcher('attribute-group');
1

image info

W wyniku zapytanie otrzymaliśmy inne metody dostępne po prototypie obiektu ponieważ wykonaliśmy zapytanie dla innego typu encji.

Wykorzystujemy metodę fetchAll:

FetcherRegistry.getFetcher('attribute-group').fetchAll().then((res) => {
    console.log(res);
})
1
2
3

Jako odpowiedź z API przychodzi atrybuty podzielone na grupy, np. cenę czy cechy produktu:

image info

Fetcher jest wykorzystywany jeszcze w wielu innych miejscach w akeneo do pobierania różnych typów danych. Od prostych obiektów zawierających informację o języku po obszerne obiekty posiadające całą gamę informacji o danym produkcie.

# 4.2. Pobieranie danych z wybranego endpointa

Zaprezentowany poniżej przykład jest dla front-endowej cześci akeneo opartej na reakcie. W konstruktorze wywołana jest medota odpowiadająca za pobranie danych polach potrzebnych do wygenerowania popupu do tworzenia paczek.

constructor(props: PropsInterface) {
    super(props);
    this.state = {
        label: '',
        width: '',
        height: '',
        weight: '',
        depth: '',
        number_of_items: '',
        package_features: '',
        non_standard_type: '',
        standard: true,
        checkData: false,
        advancedFields: [],
        recordField: [],
        simpleFields: [],
        currentLocale: currentLocale,
        recordCount: 0,
        isPending: false
    }

    this.getPackageFields();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Metoda w prosty sposób za pomoca fetcha pobiera dane z endpointa oraz tworzy dwie grupy pól, które następnie aktualizują stan komponentu.

function getPackageFields() {
  let advancedFields: [];
  let recordField: [];
  let simpleFields: Field[];

  fetch("rest/reference_entity/packages")
    .then((response) => response.json())
    .then((data: ResponseFromAPI) => {
      this.setState({ recordCount: Number(data.record_count) });

      advancedFields = data.attributes.filter((obj: Field) => {
        return obj.code === "package_features" || obj.code === "standard";
      });
      simpleFields = data.attributes.filter((obj: Field) => {
        return (
          obj.code === "label" ||
          obj.code === "width" ||
          obj.code === "height" ||
          obj.code === "depth" ||
          obj.code === "weight" ||
          obj.code === "number_of_items"
        );
      });
      // record field that relates to other entities, e.g. sets, variants
      recordField = data.attributes.filter((obj: Field) => {
        return obj.code === "non_standard_type";
      });

      const correctOrder: Record<string, number> = {
        label: 0,
        depth: 1,
        width: 2,
        height: 3,
        weight: 4,
        number_of_items: 5,
      };

      simpleFields.sort(
        (a, b) => correctOrder[a.code] - correctOrder[b.code]
      );

      advancedFields.forEach((advancedField: any) => {
        const id: string = advancedField.identifier;
        const values: Record<string, string> = {};
        let key: string;
        let value: string;

        for (let i = 0; i <= advancedField.options.length - 1; i++) {
          key = advancedField.options[i].code;
          value = advancedField.options[i].labels[currentLocale]
            ? advancedField.options[i].labels[currentLocale]
            : advancedField.options[i].code;
          values[key] = value;
        }

        const advancedFieldToUpdate: any = advancedFields.find(
          (field: Field) => field.identifier === id
        );
        advancedFieldToUpdate
          ? (advancedFieldToUpdate["values"] = values)
          : "";
      });
    })
    .then(() => {
      this.setState({ simpleFields: simpleFields });
      this.setState({ advancedFields: advancedFields });
      this.setState({ recordField: recordField });
    })
    .catch((err: unknown) => {
      console.log(err);
    });
}
1
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

Dzięki temu możeby być wykonana metoda map na tablicach advancedFields oraz simpleFields do wygenerowania odpowiednich pól:

{
  this.state.advancedFields.map((field: Field) => {
    return (
      <div
        className="AknFieldContainer"
        data-code={field.code}
        key={field.identifier}
      >
        <div className="AknFieldContainer-header">
          <label
            title={field.code}
            className="AknFieldContainer-label"
            htmlFor="s2id_autogen1"
          >
            <span className="AknBadge AknBadge--small"></span>
            {field.labels[currentLocale]
              ? field.labels[currentLocale]
              : `[${field.code}]`}
          </label>
        </div>
        <div className="AknFieldContainer-inputContainer">
          <div className="option-selector-container">
            <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: any) => {
                this.selectHandler(event, field.code);
              }}
            />
          </div>
        </div>
      </div>
    );
  });
}
1
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

W powyższym przykładzie do wygenerowania pól advancedFields użyto komponentu SelectField umówionego w poprzednich rozdziałach.

W analogiczny sposób można też pobierać dane w wykorzystywać je do kastomizacji front-endu dla komponentów opartych na require.js.

# 4.3. Modyfikowanie danych

Jedną z możliwości motyfikacji contentu jest bezpośrednia komunikacja z API (bez wykorzystania mediatorów czy recordFetcherów). Poniżej mamy prosty przykład wysłanie formularza na wskazany endpoint odpowiedzialny za obsługę dodania nowej paczki. Używamy do tego prostego fetcha oraz metody post wysyłając dane w formularzy w opowiednim formacie.

formHandler = (event: FormEvent) => {
  event.preventDefault();

  const dataToSend: object = {
    label: this.state.label,
    width: Number(this.state.width),
    height: Number(this.state.height),
    depth: Number(this.state.depth),
    weight: Number(this.state.weight),
    standard: this.state.standard === "standard" ? true : false,
    packageType: this.state.non_standard_type
      ? this.state.non_standard_type
      : null,
    packageFeatures: this.state.package_features
      ? this.state.package_features
      : null,
    numberOfItems: Number(this.state.number_of_items),
  };

  if (
    this.state.standard === "non_standard" &&
    !this.state.non_standard_type
  ) {
    this.setState({ checkData: true });
  } else if (!this.state.isPending) {
    this.setState({ isPending: true });
    console.log(dataToSend);
    fetch("/macopedia/package", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(dataToSend),
    })
      .then((response: any) => {
        this.setState({ isPending: false });

        if (response.status === 500) {
          this.setState({ checkData: true });
        } else if (response.status === 201) {
          return response.json();
        }
      })
      .then((data) => {
        if (data) {
          this.prepareCreatedPackageToAssign(data.id);
          this.props.popupHandler(false);
        }
      })
      .catch((err: Object) => {
        console.log(err);
      });
  }
};
1
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