Skip to Main Content

Drag & Drop Interactive Grid rows

Here's an example that lets you drag and drop the rows of an interactive grid to arrange the records the way you want. A table used in an interactive grid must have a column that stores the sort order. If your table doesn't already have a column that can be used for this, edit the table and add a new column.

alter table <your table name> add (display_seq number);
update <your table name> set display_seq = rownum;
alter table <your table name> modify display_seq not null;

First, create an editable interactive grid and run the page. Sort the interactive grid in ascending order by display sequence column.

Then hide the display sequence column from the report.

Save the report.

Create new column to the interactive grid .

Enter a column name. Change the type to “HTML expression”. Add to the HTML Expression:

<span aria-hidden="true" class="fa fa-sort z-sortable--handle"></span>

Change the Source Type to None. Add a Column Initialization JavaScript Function:

sortableIG.initHandleColumn

Edit the other interactive grid columns and disable Sorting for all columns except the display sequence column.

Edit the display sequence column and add a Column Initialization JavaScript Function:

sortableIG.initSequenceColumn

Add to the interactive grid region Initialization JavaScript Function:

sortableIG.initRegion

 

Add to the page JavaScript Function and Global Variable Declaration:

// create a namespace for sortable IG
var sortableIG = {};

( function( $, region, sortableIG ) {

  // Set defaults
  sortableIG.options = {
    region: {
      defaultModelOptions: {
        sequenceStep: 10
      }
      ,defaultGridViewOptions: {
        reorderColumns: false
      }
    }
    ,handleColumn: {
      defaultGridColumnOptions: {
        noHeaderActivate: true
      }
      ,layout: {
        columnAlignment: "center"
        ,noStretch: true
      }
    }
    ,sequenceColumn: {
      features: {
      	sort: true
        ,canHide: false
        ,aggregate: false
        ,compute: false
        ,controlBreak: false
        ,groupBy: false
        ,highlight: false
        ,pivot: false
      }
    }
  }

  // sortable IG handle column Initialization code
  // call function in IG column Advanced: Column JavaScript Initialization Code
  sortableIG.initHandleColumn = function( options ) {

    return $.extend( true, options, sortableIG.options.handleColumn );

  }

  // sortable IG sequence column Initialization code
  // call function in IG column Advanced: Column JavaScript Initialization Code
  sortableIG.initSequenceColumn = function( options ) {

		// get sequence column name and store it for region Initialization JavaScript Function
    sortableIG.options.region.defaultModelOptions.sequenceField = options.name;

    return $.extend( true, options, sortableIG.options.sequenceColumn );

  }

  // sortable IG initialization code
  // call function in IG region Advanced: JavaScript Initialization Code
  sortableIG.initRegion = function( options ) {

    $( $x( options.regionStaticId ) ).on( "interactivegridviewchange interactivegridreportsettingschange", function( _event, ui ) {

      var view          = region( this.id ).call( "getViews", "grid" )
        , sortableArea$ = $( $x( this.id ) )
        , helperBgClass = "u-color-29-bg"
      ;

      // https://api.jqueryui.com/sortable/
      sortableArea$.sortable({
        handle:       ".z-sortable--handle"
        ,items:       "tr:has(.z-sortable--handle)"
        ,axis:        "y"
        ,tolerance:   "pointer"
        ,scrollSpeed: 8
        ,placeholder: "a-GV-cell u-color-1-bg"
        
        ,helper: function( _event, ui ){
          ui.children().each( function(){
            $( this ).width( $( this ).width() ).addClass( helperBgClass )
          })
          return ui;
        }
        
        ,update: function( _event, ui ){
          
          var model = view.model
            , item$ = $( ui.item )
            // get moved record
            , item = ( item$.length ) ? model.getRecord( item$.data( "id" ) ) : null
            // get next or previous row from visible table depending is record moved up or down
            , after$ = ( ui.originalPosition.top > ui.position.top ) ? item$.next() : item$.prev()
            , after = ( after$.length ) ? model.getRecord( after$.data( "id" ) ) : null
          ;
          
          // remove helper background class, if row isn't actually moved
          item$.children().removeClass( helperBgClass );
          // check if we have found record and place where record is moved
          if( item && after ){
            // if record is moved upwards, we need get previous record from model
            after = ( ui.originalPosition.top > ui.position.top ) ? model.recordAt( model.indexOf( after ) - 1 ) : after;
            // move record
             if( item !== after ){
              model.moveRecords( [item], null, after );
            }
          }
          
        }
      
      });

    });

    return $.extend( true, options, sortableIG.options.region );

  }

})( apex.jQuery, apex.region, sortableIG );

Add to the Page CSS Inline:

.z-sortable--handle{
  cursor: move;
}
.ui-sortable-helper{
  display: inline-flex;
}

Now when you run a page, you can drag and drop to arrange the rows of the interactive grid from the handle column.

See working example in apex.oracle.com.

If you need support for touch events, you can try jQuery UI Touch Punch. Download jquery.ui.touch-punch.js and upload the file to Static Application Files. Reference the file e.g. on page JavaScript File URLs:

#APP_FILES#jquery.ui.touch-punch.js

