Jak nadpisać moduł oparty o RequireJS?

Wiesz już, że Akeneo PIM to rozwiązanie open source i posiada modułową architekturę. Przez moduły w tym kontekście rozumiemy niewielkie, niezależne fragmenty kodu JavaScript ładowane przez RequireJS. Nie zawsze jednak gotowy komponent odpowiada w 100% potrzebom biznesowym. Co wtedy? Istnieje kilka sposobów na to, by zmodyfikować nieco komponenty. W dalszej części artykułu omówimy jeden z nich i wyjaśnimy, w jakich przypadkach stosować tę metodę.


Przeczytaj również inne artykuły z cyklu:


Moduły i widoki w Akeneo PIM oraz ich modyfikacja

Stosowany w Akeneo system komponentów UI jest analogiczny do innych rozwiązań. Dzielenie warstwy frontendowej na małe, reużywalne wycinki kodu sprawia, że unikamy powtórzeń w kodzie oraz skracamy czas rozwoju nowych funkcjonalności. Jeśli zależy nam na modyfikacji wybranego modułu – wystarczy rozszerzyć lub nadpisać komponent zamiast tworzyć od zera kolejny. Jest to duży atut oprogramowania Akeneo.

Więcej na temat samego produktu i stacku technologicznego dla warstwy frontendu pisaliśmy tutaj.

Workflow i struktura plików

Jak zaznaczyliśmy w poprzednim rozdziale, ważnym miejscem dla dewelopera jest folder "src", w którym będzie tworzyć nowe lub nadpisywać istniejące moduły. Z kolei istniejące pliki źródłowe Akeneo, które będą modyfikowane, są zawarte w folderze "vendor/akeneo". Na poniższym przykładzie widać 2 paczki od Akeneo: Community Edition (wersja open source dostępna na GitHubie) oraz Enterprise Edition (wersja z płatną licencją):

Jak sugeruje tytuł postu blogowego, naszym zadaniem będzie nadpisanie istniejącego modułu RequireJS własnym. To jeden ze sposobów zmiany wyglądu i logiki aplikacji bez konieczności ingerowania w oryginalne pliki. W kolejnych postach przedstawimy też inne metody.

Na sam początek wybraliśmy najprostszą z nich. By zmierzyć się z tym wyzwaniem, będziemy musieli wykonać następujące czynności:

Lista jest dość długa. Na szczęście część zagadnień poznałeś w poprzednich artykułach naszego cyklu. Mamy nadzieję, że jesteś gotowy na kolejny krok!

Jak nadpisać istniejący moduł, gdzie go szukać i jak go podpiąć za pomocą requirejs.yml?

W tym podrozdziale pokażemy, jak zablokować pole produktu w zależności od tego, jaką ma wartość atrybutu. Zanim jednak przejdziemy do przykładów, poświęćmy chwilę na omówienie poszczególnych kroków nadpisywania modułów.

Aby zmodyfikować istniejący moduł w Akeneo PIM, musimy przygotować własny komponent składający się z odpowiedniego pliku JavaScript opartego na RequireJS oraz zarejestrować go w pliku konfiguracyjnym "requirejs.yml".

  • Krok 1

Pierwszym krokiem będzie odszukanie w strukturze Akeneo pliku, który zamierzamy edytować. W ramach przykładu rozszerzamy "field-manager.js" odpowiadający za obsługę generowanych pól.

Zakładamy, że już masz już stworzonego bundla w Symfony i możesz działać w nim frontendowo. Ścieżka do Twojego pliku wynika z zasad tworzenia bundli oraz z samej dokumentacji Symfony. Na tym etapie powinieneś także wiedzieć, w jaki sposób znaleźć moduł. Jeśli chciałbyś odświeżyć wiedzę na ten temat, zachęcamy do ponownego zapoznania się z naszą instrukcją.

  • Krok 2

Szukając frazy "field-manager" z file mask włączonym na "requirejs.yml", znajdziemy właściwy plik, a na jego podstawie dowiemy się, w jaki sposób dodano wspomniany "field-manager.js". Chcemy nadpisać "field-manager.js" własnym plikiem, który go zastąpi. Po odnalezieniu w plikach RequireJS odpowiedniego klucza "pim/field-manager", możemy go dodać do naszego pliku konfiguracyjnego.

  • Krok 3

Tworzymy "requirejs.yml" dla modułu, który jest rozszerzany. Pliki .yml umieszczamy zawsze w folderze "config":

W "requirejs.yml" definiujemy plik, który nadpisze domyślny moduł RequireJS "field-manager.js" w następujący sposób:

config:
 paths:
  pim/field-manager: macopediaproduct/js/product/field-manager

  • Krok 4

Należy teraz dodać sam plik "field-manager.js", który będzie nadpisywał domyślny moduł.

Stworzony przez nas moduł:

  • Krok 5

Na koniec należy znaleźć pierwotny moduł "field-manager.js" w plikach źródłowych Akeneo zgodnie ze wskazówkami z wcześniejszego postu, skopiować jego treść i przekleić do nowego modułu. Jeśli masz problem z odszukaniem tego pliku, podpowiadamy, w którym miejscu się znajduje.

  • Krok 6

