Saving dynamic model doesn't update server.

  • 1
  • Problem
  • Updated 3 years ago
  • Not a Problem
There's a long story here. I'll try to make it as short as possible...

I'm creating a dynamic model, but when I save it, the server doesn't update. When I re-query other models on the same object, the fields saved by the dynamic model are not updated.

Here's the entire code. I've bolded the definition of the dynamic model in question, and the update and save portions of the code, which seem to be the most relevant.
(function(skuid){	
var $ = skuid.$;
$(document.body).one('pageload',function(){
//Get all models on Patient_Case__c and Interaction__c
var interactionModels = [];
$.each(skuid.model.map(),function(){
if (this.objectName == 'Interaction__c') {
interactionModels.push(this);
}
});
// If there are Interaction models, create a patient case model, an interaction model, and subscribe to the save event
if (interactionModels.length) {
//Create model on Patient_Case__c for collecting Pregnancy Intention and Date Most Recent Completed Interaction
var patientCase = new skuid.model.Model();
patientCase.objectName = 'Patient_Case__c';
patientCase.id = 'PatientCase_UpdateIntention';
patientCase.recordsLimit = 1;
patientCase.fields = [
{ id: 'Id'},
   { id: 'Pregnancy_Intention__c' },
   { id: 'Date_Most_Recent_Completed_Interaction__c' }
];
patientCase.conditions = [
   { 
       type: 'fieldvalue',
       field: 'Id',
       operator: '=', 
       value: '', 
       state: 'filterableoff', 
       inactive: true,
       name: 'CaseId',
       enclosevalueinquotes: true
   }
];
patientCase.initialize().register();
//Create model on Interaction for updating Pregnancy Intention
var interactionUpdate = new skuid.model.Model();
interactionUpdate.objectName = 'Interaction__c';
interactionUpdate.id = 'Interaction_UpdateIntention';
interactionUpdate.recordsLimit = 50;
interactionUpdate.fields = [
{ id: 'Id'},
   { id: 'x_Pregnancy_Intention_ITC__c' },
   { id: 'Date__c' },
   { id: 'Status__c'},
   { id: 'Patient_Case__c'}
];
interactionUpdate.conditions = [
   { 
       type: 'modelmerge',
       field: 'Patient_Case__c',
       operator: '=', 
       model: 'PatientCase_UpdateIntention',
       mergeField: 'Id',
       inactive: false,
       enclosevalueinquotes: true,
       noValueBehavior: 'noquery' 
   },
   {
    type: 'fieldvalue',
    field: 'Status__c',
    operator: '=',
    value: 'Scheduled',
    state: 'on',
    inactive: false,
    enclosevalueinquotes: true
   },
   {
    type: 'modelmerge',
    field: 'Date__c',
    operator: 'gte',
    model: 'PatientCase_UpdateIntention',
    mergeField: 'Date_Most_Recent_Completed_Interaction__c',
    inactive: false,
    enclosevalueinquotes: false
   }
];
interactionUpdate.initialize().register();

//Subscribe to the model save event.
skuid.events.subscribe('models.saved', function(saveResult){
// Only run if the initiator was a ui element (to eliminate loop)
if (saveResult.initiatorId) {
// Find all saved Interaction__c models
var savedInteractionModels = [];
$.each(saveResult.models, function(){
if ($.inArray(this, interactionModels) > -1) {
savedInteractionModels.push(this);
}
});
// If any interaction models were saved...
if (savedInteractionModels.length) {
var dfd = new $.Deferred();
// Iterate through models until one is found that has a Patient_Case__c field
for (var i = 0, updateComplete = false; i < savedInteractionModels.length && !updateComplete; i++) {
var rowsToUpdate = {},
caseRow = savedInteractionModels[i].getFirstRow(),
caseid = (caseRow) ? caseRow.Patient_Case__c : null;
if (caseid) {
// Find Intention and Date from Patient Case
updateComplete = true;
patientCase.setCondition(patientCase.getConditionByName('CaseId'), caseid);
$.when(skuid.model.updateData([patientCase, interactionUpdate])).then(function(){
var lastPregnancyIntention = patientCase.getFieldValue(patientCase.getFirstRow(), 'Pregnancy_Intention__c');
// Update pregnancy intention for all scheduled interactions after the most recent completed
$.each(interactionUpdate.getRows(), function(i, row){
if (row.x_Pregnancy_Intention_ITC__c !== lastPregnancyIntention) {
rowsToUpdate[row.Id] = {x_Pregnancy_Intention_ITC__c : lastPregnancyIntention};
}
});
interactionUpdate.updateRows(rowsToUpdate);
// Save updates to server
$.when(interactionUpdate.save())
.done(function(){
dfd.resolve();
})
.fail(function(){
dfd.reject();
console.log('Interaction_UpdateIntention model save failed.');
});
});
}
}
return dfd.promise();
}
}
});
}
});
})(skuid);
The code runs smoothly. interactionUpdate.save() works, and gets to .done() to resolve the deferred. When I inspect the model in the console, the values are correct, and hasChanged = false. But the server hasn't changed.

