Sort Table with Drag and Drop

  • 11
  • Idea
  • Updated 6 months ago
Finally implemented jQuery .sortable() to enable drag-and-drop reordering on a skuid table. turned out to be much simpler than I expected! 

I have an Order__c field on the object which starts at index 1, and I'm just updating that field after every reorder.

Here's my inline javascript:

(function(skuid){
var $ = skuid.$;
$(document.body).one('pageload',function(){
var component = skuid.$C('MyComponentId'),
   listContents = component && component.element.find('.nx-list-contents');
listContents.sortable({
   placeholder: "ui-state-highlight",
   stop: function( event, ui ) {
       var data = ui.item.data('object'),
           model = data.list.model,
           movedRow = data.row,
           target = $(event.target);
       
       target.children().each(function(index,tr){
           var row =  $(tr).data('object').row,
               order = row.Order__c;
          if (index + 1 !== order) {
              model.updateRow(row,'Order__c',index+1,{initiatorId:component._GUID});
          }
       });
           
   }
});
});
})(skuid);

Here's the table in action:
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb

Posted 2 years ago

  • 11
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Very slick. Going to use this for sure!
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
The one problem with this solution is that it makes tables with textareas in them uneditable or selectable.
Photo of Matt Sones

Matt Sones, Champion

  • 30,864 Points 20k badge 2x thumb
Hmm, even if you click on the 'edit' row action?
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
Yes, it blocks the rich text panel that comes up.
Photo of Karen Waldschmitt

Karen Waldschmitt, Official Rep

  • 8,040 Points 5k badge 2x thumb
John~

Clarification question: Is this specific to when you use Matt's snippet or to tables in general? 

Thanks!
Karen
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
This is anytime you apply a JQuery UI sortable to a skuid table.
Photo of Skuidward Tentacles (Raymond)

Skuidward Tentacles (Raymond), Champion

  • 17,224 Points 10k badge 2x thumb
I love this and can work around the inline editing of the text. Thanks!
Photo of Karen Waldschmitt

Karen Waldschmitt, Official Rep

  • 8,260 Points 5k badge 2x thumb
Matt~

This is very cool!

Thanks for sharing with the community :)
Karen
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
Karen, can you file the rich text issue as a bug with skuid?
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Works great. I additional save the model after setting the index.

// Sprint Work Drag & Drop reorder
    var component = skuid.$C('sk-39dlIj-483'),
        listContents = component && component.element.find('.nx-list-contents');
    
    listContents.sortable({
        placeholder: "ui-state-highlight",
        stop: function( event, ui ) {
            var data = ui.item.data('object'),
                model = data.list.model,
                movedRow = data.row,
                target = $(event.target);
           
            target.children().each(function(index,tr){
                var row =  $(tr).data('object').row,
                    order = row.Sprint_Rank__c;
                if (index + 1 !== order) {
                    model.updateRow(row,'Sprint_Rank__c',index+1,{initiatorId:component._GUID});
                }

            });
                $.blockUI();
                $.when( model.save() )
                    .done(function(){
                        $.unblockUI();
                    });            
       }
    });
Photo of mB Andréas K.LeF

mB Andréas K.LeF

  • 1,244 Points 1k badge 2x thumb
I took this and made a super fancy version :D
(Edited)
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
Going further down the fancy rabbit hole I used this http://tobiasahlin.com/spinkit/ to put in a css based spinner
Photo of mB Andréas K.LeF

mB Andréas K.LeF

  • 1,244 Points 1k badge 2x thumb
I remodeled my super fancy version above as a master page snippet invokable from child pages  :D
(Edited)
Photo of Amy Dewaal

Amy Dewaal, Official Rep

  • 8,200 Points 5k badge 2x thumb
John,

Have you tried tabbing over to the rich text area? 

Thanks!
Amy
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
You can tab over but you can't click into it. It's not really an acceptable solution to train users to do this.
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
I found a workaround for the rich text issue: use a handle for the sortable action. Add a row action with no actions and put in the class of the icon. Here is code with the handle, save and requery:

(function(skuid){
var $ = skuid.$;
$(document.body).one('pageload',function(){
var component = skuid.$C('johntt'),
listContents = component && component.element.find('.nx-list-contents');
listContents.sortable({
placeholder: "ui-state-highlight",
handle: '.fa-ellipsis-v',
stop: function( event, ui ) {
var data = ui.item.data('object'),
model = data.list.model,
movedRow = data.row,
target = $(event.target);

target.children().each(function(index,tr){
var row = $(tr).data('object').row,
order = row.Order__c;
if (index + 1 !== order) {
model.updateRow(row,'Order__c',index+1,{initiatorId:component._GUID});
}
});
$.blockUI();
$.when(model.save()).done(function(){
model.updateData(function(){
$.unblockUI();
});
});
}
});
});
})(skuid);
Photo of mB Andréas K.LeF

