# 3. Budowa frontendu
Duża część warstwy forndenowej aplikacji jest oparta na requireJS. Aby zmodyfikować istniejący moduł musimy przygotować własny moduł składający się z odpowiednich plików javascript oraz pliku konfiguracyjnego .yml, który zainkludować dodany plik w czasie budowania aplikacji. Dlatego przy zmianach w plikach .yml zbudować jeszcze raz aplikację jak to zostało wspomniane w 2.4.
# 3.1. Nadpisywanie modułu własnym modułem
Na początku należy znaleźć pilk, który mamy edytować w plikach akeneo oraz znaleźć jego import w plikach requirejs.yml. W ramach przykładu rozszerzamy field-manager.js odpowiadający za obsługę generowanych pól. Szukając frazy field-manager z file mask włączonym na requirejs.yml znajdziemy odpowiedni plik w którym znajdziemy w jaki sposób zaincludowany wspomniany field-manager.js.
Ważne żeby referancja była taka sama w stworzonym przez nas pliku requirejs.yml, w tym przypadku jest to pim/field-manager.
# Product edit form
pim/field-manager: macopediaproduct/js/product/field-manager
pim/form/common/base-attributes: pimui/js/form/common/attributes
pim/form/common/attributes: macopediaproduct/js/product/maco-attributes
2
3
4
Własne moduły tworzone są w folderze src w katalogu głównym projektu. W tym katalogu znajdziemy podział na mniejsze foldery odpowiadające poszczególnym częsciom infrastruktury apliklacji, np. product lub packages. Tak jak widać na sreenshocie poniżej folder dzieli się na katalogi aplication i infrastructure. Wartstwa frontendowa powinna znaleźć się w tym drugim folderze pod dokładną scieżką: src/Macopedia/Product/Infrastructure/Symfony/Resources/public/js.

Wszystkie pliki javascript trzymamy w folderze public. W tym przykładnie dodatkowo został stworzony katalog js w razie sytuacji jakby w przyszłości dodawane byłyby również style. One też znaleźć powinny się w folderze public.
Przygotować trzeba dodatkowo plik requirejs.yml dla modułu który jest rozszerzany. Pliki .yml umieszczamy zawsze w folderze config.

Po takiej konfiguracji mozna rozpoczac prace nad zmianamy w pliku field-manager.js.
# 3.2. Dodawanie własnych plików i wykorzystanie ich w istniejących modułach
W akneneo komunikacja między poszczególnymi komponentami może odbywać się w rózny sposób (w następnych rozdziałach zostanie to omówione). Do przekazywania infomacji mogą być wykorzystane np. mediatory lub redux. Każda z tych metod ma pewne wady i zalety.
W pliku entity-with-family-variant.js obłsugiwane jest przetwarzanie danych na temat otwartego produktu. W ramach przykałdu chcemy dodać funkcje sprawdzającą czy wczytany produkt ma odoo_id oraz zablokować wybrane pola w sytuacji jesli id występuje.
Jedną z możliwości jest skorzystanie z mediatora. W tej sytuacji jednak nie zadziała ponieważ entity-with-family-variant.js jest wywoływany szybciej niż field-manager.js odpowiedzialny za generowanie pól w produkcie. Mediator wysyła tylko raz informacje w czasie wywołania i nie jest ona zapisywana za pomocą state czy local storage. Dlatego też field-manager.js nie będzie w stanie odebrać wiadomości ponieważ w momencie wywyołania go waidomość wysłana mediatorem już dawno przepadnie.
Dlatego należy stworzyć dodatkowy moduł przechowujący infomację na temat produktu oraz posiadający odpowiednie metody umożliwiajace pobranie wymaganych danych.
Nowy plik tworzymy analogicznie do poprzedniego rozdziału w folderze public oraz dodajemy go w requirejs.yml.

