How do you update display of data from one model when related data in another model changes?

I’ve got a roll up summary field that displays on a page, along with the a table displaying children records that are rolled up to make that summary. I love Skuid’s inline edit, that allows me to edit the values of the children records. However when I save the records on that table - the roll up summary field doesn’t update the new values unless I reload the page. Boo.

I’m glad I asked. Because here is the answer… YAY… With some Javascript trickery we can listen for changes to the first model and when they are detected, refresh the second model without a page refresh. Here’s the example: My iTunes on Salesforce albumes page has duration recorded on every song, and then a roll up summary field provides the total album time. To make this work, go to the Resources tab of the page builder and add an Inline Javascript Resource. The code is included below. This resource does not need to be connected to any fields in your page. It just sits there listening.

See the comment below for the code… 

It works for me and it is awesome. Thanks tons.

For added magic, combine this with Zach’s “save and close for popups” (http://community.skuid.com/t/can_the_save_button_also_close_the_window_after_it_saves). Zach’s solution allows for seamless edits in popups, but when combined with Rob’s script, we also have seamless adds in popups also.

Rob, We’ve been using the listener script to affect a lot of different models. In a complete solution, it would be ideal to use something like this to automate model updates in key areas of user interaction. Do you know of a way we could make a module out of this script to avoid duplicating so much code? Ideally, we would just want to call the listener script and define the two models each time we need the script.

Here is the code: skuid.$(function(){ var ChildModel = skuid.model.getModel(‘Tracks’), ParentModel = skuid.model.getModel(‘Album’); var listener = new skuid.ui.Editor(); listener.handleSave = function() { if (!ParentModel.hasChanged) { ParentModel.updateData(); } }; listener.registerModel(ChildModel); }); You will need to change out the names of the first two varialbes to match your models. Replace “Tracks” with your “children” model name Replace “Album” with your “parent” model name. Now if you update the minutes or seconds of a track and push save. The Album Duration automatically is updated. (Now there is some other salesforce formula and workflow trickery associated with getting the time to be correct as it roles up, but that’s another story) Let me know if this works for you.

Can someone help modify this to handle saves to a page with multiple models?

I have three models, on one page.  I would like them to only try and save it the model has data in it.

Jacob

Is this hasChanged native to jQuery? I don’t see it in docs.

Also, if I look at that value in the JS console, it looks like the value is already set to true. 

Greg,
you could put something like this in a static resource in Salesforce
and then add that static resource to your SKUID pages. I have a static resource that is a javascript file that has all my generic functions.

function updateModel( parent_model, child_model){       
    var ChildModel = skuid.model.getModel(parent_model), 
     ParentModel = skuid.model.getModel(child_model); 
    
    var listener = new skuid.ui.Editor(); 
    listener.handleSave = function() { 
        if (!ParentModel.hasChanged) { 
        ParentModel.updateData(); 
        } 
    }; 
    listener.registerModel(ChildModel); 


};

Jacob - handleSave gets called after the save has already occurred.  I believe skuid handles saves in an optimized manner such that it will only insert/update data when the data is new or has changed including down to the field level.  In other words, if 1 field on a row that has 20 fields is the only one that has changed, only that 1 field will be updated.  In your case, if one or more of your models don’t have any data, skuid will not save anything on those models.  

This does bring up a question though…

In a situation where multiple models are “selected” on the “Save” button, how to avoid calling updateData on a model that was just refreshed?  For example:

Two Models: PO & POItem
User edits field in PO & POItem

When handleSave is called, the hasChanged for both models is false.  Since Skuid only refreshes fields that changed, updateData needs to be called but then essentially fields will be refreshed multiple times.

For example, PO has a roll-up field called “Subtotal” from PO Item.   Even if changes in both models are made, the “Subtotal” field will not be updated on the screen until a full page refresh or updateData() call is made.  However, there are fields on both models that where just updated that don’t require a refresh and in some cases, the number of fields that fall in to this category could be substantial.

Questions:

1) Is there a way to indicate to skuid that when a model is saved to also request a specific list of fields to be refreshed?  This could be based on an option (e.g. options.refreshFields) or maybe skuid could detect formula fields or read-only fields and include these by default in the refresh?  Or Both, with latter being default behavior and the former being an override feature :slight_smile:

2) Since hasChanged is false on all models in handleSave in this case, is there any way to determine whether or not a model was one of the models that was just saved?  This could help avoid unnecessary calls to updateData on that model in some cases.

Thanks!

Hi Barry,

I’ll try to answer your questions, but first let me clarify what Skuid is doing in each step of the process.  I’ll use your PO and POItem objects as an example.

1. Lets say when the page loads, there is one PO in context connected to the PO model and four PO Items associated with this PO in the POItem model.

2. Next, the user of the page changes the price field on one of the PO Items but does not save yet.  In this case, the POItem model’s hasChanged property will change from false to true.  In retrospect, hasChanged may not be the most clear name for what this property means.  In reality, it means that there is a change made to this model that has not yet been persisted in Salesforce.  Possibly needsSync or something like that would have been a better name.