mB Andréas K.LeF

  • 1,244 Points 1k badge 2x thumb
Clever workaround! I like it
Photo of Matt Sones

Matt Sones, Champion

  • 31,336 Points 20k badge 2x thumb
Perfect! I'm definitely implementing the handle. Thanks, John!
Photo of Amy Dewaal

Amy Dewaal, Official Rep

  • 8,200 Points 5k badge 2x thumb
John,

I'm glad you found a workaround, and thanks for sharing it with the community!

Thanks!
Amy
Photo of Sri

Sri

  • 266 Points 250 badge 2x thumb
Matt, this is cool. Will come in very handy
Photo of Griffin Brown

Griffin Brown

  • 2,766 Points 2k badge 2x thumb
This is a dumb question, but how do I do this part:

"I have an Order__c field on the object which starts at index 1, and I'm just updating that field after every reorder."
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
Add a field called Order to the object and model or rename the field in the javascript to another field you already defined to do this.
Photo of Segolene Nicoloff

Segolene Nicoloff

  • 488 Points 250 badge 2x thumb
This is an extremely dumb question.... i've put the first presented code as a new in-line snippet, and modified the component Id and the field name...is there anything else I need to do to make it work? 
Photo of John Watson

John Watson

  • 584 Points 500 badge 2x thumb
It's not a snippet it's an inline code (the option without parenthesis).
Photo of Segolene Nicoloff

Segolene Nicoloff

  • 488 Points 250 badge 2x thumb
Thanks John! however when i move them the values of the index field remain the same. is it supposed to do that?
(Edited)
Photo of Craig Rosenbaum

Craig Rosenbaum

  • 4,776 Points 4k badge 2x thumb
How do you account for table pagination? For example, when showing rows 6-10 and reordering, it puts the sort order 1-5, ignoring the 1-5 that already exists on the table's first page. Any workaround here or just advising users to 'Show All' before reordering?
Photo of Craig Rosenbaum

Craig Rosenbaum

  • 4,776 Points 4k badge 2x thumb
Here's the solution:

In the stopHelper function I found the current table page and page size to get my start number.
component = skuid.$C(tableId),
    currentPage = component.list.currentPage,
    currentPageSize = component.list.currentPageSize,
    startNumber = currentPage * currentPageSize;

Then, I add that startNumber to the updateRow function:

model.updateRow(row, orderByFieldApiName, index + 1 + startNumber,
      {
        initiatorId: component._GUID
      });
Photo of Matt Sones

Matt Sones, Champion

  • 31,458 Points 20k badge 2x thumb
Nice work, Craig!
Photo of Craig Rosenbaum

Craig Rosenbaum

  • 4,776 Points 4k badge 2x thumb
Thanks! Always nice to answer your own question. I did notice if you have display all rows set, you need to add some logic to account for the 'All' response from currentPageSize:

 if (isNaN(startNumber)) startNumber = 0;
Photo of Karen Waldschmitt

Karen Waldschmitt, Official Rep

  • 8,160 Points 5k badge 2x thumb
Always love it when people are able to answer their own question and share their findings with the community. Thanks, Craig!
Photo of Anne Koenig

Anne Koenig

  • 80 Points 75 badge 2x thumb
Hey everyone - This is fantastic!  I am s javascript rookie, so could use a little help.  I implemented this for a multi-tab table, and when dragging a line, my mouse arrow is a significant distance away from the actual row in motion.  Any suggestions on how to improve the "tightness" of the arrow to the moving row to improve user experience? Thanks!
Photo of Craig Rosenbaum

Craig Rosenbaum

  • 4,776 Points 4k badge 2x thumb
Anne, could you post the code that you're using? I believe there are a couple versions in this thread. I had a similar issue when I first implemented it and needed to adjust the helper styling with some CSS to bring it closer. 
Photo of Anne Koenig

Anne Koenig

  • 80 Points 75 badge 2x thumb
I copied and pasted the exact version from Matt's original post.  Another wrinkle - I just installed Millau (11.0.6) release and now I get an error:   There was a problem rendering a component of type skuidpage: Cannot read property 'sortable' of undefined
Photo of Chad Gill

Chad Gill

  • 1,194 Points 1k badge 2x thumb
so painfully in the dark on this one but it is what i really want to implement.  Is there a walk thru on where/how implement?  Are you applying this snippet to a row action or what?