Nazywamy go product-loked-chacker i będzie on dostępny po referncji maco-product-locked-checker. Wykorzystujemy w nim składnie amdjs-api (opens new window). Zdefiniowane w nim są gettery do pobrania informacji na temat jakie atrybuty mają być zablokowane w przypadku posiadania przez produkt odoo_id, tego czy produkt powinnien mieć zablokowane atrybuty oraz settery do pobierania infomacji z entity-with-family-variant.js w momencie wczytania produktu i przypisania go w module.
'use strict';
define([], function () {
return {
locked: null,
lockedAttributesArray: [
'default_code',
'sa_product_id',
'sa_variant_id',
'vendor',
'vendor_product_name',
'custom_package'
],
setProduct: function (product) {
if(product.values.odoo_id && product.values.odoo_id.lenght) {
this.locked =
typeof product.values.odoo_id[0].data === 'string'
}
},
getLockedAttributesArray() {
return this.lockedAttributesArray
},
isLocked: function () {
return this.locked
}
}
});
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
Następnym krokiem jest dodanie stworzonego modułu do pliku entity-with-family-variant.js. Pierwszym krokiem jest nadpisanie tego pliku własnym. Tworzymy więc kopie tego pliku w folderze w którym trzymane są pliki javascript oraz dodajemy go w requirejs.yml (tak jak w poprzednich przykładach).
W pliku entity-with-family-variant.js includowany jest stworzony plik i za pomocą settera setProduct zapisywane są dane oraz sprawdzane czy produkt ma odoo_id.
'use strict';
define(
[
'underscore',
'oro/translator',
'pim/controller/front',
'pim/form-builder',
'pim/fetcher-registry',
'pim/user-context',
'pim/dialog',
'pim/page-title',
'maco-product-locked-checker',
'maco-product-state',
'oro/mediator'
],
function (
_,
__,
BaseController,
FormBuilder,
FetcherRegistry,
UserContext,
Dialog,
PageTitle,
ProductChecker,
ProductState,
mediator
) {
return BaseController.extend({
renderForm: function (route) {
return FetcherRegistry.getFetcher(this.options.config.entity)
.fetch(route.params.id, { cached: false })
.then((product) => {
ProductChecker.setProduct(product);
ProductState.setProductId(product.identifier);
FetcherRegistry.getFetcher("family")
.fetch(product.family)
.then((res) => {
ProductState.setFamilyId(res.meta.id);
});
if (!this.active) {
return;
}
PageTitle.set({
"product.label": product.meta.label[UserContext
.get("catalogLocale")],
});
return FormBuilder.build(product.meta.form).then((form) => {
this.on("pim:controller:can-leave", function (event) {
form.trigger("pim_enrich:form:can-leave", event);
});
form.setData(product);
form.setElement(this.$el).render();
return form;
});
}
});
}
);
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
W entity-with-family-variant.js za pomocą referencji includowany jest stworzony moduł maco-product-locked-checker i przekazywany jako argument do funkcji (zgodnie z dokumentacją requirejs). Po wykonaniu metody FetcherRegistry używamy settera setProduct.
Następnie w pliku field-manager.js należy pobrać informacje czy produkt powinnien mieć zablokowane atrybuty oraz jakie to są atrybuty. Dodajmy stworzny moduł jako dependancje oraz pobieramy tablicę atrybutów, które mają być zablokowane oraz wartość zmiennej isLocked mówiącej czy produkt ma odoo_id i tym samym ma mieć zablokowane atrybuty.

Na koniec w funkcji getField przechodzącej po każdym polu sprawdzamy czy własciwość isLocked jest równa true oraz czy attrybut aktulanego pola należy do tablicy pól, które mają być zablokowane. Jeśli oba warunki są spełnione wywoływany jest setter setLocked, który jest domyślna metodą akeneo wykorzystywaną do blokowania danego pola.

# 3.3. Rozszerzanie istniejących modułów
W przypadku większych modułów posiadających liczne metody oraz będących wykorzystywane w wielu miejscach aplikacji nienajlepszą praktyką jest ich całkowite nadpisywanie jak zostało to pokazane w poprzednich rozdziałach. Jeśli wymagane jest zmodyfikowanie lub dodanie tylko jednej wybranej funkcji zdecydowanie lepszym podejściem jest rozszerzenie domyślnego modułu i napisanie tylko tych rzeczy które chcemy zmodyfikować.
Plik attributes jest sporym modułem, który występuje w wielu miejscach. W ramach zadania należy zmodyfikować dwie metody createAttributeField oraz createFields. W plikach yml należy sprawdzić jak importowany jest domyślnie ten moduł.
Domyślnie dodawany jest w następujący sposób:
pim/form/common/attributes: pim/form/common/attributes
Aby rozszerzyć istniejący moduł należy go z zaimportować pod innym kluczem a następnie dodać go do pliku w którym rozszerzamy ten moduł. Na końcu rozszerzony moduł importujemy pod kluczem starego.
Plik attributes.js dodajemy w requirejs pod nowym kluczem base-attributes, upewniając się, że ten klucz nie jest nigdzie wykorzystywany w projekcie.
pim/form/common/base-attributes: pimui/js/form/common/attributes
Następnie tworzony jest nowy plik attributes, który będzie rozszerzał stary. W nim includujemy pierwotny plik attributes.js pod kluczem pim/form/common/base-attributes.
"use strict";
/**
* Override of the attributes module.
* @author Patryk Białek <p.bialek@macopedia.com>
*/
define([
"jquery",
"underscore",
"oro/mediator",
"pim/field-manager",
"pim/attribute-manager",
"pim/fetcher-registry",
"pim/user-context",
"pim/security-context",
"pim/i18n",
"pim/form/common/base-attributes",
"maco-product-locked-checker",
], function (
$,
_,
mediator,
FieldManager,
AttributeManager,
FetcherRegistry,
UserContext,
SecurityContext,
i18n,
BaseAttributes,
productChecker
) {
return BaseAttributes.extend({
isLocked: null,
lockedAttributesArray: null,
createAttributeField: function (object, attributeCode, values) {
let shouldBeLocked = false;
if (
this.isLocked &&
this.lockedAttributesArray.includes(attributeCode)
) {
shouldBeLocked = true;
}
return FieldManager.getField(
attributeCode,
shouldBeLocked,
this.lockedAttributesArray
)
.then(function (field) {
return $.when(
new $.Deferred().resolve(field),
FetcherRegistry.getFetcher("channel").fetchAll(),
AttributeManager.isOptional(field.attribute, object)
);
})
.then(
function (field, channels, isOptional) {
const scope = _.findWhere(channels, {
code: UserContext.get("catalogScope"),
});
const locale = UserContext.get("catalogLocale");
field.setContext({
entity: this.getFormData(),
locale,
scope: scope.code,
scopeLabel: i18n.getLabel(
scope.labels,
locale,
scope.code
),
uiLocale: UserContext.get("catalogLocale"),
optional: isOptional,
removable: SecurityContext.isGranted(
this.config.removeAttributeACL
),
});
field.setValues(values);
FieldManager.addVisibleField(field.attribute.code);
return field;
}.bind(this)
);
},
createFields: function (data, values) {
productChecker.setProduct(data);
this.isLocked = productChecker.isLocked();
this.lockedAttributesArray =
productChecker.getLockedAttributesArray();
return FetcherRegistry.getFetcher("attribute")
.fetchByIdentifiers(Object.keys(values))
.then((attributes) => {
attributes.sort((a, b) => {
if (a.sortOrder < b.sortOrder) {
return -1;
}
if (a.sortOrder > b.sortOrder) {
return 1;
}
return a.meta.id < b.meta.id ? -1 : 1;
});
return $.when.apply(
$,
attributes.map((attribute) => {
return this.createAttributeField(
data,
attribute.code,
values[attribute.code]
);
})
);
})
.then(function () {
return _.values(arguments);
});
},
});
});
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
123
124
Jak można zauważyć po zaimportowaniu pierwotny plik attributes jest dostępny pod nazwą BaseAttributes. Dzięki użyciu BaseAttributes.extend mamy dostęp do wszystkich metod i właściwości zaimportowanego przez nas pliku.
Następnie edytujemy tylko funkcje, które tego wymagają. Unikając tym samym nadpisywania całego pliku. Pozostałe właściwości i metody są dziedziczone z BaseAttributes.
Ostatnim krokiem jest dodanie w plikach yml stworzonego przez nas pliku. Ważne jest to żeby plik został dodany pod tym samym kluczem pod jakim pierwotnie był dodany rozszerzany przez nas moduł.
pim/form/common/base-attributes: pimui/js/form/common/attributes
pim/form/common/attributes: macopediaproduct/js/product/maco-attributes
2
W pierwszej lini zapinany jest stary plik attibutes, który jest includowany do nowego pliku maco-attributes, który jest zapinany pod kluczem pim/form/common/attributes, który pierwotnie należał do attibutes.