Comments

  • Jared 23 Oct 2024

    I also wanted to add, I decided to go with your original code. I altered the underlying column to be

    NUMBER

    instead of what it was,

    NUMBER(2, 0)

    , so that it could accept everything after the decimal. I also had to change some procedures that were hard-coded to get the record with SeqNo = 1. Instead, I am going to opt for something like these: https://stackoverflow.com/questions/3451534/how-do-i-do-top-1-in-oracle.

  • Jared 23 Oct 2024

    Thank you, Jari!

  • Jari Laine 23 Oct 2024

    Hi Jared,

    If you just want move rows, don't set sequence column and use below in page JavaScript

    // create a namespace for sortable IG
    var sortableIG = {};
    ( function( $, region, sortableIG ) {
     // Set defaults
     sortableIG.options = {
       region: {
         defaultGridViewOptions: {
           reorderColumns: false
         }
       }
     , handleColumn: {
         defaultGridColumnOptions: {
           noHeaderActivate: true
         }
       , layout: {
           columnAlignment: "center"
         , noStretch: true
         }
       }
     }
     // sortable IG handle column Initialization code
     // call function in IG column Advanced: Column JavaScript Initialization Code
     sortableIG.initHandleColumn = function( options ) {
       return $.extend( true, options, sortableIG.options.handleColumn );
     }
     // sortable IG initialization code
     // call function in IG region Advanced: JavaScript Initialization Code
     sortableIG.initRegion = function( options ) {
       $( $x( options.regionStaticId ) ).on( "interactivegridviewchange interactivegridreportsettingschange", function( _event, ui ) {
         var view          = region( this.id ).call( "getViews", "grid" )
           , sortableArea$ = $( $x( this.id ) )
           , helperBgClass = "u-color-29-bg"
         ;
         // https://api.jqueryui.com/sortable/
         sortableArea$.sortable({
           handle:      ".z-sortable--handle"
         , items:       "tr:has(.z-sortable--handle)"
         , axis:        "y"
         , tolerance:   "pointer"
         , scrollSpeed: 8
         , placeholder: "a-GV-cell u-color-1-bg" 
         , helper: function( _event, ui ){
             ui.children().each( function(){
               $( this ).width( $( this ).width() ).addClass( helperBgClass )
             })
             return ui;
           }
         , update: function( _event, ui ){ 
             // remove helper background class, if row isn't actually moved
             $( ui.item ).children().removeClass( helperBgClass );
           }
         });
       });
       return $.extend( true, options, sortableIG.options.region );
     }
    })( apex.jQuery, apex.region, sortableIG );
  • Jared 23 Oct 2024

    Is there a way to simply have the SeqNo increment by 1 only? I have this code that simply starts assigning SeqNo to 1, 2, 3, and so forth, for however many rows there are. I can't copy/paste the entire thing here due to a character length limit. But basically, it listens to event "interactivegridviewmodelcreate" and, on a given change, runs this function:

    function update(model) {

    var i = 1;

    model.forEach(function(record) {

    if (model.allowDelete(record)) {

    model.setValue(record, "SEQNO", i.toString());

    i++;

    }

    });

    }

    So what I would really like is just those parts of this code that allow reordering of rows - isolated from how the sequence number is incremented. I am reviewing your code, but, if you have any suggestions on where to start that would be great - I'm relatively new to APEX and don't really know JavaScript. Thank you!

  • Jari Laine 19 Oct 2024

    Hi Teddy,

    This should work in APEX 22.2, but unfortunately I can't test it.

    Are you sure you have set it up correctly?

    Create a simple application to reproduce the problem in your environment. Then export it to apex.oracle.com. Does it work on apex.oracle.com?

    Regards,
    Jari

  • Teddy A 18 Oct 2024

    Hi ,

    It doesn't for me .. Apex 22.2 .

    ANy ideas ?

    Thanks

    Teddy

    Uncaught TypeError: Cannot read properties of null (reading 'toString')

    at j (interactiveGrid.min.js?v=22.2.0:1:1369)

    at Object.getRecordMetadata (interactiveGrid.min.js?v=22.2.0:1:18412)

    at Object.moveRecords (interactiveGrid.min.js?v=22.2.0:1:29191)

    at HTMLDivElement.update (f?p=100:106:13609676601464::::::1117:21)

    at q.<computed>.<computed>._trigger (desktop_all.min.js?v=22.2.0:31:9611)

    at q.<computed>.<computed>._trigger (desktop_all.min.js?v=22.2.0:31:92360)

    at a.<computed> [as _trigger] (desktop_all.min.js?v=22.2.0:31:2891)

    at q.<computed>.<computed>.<anonymous> (desktop_all.min.js?v=22.2.0:31:91079)

    at q.<computed>.<computed>._clear (desktop_all.min.js?v=22.2.0:31:92201)

    at a.<computed> [as _clear] (desktop_all.min.js?v=22.2.0:31:2891)

  • Simon K 16 Sep 2024

    Worked great for me! Although i've had to add some rounding to the IG DML process as i need whole numbers. Would love to be able to tinker with the maths behind it as it seems to just calculate a figure between the 2 you've moved in between. Would ideally like "previous +1", but i'll play around with it. Thanks

  • Sufi 6 Sep 2024

    Perfect! It worked!!!

    Thank you so much for your effort in fixing the sample page!

    Now I got 8 interactive grids in that page with sorting enabled. Thanks again!

  • Jari Laine 5 Sep 2024

    Hi Sufi,

    I don't know any alternatives. Forgot mention that I did fix your sample page 5. And I think it works for two or more IG if you just do setup properly to all IGs. Or did you find another issue? 

    You don't need pass any region id to JavaScript function, because it does get region id from initialization parameters automatically.

    Regards, Jari

  • Sufi 5 Sep 2024

    Thanks Jari,

    Are there any alternatives that would allow me to implement sorting solution on a page featuring more than one Interactive Grids?

    Regards,

    Kareem