[Uwaga] Ten artykuł został pierwotnie przygotowany w języku angielskim i został przetłumaczony na język polski.
Czasami docieramy do granic natywnych komponentów Oracle Apex i musimy improwizować oraz wdrażać workarounds, aby osiągnąć potrzebną funkcjonalność, taką jak wiele typów inputów w jednej kolumnie interactive grid. W tym artykule chciałbym zaprezentować mój sposób na zrobienie tego bez zbędnego hackowania, zachowując wszystkie natywne funkcjonalności.
Może to brzmieć skomplikowanie, ale przechodząc przez to rozwiązanie, zobaczymy, że jest proste. Wyjaśnijmy je na przykładzie dwóch use cases. Najpierw zaczniemy od czegoś bardzo łatwego, a następnie dodamy pewne utrudnienia.
W przypadku pary Label-Value funkcjonalność jest prosta. Chcemy mieć dwie kolumny, gdzie po lewej stronie mamy label (etykietę), a po prawej value (wartość).
Możemy mieć wiele wierszy, a każdy input wartości może być innego typu. Zobaczmy, jak możemy to osiągnąć w interactive grid.
Tworzymy tabelę DYNAMIC_IG_INPUTS, która zawiera 5 kolumn: Label, typ kolumny, a następnie właściwe kolumny wartości dla varchar, date i number. Moglibyśmy również użyć tylko jednej kolumny varchar dla wartości i zastosować case, w którym rozdzielamy wartości z bazy danych na wiele kolumn w interactive grid i z powrotem, ale na razie postawimy na prostotę.
| Column Name | Data Type |
| ID | NUMBER |
| LABEL | VARCHAR2(100 CHAR) |
| COLUMN_TYPE | VARCHAR2(100 CHAR) |
| VALUE_VARCHAR | VARCHAR2(100 CHAR) |
| VALUE_NUMBER | NUMBER |
| VALUE_DATE | DATE |
Do tabeli dodamy trzy wiersze: atrybut Text z typem varchar, atrybut Number z typem number oraz atrybut Date z typem date.
W aplikacji Apex tworzymy włączony interactive grid. Proces DML będzie automatyczny: używamy ID jako primary key i dodajemy Static ID mygrid do atrybutów grida. Wynik będzie wyglądał jak poniżej.
Aby pokazać/ukryć odpowiednią kolumnę w zależności od typu, utworzymy prostą funkcję JavaScript.
/** * Sets column visibility in an Oracle APEX Interactive Grid based on the COLUMN_TYPE value for each row. * * @param {string} pi_grid - Static ID of the Interactive Grid region * * This function dynamically shows or hides cells in each row based on the value stored in the "COLUMN_TYPE" column. * For each row, it shows only cells with a CSS class matching the COLUMN_TYPE value ('dropdown', 'date', 'varchar', 'number'), * plus any cells with the 'donothide' or 'has-button' class. All other cells are hidden. * * The function optimizes performance by: * - Using a reusable filter function to avoid repetitive code * - Processing all records in a single model traversal * - Skipping invalid column types */ function setcolumns(pi_grid) { const region = apex.region(pi_grid); const grid = region.widget().interactiveGrid("getViews", "grid"); const model = grid.model; const gridElement = region.element; // Create a reusable filterAndShow function outside the loop const filterAndShow = (row$, columnType) => { // If there is a - in the columnType, take only the part after it if (columnType.includes('-')) { columnType = columnType.split('-')[1]; } const td = row$.find("td"); // Hide all cells that don't match the column type and don't have the donothide class td.each(function() { const cell = $(this); const shouldShow = cell.hasClass(columnType) || cell.hasClass('donothide') || cell.hasClass('has-button'); cell.toggle(shouldShow); }); }; // Define valid column types to avoid repetitive if/else statements const validTypes = ['dropdown', 'date', 'varchar', 'number']; // Process all records in one go model.forEach(function(record) { const rowId = model.getRecordId(record); var columnType = model.getValue(record, "COLUMN_TYPE"); console.log(columnType); console.log(`Row ID: ${rowId}, Column Type: ${columnType}`); // If the column type is object take a v value if (typeof columnType === 'object' && columnType !== null) { columnType = columnType.v; } // If columnType is not valid, default to varchar if (!columnType || !validTypes.some(type => columnType.includes(type))) { columnType = 'varchar'; console.log(`Row ID: ${rowId} has invalid column type, defaulting to varchar`); } const row$ = gridElement.find(`tr[data-id='${rowId}']`); filterAndShow(row$, columnType); }); }
Aby precyzyjnie celować w kolumny i komórki, dodamy kilka klas do atrybutów kolumn. Do kolumny VALUE_VARCHAR dodajemy varchar. To samo robimy dla pozostałych kolumn: date dla VALUE_DATE i number dla kolumny VALUE_NUMBER.
Dla kolumn Label i Column type dodajemy klasę donothide. Zapobiegnie to ukrywaniu tych kolumn.
Teraz wszystko jest gotowe, aby zobaczyć wynik. Jeśli uruchomimy funkcję w konsoli (setcolumns(’mygrid’);), zobaczymy, że niektóre kolumny zniknęły. Jeśli spróbujemy edytować komórki widoczne w kolumnie Value Varchar, zobaczymy, że komórki są różnego typu.
Wygląda na to, że rozwiązanie działa, ale nie prezentuje się najlepiej. Najpierw zmienimy nazwę kolumny Value Varchar na Value.
Dodamy Dynamic Action, która automatycznie uruchomi dla nas funkcję JavaScript. Dodałem akcję dla zdarzenia Row Initialization [Interactive Grid], ale możesz dodać wiele zdarzeń, aby wyzwalać funkcję w większej liczbie przypadków.
W sekcji True actions dodajemy setcolumns(’mygrid’);.
Następnie dodamy podstawowy kod CSS, aby ukryć pozostałe kolumny. Musimy ukryć dwie ostatnie kolumny wraz z nagłówkiem i komórkami. Można to zrobić w bardziej wyrafinowany sposób, ale poniższy kod zadziała w sam raz.
/*Hiding COL*/ #mygrid .a-GV-table col:nth-last-child(1), #mygrid .a-GV-table col:nth-last-child(2), /*Hiding TH*/ #mygrid .a-GV-table th:nth-last-child(1), #mygrid .a-GV-table th:nth-last-child(2), /*Hiding TD*/ #mygrid .a-GV-table td:nth-last-child(1), #mygrid .a-GV-table td:nth-last-child(2){ display: none; }
Teraz ukończony interactive grid wygląda następująco. Wszystkie walidacje itp. będą działać, ponieważ w rzeczywistości istnieje wiele kolumn. Możemy również ustawić walidację required w oparciu o wartość typu kolumny.
W drugim przypadku dodamy kilka utrudnień:
Najpierw tworzymy kolejną tabelę DYNAMIC_IG_INPUTS2, która zawiera pięć kolumn: Label, column type oraz kolumny value dla varchar, dropdown (liczba) i date.
| Column Name | Data Type |
| ID | NUMBER |
| LABEL | VARCHAR2(100 CHAR) |
| COLUMN_TYPE | VARCHAR2(100 CHAR) |
| VALUE_VARCHAR | VARCHAR2(100 CHAR) |
| VALUE_DROPDOWN | VARCHAR2(100 CHAR) |
| VALUE_DATE | DATE |
Kolejny krok jest taki sam jak wcześniej: utworzenie nowego, włączonego interactive grid ze Static ID mygrid z automatycznym procesem.
Utworzymy jeszcze jedną tabelę: DYNAMIC_LOV. Posłuży ona jako typ kolumny dla grida. Będzie miała dwie kolumny: display_value i return_value.
| Column Name | Data Type |
| DISPLAY_VALUE | VARCHAR2(100 CHAR) |
| RETURN_VALUE | VARCHAR2(100 CHAR) |
Dodamy kilka wartości do tabeli.
I ustawimy kolumnę Column Type w gridzie jako Select list z wartościami z dynamic_lov.
Ustawimy kolumnę VALUE_DROPOWN jako select list, a jej Type na SQL Query z kodem podobnym do poniższego. W sekcji Cascading List of Values ustawimy Parent Column na COLUMN_TYPE. Nie jest to idealny przykład, ale zadziała.
select display_value, return_value from ( select distinct job as display_value, job as return_value, 'job-dropdown' as dropdown_type from emp union select distinct dname as display_value, dname as return_value, 'department-dropdown' as dropdown_type from dept ) where dropdown_type = :COLUMN_TYPE
Teraz trudniejszą częścią tego rozwiązania są Dynamic Actions. Najpierw potrzebujemy DA w punkcie inicjalizacji Rows (podobnie jak w poprzednim przykładzie), ale musimy również ustawić wiele akcji, aby przesłać wartość COLUMN_TYPE, a następnie odświeżyć kolumny grida za pomocą funkcji JavaScript setcolumns.
Skończyło się na czterech Dynamic Actions. Nie wszystkie są niezbędne i zależy to od tego, w którym miejscu wiersza znajduje się dynamiczny dropdown. Trudno jest jednak poprawnie przygotować komórki i listy rozwijane po wybraniu COLUMN_TYPE.
Dynamic Actions to połączenie submit values dla COLUMN_TYPE i wywoływania funkcji JavaScript setcolumns. Dodano jedną dodatkową funkcję, która wykonuje Set Focus na VALUE_VARCHAR – aby opuścić pole typu kolumny i przesłać wartość po zmianie.
Po dodaniu Dynamic Actions grid będzie wyglądał następująco.
I znowu to samo. Kroki porządkowe są takie same jak wcześniej. Zmieniamy nazwę kolumny wartości, która jest pierwsza od lewej, na Value i ukrywamy niepotrzebne kolumny za pomocą tego samego kodu CSS.
Ostateczny wynik wygląda tak:
Jeśli chodzi o dynamiczne typy kolumn, często nie można tak naprawdę korzystać z natywnych autoryzacji i walidacji. Ale nie w tym przypadku. Wszystkie kolumny mogą korzystać z autoryzacji, wszystkich warunków po stronie klienta/serwera, a także walidacji. Tych natywnych dla typu kolumny, ale także dodatkowych niestandardowych walidacji, które można dołączyć do kolumny interactive grid.
W najgorszym przypadku, gdy JavaScript i CSS zawiodą, wprowadzanie danych nadal będzie bezpieczne i przetworzy wszystkie walidacje. Nie ma tu żadnych workarounds ani kompromisów.
Aplikacja demonstracyjna z oboma przypadkami jest dostępna TUTAJ. Cały kod, którego użyłem w tym artykule, jest dostępny TUTAJ! Daj mi znać, co sądzisz o tym rozwiązaniu! I oczywiście dziękuję za lekturę. Jeśli interesują Cię podobne treści związane z APEX, sprawdź inne moje artykuły na tym blogu: