Capture sort order on Table Component

  • 1
  • Question
  • Updated 4 years ago
  • Answered
Skuidians,

I'd like to capture the sort order a user sets for a table.  Is that possible?  Alternatively, could I have a field determine the column ordering of a table?

For example,  I have a column in a table labeled "Ranking".  If I change it from descending to ascending I'd like to update a field.  Or can I have a field in a model control the Ranking sort order or which column I am sorting by.

Thoughts?

Thanks!
Peter
Photo of Peter Herzog

Peter Herzog

  • 1,664 Points 1k badge 2x thumb
  • happy about Skuid

Posted 5 years ago

  • 1
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Hi Peter, sorry for the delayed response on this. It's a complicated question to answer, and there is one piece of functionality that doesn't work quite as expected as of our latest available Skuid release, but we have resolved this in dev and should have a release out this week that includes the adjustment. More on this later.

I built out a basic prototype of this capability by creating a custom "Sort Order" object in one of our demo orgs. Here's what the object looks like from Schema Builder:


Full details:

API Name: Sort_Order__c
Custom Fields:
1. Skuid_Page__c: Lookup(skuid__Page__c)
2. Model_Id__c: Text(100)
3. Sort_Order__c: Text(255)

Basically the idea is that we'll be using this Object to store, for each User in your org, the Sort Order / Order By Clauses of particular Models on particular Skuid Pages. 

So here's the overview of the prototype I put together:
1.  have a Skuid Opportunities list page that has an "Opportunity" Model, and a Table that displays Opportunities in that Model
2. when a running User comes to this page, initially we do NOT load any Opportunities. Rather, we check a "Sort Order" model to see if there are any saved Sort Orders for any Models on this page, e.g. for our Opportunity model.
3. Let's assume initially there are no saved Sort Orders for this user. It's the first time the user has viewed this Skuid Page since we built it, so no Sort Orders have been created yet for them.
4. So what we do is, we go ahead and query our Opportunity model to get in some data.
5. Now, as soon as the user goes and changes the Sort order of one of the columns, we are listening in the background (using skuid.events.subscribe('models.loaded'), down at the bottom of our JavaScript which will be described shortly), and so we take the new sort order, and we use it to create a new record of our Sort_Order__c object. We then save this record to the salesforce.
6. If the user does any more sorts, we'll update this record.
7. Now say that the user comes back to this page later. On page load, we find saved Sort Orders for this user. So what we do is grab the "Sort_Order__c" field of our saved record, e.g. something like "Name ASC NULLS LAST, LeadSource ASC NULLS LAST", and we plop it into the Opportunity model's orderByClause, which controls the SOQL ORDER BY clause, and we then query the Model. 

So I imagine you'll want to tweak my logic somewhat, but here are the key pieces of our JavaScript API that you'll need to leverage to make this work:

1. model.orderByClause --- this is what sets the SOQL ORDER BY of a Skuid Model, and by extension the ordering of Tables that are bound to that Model. You'll want to read/write to this in order to influence the sort order of the Table.
2. model.updateData() --- in order to actually requery a Model, you'll need to call this function.
3. skuid.events.subscribe('models.loaded',function(loadResult){ });
Essentially this is an Event listener using Skuid's Publish/Subscribe Events API, which was introduced in our Skuid Spring 14 release (but is not well documented yet). It is fired after models have been loaded with new data --- so essentially, after a model.updateData() finishes. What you can do with this is say, "Looks like one of my Models got updated --- did its sort order change? If so, let's go update our Sort Order object".

So here's my code example. I have added a new JavaScript Resource with Location = Inline, with the following as its body: 

