Benutzer-Werkzeuge

Webseiten-Werkzeuge


prog:apex_select2_ajax

Oracle Apex 18.2 - Select2 Java Script Library in einem Classic Report integrieren- Interactive Grid Funktionaliät mit JQuery in einem Classic Report nachimplementieren

Aufgabe: Für eine Ausschilderung auf Basis der Displays von Philips (https://www.philips.de/p-m-pr/digital-signage-losungen ) ist eine Pflegemaske für die Raumzuordnung mit einem entsprechenden Richtungspfeil in der Raumliste zu implementieren.

In der Datenbank wird dabei hinterlegt welches Display für welche Räume zuständig ist. Je nach Standort des Displays wird dann der entsprechende Richtungspfeil für die Veranstaltung in dem Raum angezeigt.

Rand-Parameter:

  • Die Auswahlliste für den Richtungspfeil soll auch den Pfeil anzeigen! Das ist die Herausforderung!
  • Die bearbeiten Zeilen sollen hervorgehoben dargestellt werden
  • Der vorherige Richtungspfeil wird mit angezeigt, um die Änderung leichter nachzuvollziehen
  • Das Setzen eine Richtungspfeil ordnet den Raum dem Display zu
  • Die Anzahl der Räume ist begrenzt ⇒ alle Räume können in einer Tabelle auf der Seite dargestellt werden
  • 8 Richtungspfeile können ausgewählt werden ( Basierend auf den Pfeilen um APEX Font ( https://apex.oracle.com/pls/apex/f?p=42:icons ) )
  • Die Tabelle kann nach den bereits zugeordneten Räumen gefiltert werden

So sieht die Maske nun am Ende aus:

  Oracle APEX - dynamsiche Select2 Liste in Clasic Report


Lösungansatz

1. Versuch:

Im ersten Schritt habe ich das über einen Interactive Grid versucht zu lösen, die Anzahl der Zeilen ergibt sich aus den Räumen, pro Zeile kann über eine Checkbox der Raum zu Display zugeordnet und ein Richtungspfeil ausgewählt werden. D.h. die treibende Query des Berichts ist ein Select über die verfügbaren Räume mit einem Out Join über die bestehenden Zuordnungen. Im der Matrix kann dann der Richtungspfeil gewählt werden beim Click auf die jeweile Zeile. Beim Speichern wird die Default Funktion abgefangen und dann mit einem eigenen Handler das ganze gespeichert ( siehe z.B. hier ⇒ Oracle Apex 5 - Interactive Grid anpassen und konfigurieren - DML ändern bei ORA-22816: unsupported feature with RETURNING clause.

Leider funktioniert das aber dann nicht so recht wie gewünscht, nur die geänderten Daten werden ja beim Submit übertragen und die Oberfläche passt nicht so wirklich zu der an sich einfachen Anforderung einer einfachen Raum Matrix. So ist es z.B. nicht so einfach eigene GUI Elemente in den Bearbeitungsmodus einer einzelnen Tabellenzelle zu implementieren.

2. Versuch

Die Raum Liste mit einem Classic Report darstellen und die Konfiguration der Richtungspfeile über eine dynamisch eingeblendete Select Liste ermöglichen.

Als Basis für die Select Liste dient die JavaScript Library select2 ⇒ https://select2.org/.

Für Oracle Apex gibt es bereits ein Plugin für diese Liste, siehe ⇒ https://apex.world/ords/f?p=100:710:4758666274399::::P710_PLG_ID:BE.CTB.SELECT2 von Nick Buytaert.

Das Plugin dient in diesem Fall aber mehr dazu die JavaScript Abhängigkeiten in die Seite zu laden und um das Template Objekte für die Auswahl Liste zu erzeugen.

Problem mit der Lösung:

Wird eine Region neu in der Seite geladen (ohne Submit der Seite!) wird der Event Handler (definiert über einen JQuery Selector) nicht mehr an die Html Tabellen Zelle gebunden!

D.h. um die Tabelle neu einzulesen ist immer ein Submit und ein vollständiges Laden der Seite notwendig damit der Eventhandler wieder richtig funktioniert.

Mein Fehler ⇒ Einfach den „Event Scope“ der Dynamic Action auf „Dynamic“ ( Binds the event handler to the triggering element(s) for the lifetime of the current page, irrespective of any triggering elements being recreated via Partial Page Refresh (PPR)) setzen!


Umsetzung: Dynamisch eine Select2 Auswahl Liste in einem Classic Report verwenden und Auswahl in der DB speichern

Voraussetzung

Select2 Plugin in die Applikation laden
  1. Aus „src\main\plugin“ Datei item_type_plugin_be_ctb_select2.sql als Plugin in die Applikation laden
Template Select Liste in der Seite hinterlegen

Diese Select Liste wird nicht auf der Seite direkt dargestellt.

Diese Liste wird mit JQuery geclont ( in meine Beispiel über die Klasse referenziert) und in der jeweiligen Tabellen Zelle dargestellt.

Ablauf:

  • Page Item Select Liste mit dem Fonts für die Richtungspfeile anlegen ( wie P100_ICON_VALUES) und mit den möglichen Werte versorgen (select oder statische Liste)
  • Diese Page Item mit „On Page Load“ mit Dynamic Action („Hide“) ausblenden
  • CSS Klasse auf dem Element hinterlegen unter Advanced „CSS Clases“ „DEFAULT_SELECT_ICON“

Nun kann je nach Bedarf auch noch etwas an der Optik mit der Liste über die Klasse gearbeitet werden.


Bericht - Classic Report - über die Räume erstellen

Eine klassischen APEX HTML Tabelle auf Basis eines SQL Kommandos mit einem „Classic Report“ in die Seite einfügen.

Wichtig ist es die ROWNUM in die Abfrage mit aufzunehmen, alternativ kann natürlich auch ein anderer eindeutiger numerischer Schlüssel in der Ergebnismenge verwendet werden.

Die ROWNNUM dient später als Schlüssel für eine dedizierte Tabellenzeile und muss mit dieser angezeigt werden.

Wird die Liste in SQL sortiert, die Rownum erst in einem äußeren Select hinzufügen, damit die Rownum auch der eigenen Sortierung folgt.

Unter „Attributes“ auf dem Classic Report über die Template Options „Alternating Rows“ und „Row Highlighting“ ausschalten (auf Disable setzen) , wird später manuell durchgeführt.


Modell der Daten aus der Tabelle in ein Objekt einlesen

Wir müssen ja wissen welche Element wie auf der Seite zugeordnet wurden.

Dazu speichern wir uns die Werte in der angezeigten Tabelle in ein Modell ( Ein Recordset in einem Array).

Der Java Script Block wird auf Page Ebene in „Function and Global Variable Declaration“ hinterlegen.

Die Daten werden aus der Tabelle in ein Model geladen, als Parameter dient eine Spalte der Tabelle, von der Spalte aus wird die ganze Zeile referenziert und ausgewertet, die Spalte ROWNUM dient als Primary Key für das Array. Da ja kein Text für die Icons in der Zelle steht wird die Klasse ausgewertet und der Wert für das Icon ausgelesen.

// ---------------- Schritt 1 --------------------
// create Model from table view
 
// build the dataset from all rows of the table
// build the dataset from all rows of the table
function getDataSet( table_rows ,pkRowName) {
    var elemName  = {};
    var rownum = 0;
    var dataset = [];
    var data_row = {};
    table_rows.each( 
          function (idx,elem)  {
                  // read one row of the table
                  data_row = {};    
                  $(elem).closest('tr').children().each( 
                        function (idx,elem)  {
                            // read the hint which data cell 
                            elemName=$(elem).attr("headers");                            
 
                            // read and store the value of the cell
                            elemValue = elem.innerText;
 
                            if (elemValue.length == 0) {
                                //console.log("Read length :: " + elemValue.length + 'for row :: '+elemName);
                                //console.log($(elem));
                                elemValue='-';
                                // check if span element exits and read class                                
                                $(elem).children().each (
                                    function (idx,spanelem)  {
                                        elemValue=$(spanelem).attr("class");     
                                        elemValue=elemValue.replace(/ fa-2x/g,'');
                                        elemValue=elemValue.replace(/fa /g,'');
                                        elemValue=elemValue.replace(/ fa/g,'');
                                        //console.log("get class  :: " + elemValue + 'for row :: '+$(spanelem));
                                    }
                               );
                            }
 
                            data_row[elemName]= elemValue;       
 
 
                            // get the primary key (index counter) for the collection
                            if (elemName == pkRowName) {
                                  rownum=elem.innerText; 
                            }                       
                       }          
                  );
                dataset [rownum]= data_row;              
            }
     );
    return dataset;
}
 
 
// get the orignal value from the dataset for this cell
function getCellValueFromDataSet(cell,pkRowName) {
    var rownum=-1;
     $(cell).closest('tr').children().each( 
                        function (idx,elem)  {
                            // read the hint which data cell 
                            elemName=$(elem).attr("headers");                           
                            // get the primary key (index counter) for the collection
                            if (elemName == pkRowName) {
                                  rownum=elem.innerText; 
                            }                       
                       }          
                  );
    return  dataset[rownum];
} 
 
 
 
 
//-----------------------------------
// first init of the dataset
var headerColumnName="ICON_DISPLAY";
//dataset
var dataset = getDataSet($('[headers="'+headerColumnName+'"]'),'ROWNUM');

Java Script Funcionen für die Verwendung des Select2 Elementes hinterlegen

Auf Page Ebene in „Function and Global Variable Declaration“ hinterlegen.

Init Functions für die Select2 Liste:

 
//-----------------------------------
 
 
// ------
// globals
var actCell = {};
var lastCell = {};
var actCellValue = {};
var lastCellValue = {};
var actCellText = {};
var lastCellText = {};
var lastTriggerElement = {};
var secondClick=false;
 
 
 
// select2 Functions 
 
 
// ----
// get the option list as template
function iconSelectList() {
   return $('.DEFAULT_SELECT_ICON').clone().addClass('select-icon');
}
 
 
 
var iconSelectList = iconSelectList();
 
 
//-------
// default text if you open the element
var defaultPlaceholderText="Icon auswählen";
function getDefaultPlaceholderText (elem){
    return defaultFristText;
}
 
//--------
// set the value of a cell
function setCellvalue ( cell, text, textClass) {
     var textSpan=$(cell).find('[id^=ROW_]');
     console.log('Set Text  => ' + text);    
     console.log('Set Class => ' + textClass);
 
    //only if something was selected
    if (text != defaultPlaceholderText) {
              // console.debug(cell);              
              // $(textSpan).text(text);
               $(textSpan).removeClass();
               $(textSpan).addClass('fa '+textClass+' fa-2x');
             }
      else {
           // insert the old value
 
            $(textSpan).text(lastCellText);
            $(textSpan). removeClass();        
            $(textSpan).addClass('fa ' +lastCellValue +' fa-2x');
      }        
} 
 
//--------
// get the Value of a cell
function getCellValue( cell )  {
    var cellValue = {};
    var textSpan=$(cell).find('[id^=ROW_]');
    cellValue =  $(textSpan).attr('datavalue');
    console.log("Read akt value :" + cellValue);
    return cellValue;
}
 
// get the Text of the cell
function getCellText( cell )  {
    var cellText = {};
    var textSpan=$(cell).find('[id^=ROW_]');
    cellText =  $(textSpan).text();
    console.log("Read akt text :" + cellText);
    return cellText.toString();
}
 
 
//--------------
// format select2 Entries
function setListEntry(elem) {
   var originalOption = elem.element;
   return '<i aria-hidden="true" class="fa  '+ elem.id+' fa-2x">     '+elem.text+'</i>'; //iconFont
}
 
 
//---------
// return the selected element
function returnSelection(elem){ 
   return elem.text;
}
 
 
//---------
//
// Clear the open elementes
function clearAll(elem) {
    // invalid the last element 
    lastTriggerElement = {};
    //set the value on the last cell back
    if (secondClick) {
 
      console.log("Auswahl Vaule ->" +  $(".select-icon").val());
      console.log("Auswahl text -> " +  $('.select-icon').find(':selected').text() );
 
      actCellText  = $('.select-icon').find(':selected').text() ;
      actCellValue = $(".select-icon").val(); 
 
      $( ".select-icon" ).select2('destroy');
      $( ".select-icon" ).remove();
 
      setCellvalue( actCell, actCellText, actCellValue);
    }
 
    // reset all
    actCell={};
    secondClick=false;
 
}

Die eigentliche Liste implementieren:

//---------
//create new Box at $(this.triggeringElement).closest('td') 
function initSelect2( cell , triggerElem ) {
 
    // trigger only if first time clicked
    if (lastTriggerElement == triggerElem ) {
        console.log("Same Element !");
        console.log(triggerElem);
    }
    else {
 
        lastTriggerElement=triggerElem;
 
         //remeber the old values
 
        lastCellValue=actCellValue;   
        lastCellText=actCellText; 
 
        actCellValue=getCellValue( $(triggerElem).closest('td'));
        actCellText=getCellText(   $(triggerElem).closest('td'));
 
        //remenber the last edit cell from the history
        lastCell=actCell;     //( actCell ? actCell : cell  );
 
 
        // remove all other boxes on the page
 
        lastCellText  = $('.select-icon').find(':selected').text() ;
        lastCellValue = $(".select-icon").val(); 
 
 
        $(".select-icon").select2('destroy');
        $(".select-icon").remove();
 
 
 
 
        //set the value on the last cell
        // only if value extis (not first time)
        if (secondClick) {
            setCellvalue( lastCell, lastCellText, lastCellValue );    
        }
        else {
            secondClick=true;
        }
 
        //remeber the actual values of in the globals
        actCell=cell;
 
         //clear this cell    
        setCellvalue( cell, "" , "" );
 
        // set at the first java Script option 
        console.log("Remember akt values t::"+ actCellText + " class:"+actCellValue);
 
        // add box to cell
        cell.append(iconSelectList);
 
        //create select2 box
        $('.select-icon').select2({      
                  width:         "100%" , 
                  minimumResultsForSearch: Infinity            , 
                   allowClear:          true                    , 
                   placeholder:    'Icon auswählen'      , 
                  templateResult:     setListEntry              , 
                  templateSelection:  returnSelection           ,
                  //dropdownCssClass:   'iconFont'                 ,                   
                  escapeMarkup:       function(m) { return m;  }
        });
 
        //set the list to the actual value
        console.log("set pre selected value  t::"+ actCellText + " class:"+actCellValue);
        $('.select-icon').val(actCellValue).trigger('change');
    }
}

Tastatur Handling mit berücksichtigen

Nach der Auswahl eines Elements soll ja die Select2 Liste wieder entfernt und das Icon angezeigt werden.

Tastatur Event Handler definieren:

//---------
//
// Key Handling
//
 
document.onkeydown = TabExample;
 
function TabExample(evt) {
  var evt = (evt) ? evt : ((event) ? event : null);
  var tabKey = 9;
  if(evt.keyCode == tabKey) {
    clearAll(this);
  }
}

Auswahl Liste in dem Bericht beim Click auf eine Zelle aktiveren

Über eine Dynamic Action auf einen JQuery Selector den Java Skript Aufruf bei einem Click in eine Zelle aktivieren.

JQuery Selctor für alle Zellen ist hier bei: [headers=„ICON_DISPLAY“]

Zuerst wird optisch markiert, in welcher Zeile wir etwas editiert haben:

    // mark the triggered element
    if (lastTriggerElement == this.triggeringElement ) {
 
        console.log("Same Element !");
    }
    else {
 
 
    // the complete line
    $(this.triggeringElement).closest('tr').css({
        "background-color": "#fbfbdf"
    });
 
    //the box
    $(this.triggeringElement).closest('td').css({
        "background-color": "#e5f3e5"
    });
 
 
}

Select2 Liste einfügen:

// create select2 Element 
initSelect2( $(this.triggeringElement).closest('td') ,this.triggeringElement);

Auswahl Liste wieder deaktiveren

Klickt der Anwender in die nächste Zelle soll die Liste wieder ausgeblendet werden.

Dynamic Action anlegen mit JQuery Selector auf die anderen Spalten der Tabelle wie : [headers=„RAUMNR“] , [ headers=„RAUM“] , [headers=„ICON_NAME“]

JavaScript code:

// clear the last entries
clearAll( $(this.triggeringElement) );

Die gleiche JavaScript Methode wird auch beim Save Button etc. aufgerufen um die Liste wieder zu schließen.


Speicher der Auswahl / Änderungen

Über eine Button „Speichern“ wird die geänderte Tabelle in der Datenbank gespeichert.

Vorbereitung:

  • Save Button anlegen
  • Hidden Item für die zu übertragenden Werte anlegen ( diese müssen die Eigenschaft „Value Protected auf „No“ besitzen!)
  • Java Script Block über das Übertragen des Modells in die Hidden Items ( Liste mit “:„ separiert pro Wert )
  • Dynamic Action für das Speichern der Daten erzeugen
    • JavaScript Block um die Tabelle auszuwerten und in das Modell zu übertragen
    • PL/SQL Block um die Änderungen an die DB zu übertragen
    • JavaScript Block um eine Meldung anzuzeigen
Funktion um notwendigen Werte aus dem Modell in die Hidden Items zu übertragen

Code um die Werte aus dem Modell auch auszulesen (In dem Java Script Block der Seite hinterlegen):

function setItemValues(dataset){
    //clear old entries
    $s('P322_SUMBIT_ROOM_LIST','');
    $s('P322_SUBMIT_ICON_LIST','');
 
    //loop over the dataset
    dataset.forEach (
 
     function (data_row)  {
           //get Room Nr
           akt_val_raumnr=$v('P322_SUMBIT_ROOM_LIST');
           elem_raumnr=data_row['RAUMNR'];
 
 
          elem_icon=data_row['ICON_DISPLAY'];
 
         if ( elem_icon != '-' &&  elem_icon.length > 0  && elem_icon != 'null' ) {
 
              akt_val_icon=$v('P322_SUBMIT_ICON_LIST');
 
              // add icon to list
              akt_val_icon+=elem_icon+':'
              $s('P322_SUBMIT_ICON_LIST',akt_val_icon);
 
              //add room to list
              akt_val_raumnr+=elem_raumnr+':'
 
              $s('P322_SUMBIT_ROOM_LIST',akt_val_raumnr);
 
             console.log(" Room Nr  :: " + elem_raumnr + ' ICON :: '+elem_icon);
          }
 
         }
    );
 
 
}

Dieser Teil ist nicht parametrisiert und muss dann auf die jeweiligen Gegebenheiten angepasst werden.


Dynamic Action anlegen

Die Dyanamic Action zum Speichern besteht aus 3 Blöcken, Werte einsammeln, Werte übertragen und in die DB schreiben, Ergebnis-Meldung anzeigen.

Ablauf Java Script Block 1
  1. Modell auslesen
  2. Daten aus den Modell in die entsprechenden Page Items schreiben, diese müssen die Eigenschaft „Value Protected auf „No“ besitzen!

Java Script Code für das Setzen der Page Items

// clear the last select2 list if still open
clearAll( $(this.triggeringElement) );
 
 
//-----------------------------------
// first read again the dataset
// dataset
// variable defined in the main part of the page!
//
dataset = getDataSet($('[headers="'+headerColumnName+'"]'),'ROWNUM');
 
// copy the values to the submit items
setItemValues(dataset);
 
 
//remove last : if extis
 
akt_val_raumnr=$v('P322_SUMBIT_ROOM_LIST');
akt_val_icon=$v('P322_SUBMIT_ICON_LIST');
 
 
if(akt_val_raumnr.slice(-1) == ":"){
    akt_val_raumnr = akt_val_raumnr.slice(0,-1)+ "";
}
 
if(akt_val_icon.slice(-1) == ":"){
    akt_val_icon = akt_val_icon.slice(0,-1)+ "";
}
 
$s('P322_SUBMIT_ICON_LIST',akt_val_icon);
$s('P322_SUMBIT_ROOM_LIST',akt_val_raumnr);
 
 
//debug
console.log( 'P322_SUMBIT_ROOM_LIST :: ' + $v('P322_SUMBIT_ROOM_LIST') );
console.log( 'P322_SUBMIT_ICON_LIST :: ' + $v('P322_SUBMIT_ICON_LIST') );
console.log( 'P322_DISPLAY          :: ' + $v('P322_DISPLAY') );
PL/SQL Block für das eigentliche Speichern der Daten

Die Daten werden als “:“ separierter Text String übertragen! Und dann in PL/SQL mit Hilfe von „apex_string.split“ wieder in ein Array zurück übertragen.

Für das Fehlerhandling im PL/SQL Block zwei Hidden Item anlegen:

  • P322_MESSAGE (Value Protected auf No!)
  • P322_MESSAGE_CODE (Value Protected auf No!)

In der Dynamic Action:

DECLARE
 
 v_message VARCHAR2(4000);
 v_message_code PLS_INTEGER:=0;
 
BEGIN
 p_aus_masterdata.set_display_room_assigments( p_display =>  :P322_DISPLAY
                                     , p_room_group_list =>  :P322_SUMBIT_ROOM_LIST
                                     , p_room_icon_list  =>  :P322_SUBMIT_ICON_LIST 
                                     ,  p_message        => v_message
                                     ,  p_message_code   => v_message_code) ;
 
 
  :P322_MESSAGE := v_message;
  :P322_MESSAGE_CODE :=v_message_code;
END;

Darauf auchten das bei „ITEMS to Submit“ auch alle notwendigen ITEMs eingetragen werden und bei „Items to Return“ die beiden Message Items hintelegt sind!

In der Datenbank (in Auszügen):

PROCEDURE set_display_room_assigments( p_display NUMBER 
                                     , p_room_group_list VARCHAR2
                                     , p_room_icon_list VARCHAR2
                                     , p_message      OUT VARCHAR2
                                     , p_message_code OUT INTEGER) 
IS
 
    v_routine_name   VARCHAR2 (50) :=    g_pck  || '.set_display_room_assigments';
    v_count INTEGER;
    v_icon_array wwv_flow_t_varchar2;
    v_room_array wwv_flow_t_varchar2;
 
    v_icon_name VARCHAR2(256);
    v_room_pk NUMBER(11);
 
    v_message_code PLS_INTEGER:=0;
    v_message_text VARCHAR2(4000):='Raumzuordnung gespeichert'; 
 
BEGIN
 
 
  v_icon_array := apex_string.split( p_room_icon_list, ':' );
  v_room_array := apex_string.split( p_room_group_list, ':' );
 
  DELETE FROM RaumDisplays WHERE DISPLAY_ID=p_display;
 
 
   IF v_room_array.COUNT  > 0 THEN
        FOR i IN v_room_array.FIRST .. v_room_array.LAST
        LOOP
          IF v_room_array.EXISTS(i) THEN
 
            -- get the pk of a room
            SELECT  ... INTO v_room_pk
             WHERE xxxxr=v_room_array(i);
 
          END IF;
          IF v_icon_array.EXISTS(i) THEN          
                  INSERT INTO RaumDisplays ( ID, DISPLAY_ID, ICON_NAME, SCHULRAUM_OID ) 
                   VALUES
                   ( RaumDisplays_seq.NEXTVAL
                   , p_display
                   , v_icon_array(i)
                   , v_room_pk);
            END IF;   
 
        END LOOP;
   END IF;
 
   COMMIT;    
 
   p_message_code:=     v_message_code;
   p_message     :=     v_message_text;
 
EXCEPTION
    WHEN OTHERS THEN
    p_message      := 'Error : Update Room to Display Assigment failed :: ' ||SQLERRM;
    p_message_code :=  SQLCODE;
    raise_application_error(-20001, p_message);  
 
 
END set_display_room_assigments;
JavaScript Block für das Anzeigen einer Meldung

Da „apex_application.g_print_success_message“ nur beim Rendern der Seite funktioniert ( z.b. in einem Prozess in PL/SQL) muss eine inline Message über die API verwendet werden.

Nächste Action in Java Script:

apex.message.clearErrors();
 
v_message     =$v('P322_MESSAGE');
v_message_code=$v('P322_MESSAGE_CODE');
 
if ( v_message_code != '0' ) {
 
    apex.message.showErrors([
      {
        type: apex.message.TYPE.ERROR,
        location: ["page"],
        message: v_message,
        unsafe: false
      }
    ]);
} 
else { 
  apex.message.showPageSuccess( v_message );
}

Beim Speichern wird dann eine entsprechende Box mit einer Meldung aus der DB angezeigt, hier ein Fehler:

 Oracle Apex Inline Error Message JavaScript API apex.message.showErrors


Optimierungspotential

  • Schöners und moderners JavaScript ( man ist halt doch pl/sql entwickler 8-) )
  • Weniger Abhängigkeiten zu globalen Variablen
  • Mehr Parametrisierung um das einfacher auf andere Anwendungsfälle zu übertragen

CSS Spielereien:

//Breite anpassen
.select2-wrapper {
    width: 200px;
}
 
// Größe und Position anpassen
.select2-results .fa {
    float: left;
    position: relative;
    line-height: 20px;
}

Quellen

Cookies helfen bei der Bereitstellung von Inhalten. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Rechner gespeichert werden. Weitere Information
"Autor: Gunther Pipperr"
prog/apex_select2_ajax.txt · Zuletzt geändert: 2019/01/14 12:37 von Gunther Pippèrr