Po wykonaniu tych czynności można rozpocząć prace nad zmianami w pliku "field-manager.js". Aby plik był już widziany przez watcher, należy go wyłączyć (w przypadku, gdy jest włączony) i przebudować całą warstwę frontendu za pomocą komendy "make front" bądź "make upgrade-front" (w zależności od wersji oprogramowania).

Następnie możemy ponownie uruchomić watcher z użyciem komendy "yarn run webpack-dev --watch".

Zgodnie z programistyczną tradycją napiszmy nasz pierwszy “Hello world”.  W pliku "field-manager.js" dopisujemy w metodzie "getField" prosty "console.log":

getField: function (attributeCode, shouldBeLocked = false, lockedAttributesArray = []) {
   var deferred = $.Deferred();
   conosole.log('Hello world');


   if (fields[attributeCode]) {
       deferred.resolve(fields[attributeCode]);
       return deferred.promise();
   }

   FetcherRegistry.getFetcher('attribute').fetch(attributeCode).done(function (attribute) {
       getFieldForAttribute(attribute).done(function (Field) {
           fields[attributeCode] = new Field(attribute);
           deferred.resolve(fields[attributeCode]);
       });
   });

   return deferred.promise();
},

Metoda "getField" jest wywoływana dla każdego pola produktu. Tak więc teraz dla poszczególnych pól zostanie wyświetlony "console.log" – “Hello world”:

Właśnie napisaliśmy pierwszą modyfikację nadpisanego modułu. Nie jest co prawda wielka zmiana, ale nie od razu Rzym zbudowano.

W ramach ćwiczenia spróbujmy wyświetlić jeszcze kod dla każdego pola. W tym celu podmieniamy nasz string “Hello World” na zmienną "attributeCode":

getField: function (attributeCode, shouldBeLocked = false, lockedAttributesArray = []) {
   var deferred = $.Deferred();
   console.log(attributeCode);

   if (fields[attributeCode]) {
       deferred.resolve(fields[attributeCode]);
       return deferred.promise();
   }

   FetcherRegistry.getFetcher('attribute').fetch(attributeCode).done(function (attribute) {
       getFieldForAttribute(attribute).done(function (Field) {
           fields[attributeCode] = new Field(attribute);
           deferred.resolve(fields[attributeCode]);
       });
   });

   return deferred.promise();
},

Kody dla poszczególnych pól:

Teraz zablokujmy pole odpowiadające za nazwę, czyli "name", tak aby było tylko do odczytu.

Powyżej można zobaczyć widok produktu oraz wyrenderowanych pól. Patrząc od dołu, drugie pole stanowi "name" (które ma być zablokowane). Polecenie "console.log" możemy zastosować także dla obiektu "attribute', co pomoże nam zweryfikować, do jakich właściwości mamy dostęp:

getField: function (attributeCode, shouldBeLocked = false, lockedAttributesArray = []) {
   var deferred = $.Deferred();

   if (fields[attributeCode]) {
       deferred.resolve(fields[attributeCode]);
       return deferred.promise();
   }

   FetcherRegistry.getFetcher('attribute').fetch(attributeCode).done(function (attribute) {
       getFieldForAttribute(attribute).done(function (Field) {
           console.log(attribute);

           fields[attributeCode] = new Field(attribute);
           deferred.resolve(fields[attributeCode]);
       });
   });

   return deferred.promise();
},

Po dodaniu naszego "console.log", możemy zobaczyć obiekt "attribute":

Jedną z właściwości jest pole "is_read_only". Jeśli Twoim celem jest zablokowanie pola, powinno ono zwracać wartość "true". Dodajemy więc warunek, sprawdzający, czy mamy do czynienia z polem "name" oraz ustawiamy "is_read_only" na "true":

getField: function (attributeCode, shouldBeLocked = false, lockedAttributesArray = []) {
   var deferred = $.Deferred();

   if (fields[attributeCode]) {
       deferred.resolve(fields[attributeCode]);
       return deferred.promise();
   }

   FetcherRegistry.getFetcher('attribute').fetch(attributeCode).done(function (attribute) {
       getFieldForAttribute(attribute).done(function (Field) { 
           if (attributeCode === 'name') {
               attribute.is_read_only = true;
           }

           fields[attributeCode] = new Field(attribute);
           deferred.resolve(fields[attributeCode]);
       });
   });

   return deferred.promise();
},

Gdy teraz spojrzymy ponownie w okno przeglądarki, szybko zauważymy, że pole "name" jest zablokowane:

Podobał Ci się ten artykuł? Jeśli chciałbyś poznać inne metody modyfikacji modułów, koniecznie śledź naszego bloga! Już niedługo pojawi się kolejny artykuł, w którym krok po kroku przedstawimy proces rozszerzania istniejącego modułu RequireJS.

Obserwuj nas na Twitterze i Facebooku. Dołącz do nas na LinkedIn!

Przełomowe pomysły wymagają przełomowych technologicznych rozwiązań