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

  • 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

  • Jari Laine 5 Sep 2024

    Hi Sufi,

    You get errors because you can't pass parameters to sortableIG functions how you have tried. And you have defined sequence column only one IG.

    Regards, Jari

  • Sufi 4 Sep 2024

    When I rename the sequence column in my second table, I encounter a "Sequence field not found" error. Additionally, I tried modifying the sortableIG.initRegion function to accept the region's static ID and sequence column name, but I received a type error: sortableIG.initRegion(...) is not a function. Please refer to page 5 in my application for more details.

  • Jari Laine 3 Sep 2024

    Hi Sufi,

    I don't know why it's not working if IG is subregion of Tabs Container. But it seems to work if changing selector for sortable to region id.

    Btw, tabs and region display selector are two different things. See your sample pages 3 and 4.

    Regards, Jari

  • Sufi 3 Sep 2024

    I reproduced the issue in https://apex.oracle.com/pls/apex/r/xxx

    I have a region display selector and having multiple tabs. each tab will have an ig.

    If I place outside of my region display selector then this solution works. Also this solution works for the first ig only.

    My credentials

    workspace - xxx

    username - xxx

    pw - xxx

  • Jari Laine 28 Aug 2024

    Hi Mike,

    Please reproduce issue on apex.oracle.com and share developer login details to workspace. I can take a look what could be problem.

    Regards, Jari

  • Mike 28 Aug 2024

    This doesn't work when we have Interactive Grid as tabs, can you please help what needs to be changed in code to fix this issue.Thanks!