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$ = view.view$.find( ".a-GV-w-scroll tbody" )
        , 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

  • Jari Laine 06-JAN-24 08.17.30.701201 AM

    Hi Kai,

    Thanks for informing this.

    I did try check, but quickly didn't find solution. IG handles internally calculating that sequence field and there isn't anything in custom code that sets that sequence number.

    I still keep checking and let you know if I find solution.

    Regards,
    Jari

  • Kai 04-JAN-24 04.00.37.939943 PM

    Hi Jari,
    nice pice of code. But it dosn't work, if the application primary language ist set to german. In Germany the '.' is the thousand separator, and the ',' is the decimal point. So the calculation for the sort column leads to totally other results. you can see it, if you set sequnce column to visible.
    Is there a way to use the apex.local in the javascriopt as described in <a href="https://blog.cloudnueva.com/easy-number-handling-in-javascript-with-apexlocale" target ="_blank">https://blog.cloudnueva.com/easy-number-handling-in-javascript-with-apexlocale</a>?
    Greetings from germany
    Kai

  • Mario 14-NOV-23 04.56.12.844260 PM

    Thank Jari, I took a look at the IG Cookbook and found the use of the >defaultModelOptions.sequenceField option but it seems that it's not documented in the Apex API document though. Thanks.

  • Jari Laine 12-NOV-23 08.48.03.778967 AM

    Hi Mario,

    Good point.

    Newer checked is it documented. I did take it from John Snyders IG Cookbook sample application page 11.

    Regards,
    Jari

  • Mario 10-NOV-23 08.54.15.773983 PM

    Hello,
    This is a great implementation. The only part that I don't get is the sequenceField in defaultModelOptions.sequenceField. I can not find this option in the Apex JS API. Can you explain it more in detail please ?

  • Chris 21-AUG-23 06.51.29.211988 AM

    Thank you Jari. Works like a charm.