It seems like I must be doing something wrong in the model definition?
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb

Posted 3 years ago

  • 1
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
^bump^
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Hey Friends...

I really need an answer for this before Friday, if at all possible. Really appreciate any help. Thanks!
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
^^
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Hey Matt - I took a quick pass at the code.  Can you try the following to help isolate:

1) Open up browser and set a breakpoint on $.when(interactionUpdate.save()) and inside the done and inside the fail
2) Make the situation occur on your page
3) When the breakpoint is hit for the when, go to the network tab and clear the list
4) "run" so that the code continues and it should hit the done per your comments above
5) check the network list and see if a network call was made to your SFDC server

If it was, verify the HTTP response was 200 (it should be but just to be sure).  Assuming yes, then this is likely something either in your model definition or a problem on the server side with skuid.  I'm thinking its more likely the former than the latter but this will help by checking to see if the call is even made to the server.

Let me know how it goes...
(Edited)
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Hey Matt -

I believe I've found the source of your issue and along the way found another issue that you might be running in to.  

First, let's start with the root cause of why the server isn't updating.  In your code, you are not calling skuid.model.load.  The result of this is that skuid doesn't have the metadata necessary to appropriately handle the updateRows call.  If you run the test I suggested above, I'm fairly certain that you will see that no network call is made after calling "save".  By adding the following line of code after you register the interactionUpdate model, you should see your data update on the server:

skuid.model.load([patientCase, interactionUpdate])

In troubleshooting your issue, I ran in to a problem with model conditions, see enclosevalueinquote condition parameter remoting exception (Banzai 7.12) for more details.

Per the above, if you are running Skuid 7.x, you will want to change your condition attribute to encloseValueInQuotes

Since I didn't have your custom objects, in order to troubleshoot this, I created a similar page using Accounts and Contacts.  I've included that page below so you can take a look.  Please forgive the refactoring of your original code, as I wrote the test page, it was easier for me to break up things as I figured out what you were attempting to do. Hopefully what I came up with is close :)

<skuidpage unsavedchangeswarning="yes" personalizationmode="server" showsidebar="true" showheader="true" tabtooverride="Account">
   <models>
      <model id="Contact" limit="20" query="true" createrowifnonefound="false" adapter="" type="" sobject="Contact" doclone="">
         <fields>
            <field id="FirstName"/>
            <field id="Name"/>
            <field id="LastName"/>
            <field id="Description"/>
            <field id="AccountId"/>
            <field id="Account.Name"/>
         </fields>
         <conditions>
            <condition type="param" value="id" field="Id" operator="=" enclosevalueinquotes="true" novaluebehavior=""/>
         </conditions>
         <actions/>
      </model>
   </models>
   <components>
      <pagetitle uniqueid="sk-3u-sd-81" model="Contact">
         <maintitle>
            <template>{{Name}}</template>
         </maintitle>
         <subtitle>
            <template>{{Model.label}}</template>
         </subtitle>
         <actions>
            <action type="multi" label="Save" icon="sk-icon-save">
               <actions>
                  <action type="save" rollbackonanyerror="true">
                     <models>
                        <model>Contact</model>
                     </models>
                  </action>
               </actions>
            </action>
            <action type="multi" label="Cancel" icon="sk-icon-cancel">
               <actions>
                  <action type="cancel">
                     <models>
                        <model>Contact</model>
                     </models>
                  </action>
               </actions>
            </action>
         </actions>
      </pagetitle>
      <basicfieldeditor showheader="true" showsavecancel="false" showerrorsinline="true" model="Contact" buttonposition="" uniqueid="sk-3COBx-1173" mode="read">
         <columns>
            <column width="100%">
               <sections>
                  <section title="Section A" collapsible="no">
                     <fields>
                        <field id="Name" valuehalign="" type=""/>
                        <field id="FirstName"/>
                        <field id="LastName"/>
                        <field id="AccountId"/>
                        <field id="Description"/>
                     </fields>
                  </section>
               </sections>
            </column>
         </columns>
         <renderconditions logictype="and"/>
      </basicfieldeditor>
   </components>
   <resources>
      <labels/>
      <css/>
      <javascript>
         <jsitem location="inline" name="newInlineJS" cachelocation="false" url="">
(function(skuid){	
    var $ = skuid.$
        , counter = 0;

    // creates an object map of the property specified by objectKeyPropName
    // from each object in the array specified by items
	var createMapForArray = function(items, objectKeyPropName) {
		return items.reduce(function(obj, k) { 
			var key = k[objectKeyPropName];
			obj[key] = k; 
			return obj; 
		}, {});
	};
	
	// creates an object map of model id to model
	// for each model that is for SObject Contact
    var getcontactModelMap = function (models) {
        var contactModels = models.filter( function(model) {
            return model.objectName === 'Contact';
        });
        
        return createMapForArray(contactModels, 'id');
    };
    
    var getRowsToUpdate = function(contactModel, fieldUpdates) {
        var rowsToUpdate = {};
        // Update description for all contacts to the website of the account plus the /s#
        $.each(contactModel.getRows(), function(i, row) {
            rowsToUpdate[row.Id] = fieldUpdates;
        });
        
        return rowsToUpdate;
    };
    
    // create a dynamic account model
    var createAccountModel = function() {
        var accountModel = new skuid.model.Model();
        accountModel.objectName = 'Account';
        accountModel.id = 'DynamicAccount';
        accountModel.recordsLimit = 1;
        accountModel.fields = [
          { id: 'Id' }
          , { id: 'Website' }
          , { id: 'CreatedDate' }
        ];
        accountModel.conditions = [
            { 
                type: 'fieldvalue'
                , field: 'Id'
                , operator: '='
                , value: '' 
                , state: 'filterableoff' 
                , inactive: true
                , name: 'AccountId'
                , encloseValueInQuotes: true
            }            
        ];
        accountModel.initialize().register();
        
        return accountModel;
    };
    
    var createContactModel = function() {
        //Create model on Interaction for updating Pregnancy Intention
        var contactModel = new skuid.model.Model();
        contactModel.objectName = 'Contact';
        contactModel.id = 'DynamicContact';
        contactModel.recordsLimit = 50;
        contactModel.fields = [
            { id: 'Id'}
            , { id: 'Description' }
            , { id: 'CreatedDate' }
            , { id: 'LeadSource'}
            , { id: 'AccountId'}
            , { id: 'Name' }
        ];
        
        contactModel.conditions = [
            { 
                type: 'modelmerge'
                , field: 'AccountId'
                , operator: '=' 
                , model: 'DynamicAccount'
                , mergeField: 'Id'
                , inactive: false
                , encloseValueInQuotes: true
                , noValueBehavior: 'noquery' 
            }
            // NOTE - The following conditions are commented out to make it easier to find contacts
            // but Matt's original code did have a condition for a picklist field
            // , {
            // 	type: 'fieldvalue'
            // 	, field: 'LeadSource'
            // 	, operator: '='
            // 	, value: 'Web'
            // 	, state: 'on'
            // 	, inactive: false
            // 	, encloseValueInQuotes: true
            // }
            , {
            	type: 'modelmerge'
            	, field: 'CreatedDate'
            	, operator: 'gte'
            	, model: 'DynamicAccount'
            	, mergeField: 'CreatedDate'
            	, inactive: false
            	, encloseValueInQuotes: false
            }
        ];
        contactModel.initialize().register();
        
        return contactModel;
    };    
    
    // find the account id for the first contact
    // that has an account id
    var findAccountId = function(models) {
        for (var i = 0; i &lt; models.length; i++) {
            var contactRow = models[i].getFirstRow();
            if (contactRow &amp;&amp; contactRow.AccountId) {
                return contactRow.AccountId;
            }
        }
        
        return null;
    };
    
    // get the list of models that are being saved
    // that were identified in our list of Contact models
    var getContactModels = function(savedModels, contactModelMap) {
        // Find all saved Contact models
        var savedContactModels = [];
        $.each(savedModels, function(idx, model) {
            if (contactModelMap.hasOwnProperty(model.id)) {
                savedContactModels.push(this);
            }
        });
        
        return savedContactModels;
    };
    
    $(document.body).one('pageload',function(){
        // get map of Contact models
        var contactModelMap = getcontactModelMap(skuid.model.list());
        // build array of Contact Models
        var contactModels = $.map(contactModelMap, function(model, modelId) { 
            return model;
        });

        // if there are contact models, create an account &amp; contact models and subscribe to Skuid's save event
        if (contactModelMap &amp;&amp; !$.isEmptyObject(contactModelMap)) {
            var accountModel = createAccountModel();
            var contactModel = createContactModel();
            // make sure we load 'em up
            skuid.model.load([accountModel, contactModel]);

            //Subscribe to the model save event.
            skuid.events.subscribe('models.saved', function(saveResult){
                // Only run if the initiator was a ui element (to eliminate loop)
                if (saveResult.initiatorId) {
                    // check the models that were saved and see if any where from our Contact model list
                    var savedContactModels = getContactModels(saveResult.models, contactModelMap);
                    
                    // If any contact models were saved...
                    if (savedContactModels.length) {
                        var dfd = new $.Deferred();
                        
                        // find the account id to use
                        var accountId = findAccountId(savedContactModels);
                        
                        // if we have an account id
                        if (accountId) {
                            // set the account model condition
                            accountModel.setCondition(accountModel.getConditionByName('AccountId'), accountId);
                        
                            // update the account model and contact model
                            $.when(skuid.model.updateData([accountModel, contactModel])).then(function() {
                                // get the first row of the account and then
                                // build the new website name
                                var acctRow = accountModel.getFirstRow()
                                    , website = (acctRow &amp;&amp; acctRow.Website) || 'http://www.test.com'
                                    , newWebsite = (website + '/s' + (counter++));

                                // get the rows to update
                                var rowsToUpdate = getRowsToUpdate(contactModel, { Description: newWebsite });
                                // update the rows in the contact model
                                contactModel.updateRows(rowsToUpdate);
                                
                                // Save updates to server
                                $.when(contactModel.save())
                                    .done(function() {
                                        console.log('Successfully saved contact updates.');                                        
                                        // reload data for all contact models
                                        skuid.model.updateData(contactModels).then(function() {
                                            console.log('updated all contact models');
                                            dfd.resolve();
                                        });

                                    })
                                    .fail(function() {
                                        dfd.reject();
                                        console.log('Interaction_UpdateIntention model save failed.');
                                    });
                            });
                        }	
                    }
                    
                    return dfd.promise();
                }
            });
        }
    });
})(skuid);</jsitem>
      </javascript>
   </resources>
   <styles>
      <styleitem type="background" bgtype="none"/>
   </styles>