3. Next, the user clicks Save on a page title Save/Cancel button that has been connected to both the PO and POItem models.  In this case, the PO model will not be saved, it’s hasChanged property has never been sent, so there is nothing to send to the server.  The one change we made to the POItem model will be sent to Salesforce.  The server side processing of Skuid will then return a “result” object letting us know how the save went on that one object.  If the result was successful, the result will also contain new updated data for every field on that row.  Skuid will use this result data to update the UI and then change the hasChanged property on the model back to false.  

The other 3 POItems that were not changed by the user of the page, will not receive updated information as a part of this save result.  Any rows on the PO model will also not receive updates as a part of this save result.  In many cases, this is not a problem, because only the row that got updated will be affected by the save.  However, in other cases (Like your roll-up summary example) a save on one object will have side effects on other objects.  Another example of a side effect of a save would be if you had a boolean field on PO Item called WasLastItemUpdated__c that got populated via a trigger on PO Item.  The trigger would set this field on the last updated item to true and change the value of this field for all other PO items for the given PO to false.  In this case, our other 3 PO Items could be out of sync as well.

4. This is where the usefulness of the script that Rob shared comes in.  If you attach a save handler to a model as Rob shows, you can immediately go back to the server and update any information that was made out of sync by the first save.

The downside of this as Barry pointed out is that this is another round trip back to the server to do this refresh.  Why can’t we just handle it with one server call instead of two?

Ok, I’ll try to answer your questions now.

1. No, there is currently not a way to let Skuid know about extra fields that should be refreshed as a part of a save operation.  Only fields on the rows that were updated or created will have their field values refreshed.  (This means that formula fields on rows that were updated or created will work correctly as expected).  I think auto detecting fields would be quite difficult as there is no way to get a good list of fields that could be affected as part of this save operation.  It really could be any field on any model in the page depending on what triggers are in place.  Providing an options.refreshFields could be a solution, but we don’t have anything like that right now.

2. There is a way to get at this information. In your handleSave function, you can receive 2 parameters.  The first parameter is a boolean value that lets you know if everything you tried was a “total success”.  The second parameter is the full result object.  You can inspect this result object to get the information you need.  But keep in mind that just because a model was just saved, it doesn’t mean that all rows in that model are totally in sync.  A trigger or workflow rule could have made a change to one of the other rows that was not part of the update.

Let me know if this makes sense or if I need to clarify anything.



Hi Ben -

First off, thank you so much for writing up such a thorough explanation.  This actually answers several of the questions I had beyond those just listed above.  Greatly appreciate the time you took to capture all of this.

As you infer, to achieve what I’m after (not refreshing data that was just refreshed) what I really need is a way to be able to refresh only certain model rows instead of the entire model (e.g. model.updateRows(arrayOfRowIdsToRefresh) or model.updateData(arrayOfRowIDstoRefresh). Not sure there is a ton of value in having that api method except for in some edge cases like mine.

As a kind of in-between solution to at least achieve making multiple round trips, possibly a new option property/argument on “save” (also exposed via buttons in page title) that is an enum with the members RefreshChangedRowOnly (default), RefreshAllRowsInModelThatHasChanges, RefreshAllModelsIfAtLeastOneModelHasAChange?

One other note is that it would be great if the docs at http://help.skuidify.com/m/11720/l/205332-skuid-ui-editor could be updated to reflect the arguments for handleSave, etc.

Thanks again!

I need the vice-versa i.e., if a picklist field value in a model is changed then the related  data table in the other model should be updated depending upon the value selected picklist field

Pavan; 
The code here is now essentially obsolete because of the implementation of the action framework.  If you have summer release installed you can use it.  Here is a brief sketch. 

1. Create a condition on the model behind the related data table for the value of the selected picklist field.  Its value should be blank and its state should be filterable default off. 

2. In the model generating the picklist add an action (new section below conditions). 
- Initiating event should be:  “Row in model updated”   -  this will kick off the action any time the picklist field is updated. 

3 Set up the following actions in the actions tab:
- Activate and Set value of Model Condition.  (Find the model and condition from step 1. In the Value box use the field picker to select the picklist field.  
- Query Model.  (Choose the model from step 1)

Use conditional rendering if you don’t want the table to show until the picklst is selected.   Or use “filterable default on” in the first step to pass a default value. 

Hope this helps. 

Would this work if I have two separate Skuid pages? Can you listen for changes on models on a different page?

So the basic answer is no.  The script above would not work across pages because the listeners are client side items that have no way of knowing that changes are being made in other pages. 

But other strategies might be employed,  like polling the server to determine if changes have been made, using the streaming API,  etc.    All these would be custom development not direclty related to Skuid - but would achieve the target of allowing one page to be made aware of changes made on another page.  

Hi Rob,

I have similar situation but in my case not parent child relationship.
I have model of account which is used to display Accountlist and also created another model of Account as MYFavoriteAccount  where user will be adding and removing the account from list view to MyfavoriteAccount.

So same model both the side.
Now the problem is if I delete any record from listview model it should also delete from MyFavoriteAccount model.

Any help.

Thanks.

This is pretty simple actually.  If you make your “Save” button on the list view table a “multiple actions” button that saves the ListView and then Query the MyFavoriteAccount model.  This should remove the entry from the second table.