(function(skuid){   
   var $ = skuid.$;
   $(function(){
       // Do NOT try to auto-sort these Models
       var modelsToIgnore = {
          
       };
      var SortOrders = skuid.model.getModel('SortOrders');
      modelsToIgnore[SortOrders.id]=1
      // See if we have a saved Sort Order for the Opportunity model
      var modelsToLoad = [],
        uniqueModelsToLoad = {};
        
      var GetSortOrderForModel = function(model){
          var target;
          $.each(SortOrders.data,function(i,so){
              if (so.Model_Id__c===model.id) {target = so;return false;}
          });
          if (!target){
              target = SortOrders.createRow();
                SortOrders.updateRow(target,{
                   Model_Id__c: model.id,
                   Skuid_Page__c: skuid.page.id,
                   Sort_Order__c: model.orderByClause,
                   OwnerId: skuid.utils.userInfo.userId
                });
          }
          return target;
      };    
        
      if (SortOrders.data && SortOrders.data.length) {
          $.each(SortOrders.data,function(i,so){
              
             var modelId = so.Model_Id__c;
            if (modelId) {
                var model = skuid.model.getModel(modelId);
                if (model) {
                    // if we found a Model, apply our Sort Order to it
                    model.orderByClause = so.Sort_Order__c;
                    if (modelId in uniqueModelsToLoad) {}
                    else {
                        modelsToLoad.push(model);
                        uniqueModelsToLoad[modelId]=model;
                    }
                }
            }    
          });
      } else {
          // Create a default Sort Order for all Models 
          // NOT being sorted by our saved sorts
          $.each(skuid.model.list(),function(i,model){
                var modelId = model.id;
                if (modelId in uniqueModelsToLoad) return true;
                else {
                    if (model.orderByClause) {
                        GetSortOrderForModel(model);
                    }
                    // Add this Model to the list of Models to load,
                    // if it does not yet have any data in it
                    if (model.data.length===0 && !(modelId in modelsToIgnore)){
                        modelsToLoad.push(model);
                    }
                }
          });
      }
      if (modelsToLoad.length) {
          skuid.model.updateData(modelsToLoad);
      }
      
      
      // NOW, setup some listeners on all our Models
      // so that whenever they are updated,
      // we save our Sort Orders.
      skuid.events.subscribe('models.loaded',function(loadResult){
          $.each(loadResult.models,function(modelId,model){
              if (model.orderByClause){
                 var so = GetSortOrderForModel(model);
                 SortOrders.updateRow(so,'Sort_Order__c',model.orderByClause);
              }     
          });
          SortOrders.save();
      });
      
   });
})(skuid);

And here is a full page XML that relies on the existence of the Sort_Order__c object I describe above being in your org:

https://gist.github.com/zachelrath/0ed2282c821fc8171060



So lastly, once you get this up and working, you'll discover one "catch" that I alluded to earlier --- the Table component does not rebuild its Sort/Ordering icons (the little arrows) whenever its source Model is updated. We've fixed this as of Skuid 4.15+, which is not out yet, but should be by the end of this week. Until then, what you can do is to manually force your Table component(s) to rebuild themselves by changing this line 

if (modelsToLoad.length) {
      skuid.model.updateData(modelsToLoad);
}

to have a callback that manually finds Tables and forces them to do a full rerender:

if (modelsToLoad.length) {
      skuid.model.updateData(modelsToLoad,function(){
         skuid.$C('MyTable').render();
         skuid.$C('MyOtherTable').render();
     });
}

(Edited)
Photo of Peter Herzog

Peter Herzog

  • 1,664 Points 1k badge 2x thumb
Zach,

This is working fantastic!  Thank you very much for your help!!

Peter
Photo of Peter Herzog

Peter Herzog

  • 1,664 Points 1k badge 2x thumb
Zach,

Is it possible to take the captured sort order data and load the table components on the page accordingly?  

Said differently, if I sort by "Name ASC" this time it will load by "Name ASC" next time.

Thanks!

Peter 
Photo of Peter Herzog

Peter Herzog

  • 1,664 Points 1k badge 2x thumb
Zach,

It appears the simple solution I had in mind worked.  I referenced Sort_Order__c  from my Sort Order model in the "Fields to order records by" in another model and it worked.

{$Model.SortOrders.data.0.Sort_Order__c}}

I wasn't sure if the sequence of the model load would cooperate and allow this.  

Do you see any issue with this approach?  

Peter
Photo of Peter Herzog

Peter Herzog

  • 1,664 Points 1k badge 2x thumb
I am now seeing some issues with the approach.  If a record exists with a SOQL ORDER loaded it seems to work great.  I am noticing if there isn't a record and I sort the table I get results like:

Name ASC NULLS LAST, Date ASC NULLS LAST,{{$Model.MySortOrdersModel.data.0.Sort_Order__c}}

or I get:

Name ASC NULLS LAST, undefined

I am looking at what I changes need to be made to resolve this.  Any thoughts?

Thanks!

Peter
(Edited)
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Peter, 

Isn't this what's already happening for you? I'm pretty sure the JavaScript shown above is doing exactly this --- on initial page load, it looks through the SortOrders model and finds any saved Sort Orders, and uses these to initially sort data in the table. Is it not working this way for you? 

Zach
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Erg! I can't read javascript! I must join the dark side! :S
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
I kinda already look sith like anyway. Might as well.
Photo of Peter Herzog

Peter Herzog

  • 1,664 Points 1k badge 2x thumb
Ok, I reverted back and it appears to be working with one model, however we are using sort orders on multiple models now.  It's creating sort order records perfectly but some tables are not honoring them on load. 

Do I need to populate these (or make any other changes) to support multiple models?
var modelsToLoad = [],
uniqueModelsToLoad = {};
Should I add this?

if (modelsToLoad.length) {
     skuid.model.updateData(modelsToLoad,function(){
         skuid.$C('MyTable').render();
         skuid.$C('MyOtherTable').render();
     });
}
(Edited)