</skuidpage>
(Edited)
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Agreed with Barry's analysis - if you haven't called load() on your Models, then they won't have the Metadata required in order to know that your fields are editable/createable, and so when you call updateRow(s) or other similar methods, no changes will be written to the Model, because Skuid thinks that the fields are not editable/createable, so it won't register these changes, thus when a save() call is made, no changes will be sent to the server because there are no changes in the Model! You can test this yourself by setting a JS breakpoint in your code right before calling save() and you'd see that there would not be changes for the fields in the Model.

Regarding the encloseValueInQuotes property on your dynamically-created Conditions, as I answered Barry in the other community post, you have always needed to camel-case this property's name, if you use "enclosevalueinquotes" you'll get problems, your Model's query won't run because you'll get a server-side error. Always use encloseValueInQuotes(camel-cased) when setting this property via JavaScript.
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Thanks for the extra background and info Zach!

Matt - To follow-on with Zach's comments, what he mentions is how I figured out that load was required.  In short, using your original code I put a breakpoint on updateRows and then checked hasChanged and changes properties of the model and they were false and empty respectively even though the row data itself has been updated.  This led me down the path of the missing "load" call.  Sorry for not providing that extra background/detail in my previous post.
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Barry...

You are awesome.

Thanks.
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
IT WORKS! :)
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Awesome!!