Completely copy row to a different parent record

  • 1
  • Question
  • Updated 3 years ago
  • Answered
I'm trying to copy the row of a child model to a different parent.

Anyone see anything wrong with this code?

var $ = skuid.$,
    mergeCase = skuid.$M('MergeCase'),
    mergeData = mergeCase.getFirstRow(),
    primaryPatient = skuid.$M('PrimaryPatient');
var copyValues = [];
$.each(mergeData, function(f,v){
    if (f !== 'attributes' && f !== 'Id' && f !== 'Patient__c') {
        copyValues.push({field: f, value: skuid.utils.decodeHTML(v)});
    }
});
primaryPatient.createRow({additionalConditions: copyValues});

I'm getting this error message (line 79 of my page corresponds to the createRow() line of the above.


Is there a better way to do this?
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb

Posted 3 years ago

  • 1
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Found the problem I think. On this jquery page. http://api.jquery.com/jquery.each/

Note: The $.each() function internally retrieves and uses the length property of the passed collection. So, if the collection has a property called length — e.g. {bar: 'foo', length: 10} — the function might not work as expected.
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
You're good, Pat!
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Your code looked good, so I started to look at the each function. Then did search on "length" on that page.

Wee!!!
Photo of Irvin Waldman

Irvin Waldman, Champion

  • 9,006 Points 5k badge 2x thumb
if (copyValues.length > 0 ) {
   primaryPatient.createRow({additionalConditions: copyValues});
} else {
   console.log('hmmmm, nothing to copy!?');
}


Drop into the developer console and set a breakpoint on createRow.  Take a look at copyValues.
Photo of Matt Sones

Matt Sones, Champion

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

Irvin: copyValues definitely has rows (about 104 of them).

Pat: I'm not seeing a 'length' property on the standard skuid row object, and I don't have a field called "length"... so I'm not sure that's the problem either.

Unless I'm missing something?


Apparently 'a' is returning null here (in skuid__JQuery) :

each: function(a, b, c) {
            var d, e = 0, f = a.length, g = t(a);
            if (c) {
                if (g)
                    for (; f > e && (d = b.apply(a[e], c),
                    d !== !1); e++)
                        ;
                else
                    for (e in a)
                        if (d = b.apply(a[e], c),
                        d === !1)
                            break
            } else if (g)
                for (; f > e && (d = b.call(a[e], e, a[e]),
                d !== !1); e++)
                    ;
            else
                for (e in a)
                    if (d = b.call(a[e], e, a[e]),
                    d === !1)
                        break;
            return a
        },
(Edited)
Photo of Irvin Waldman

Irvin Waldman, Champion

  • 9,006 Points 5k badge 2x thumb
Try this:

$.each(mergeData, function(f,v){
if (f !== 'attributes' && v !== null && f !== 'Id' && f !== 'Patient__c') {
var modelField = primaryPatient.getField(f);
if ((typeof val === 'object')
|| (modelField && modelField.createable)) {
copyValues.push({field: f, value: skuid.utils.decodeHTML(v)});
}
}
});
Photo of Barry Schnell

Barry Schnell, Champion

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

If I'm understanding what you're after correctly - copying a row from one model to another but ensuring certain values aren't copied over - then you might want to consider a different approach.

In short, the current approach you are taking relies on conditions to set the default values, however some of the fields that you are passing in are "__r" fields, etc. and those can't have conditions.  It's possible that the javascript error you are encountering is the result of one of these fields.

Unfortunately, there isn't an API to "copyRow" currently but the approach I would suggest is something along the lines of the following.  Note that this is the "brute" force method and just copies every value (expect for those that you know you don't want to copy).  To really do this correctly, you should walk the target model and look in the source model for a field that matches by name and then grab that value.  This ensures that you don't put a field in to the target model row that exists in the source model but not the target.  You can create a generic "copyRow" function that accepts a source model, source row, target model and target row and performs the copy.

That said, assuming that you know for certain that your source and target models are 100% the same, you could do this:

var $ = skuid.$
    , sourceModel = skuid.$M('MergeCase')
, sourceRow = sourceModel.getFirstRow()
, targetModel = skuid.$M('PrimaryPatient'); // newRow is the row we're creating in the target model
// copiedRow is a copy of the row from the source model
var newRow = targetModel.createRow()
    , copiedRow = $.extend({}, sourceRow);
    
// delete the fields in the copied row we don't want
delete copiedRow['Id'];
delete copiedRow['attributes'];
delete copiedRow['Patient__c'];
// update the newly created row in the target model with the values we "copied"
targetModel.updateRow(newRow, copiedRow);
Again, this assumes the models are identical.  If they are not, I'd suggest writing the generic "copyRow" method.

Here is a sample page that performs a copy based on Account SObject:

<skuidpage unsavedchangeswarning="yes" personalizationmode="server" showsidebar="true" showheader="true" tabtooverride="Account">   <models>
      <model id="AccountSource" limit="1" query="true" createrowifnonefound="false" sobject="Account" adapter="" type="">
         <fields>
            <field id="Name"/>
            <field id="CreatedDate"/>
            <field id="ParentId"/>
            <field id="Parent.Name"/>
         </fields>
         <conditions>
            <condition type="param" enclosevalueinquotes="true" operator="=" field="Id" value="id"/>
         </conditions>
         <actions/>
      </model>
      <model id="AccountTarget" limit="20" query="false" createrowifnonefound="false" adapter="" type="" sobject="Account" doclone="">
         <fields>
            <field id="Name"/>
            <field id="CreatedDate"/>
            <field id="ParentId"/>
            <field id="Parent.Name"/>
         </fields>
         <conditions/>
         <actions/>
      </model>
   </models>
   <components>
      <pagetitle model="AccountSource" uniqueid="sk-3pE7jy-67">
         <maintitle>
            <template>{{Name}}</template>
         </maintitle>
         <subtitle>
            <template>{{Model.label}}</template>
         </subtitle>
         <actions>
            <action type="multi" label="Copy Row To Target">
               <actions>
                  <action type="custom" snippet="copyRowToTarget"/>
               </actions>
            </action>
         </actions>
      </pagetitle>
      <basicfieldeditor showsavecancel="false" showheader="true" model="AccountSource" mode="read" uniqueid="sk-3pE7jy-68" buttonposition="">
         <columns>
            <column width="50%">
               <sections>
                  <section title="Basics" collapsible="no">
                     <fields>
                        <field id="Name"/>
                        <field id="ParentId"/>
                     </fields>
                  </section>
               </sections>
            </column>
            <column width="50%">
               <sections>
                  <section title="System Info">
                     <fields>
                        <field id="CreatedDate"/>
                     </fields>
                  </section>
               </sections>
            </column>
         </columns>
      </basicfieldeditor>
      <basicfieldeditor showheader="true" showsavecancel="false" showerrorsinline="true" model="AccountTarget" buttonposition="" uniqueid="sk-3pFktd-322" mode="read">
         <columns>
            <column width="50%">
               <sections>
                  <section title="Section A" collapsible="no">
                     <fields>
                        <field id="Name"/>
                        <field id="ParentId" valuehalign="" type=""/>
                     </fields>
                  </section>
               </sections>
            </column>
            <column width="50%">
               <sections>
                  <section title="Section B" collapsible="no">
                     <fields>
                        <field id="CreatedDate"/>
                     </fields>
                  </section>
               </sections>
            </column>
         </columns>
      </basicfieldeditor>
   </components>
   <resources>
      <labels/>
      <css/>
      <javascript>
         <jsitem location="inlinesnippet" name="copyRowToTarget" cachelocation="false">var $ = skuid.$,
sourceModel = skuid.$M('AccountSource'),
sourceRow = sourceModel.getFirstRow(),
targetModel = skuid.$M('AccountTarget');
// newRow is the row we're creating in the target model
// copiedRow is a copy of the row from the source model
var newRow = targetModel.createRow()
    , copiedRow = $.extend({}, sourceRow);
    
// delete the fields in the copied row we don't want
delete copiedRow['Id'];
delete copiedRow['attributes'];
delete copiedRow['ParentId'];
// update the newly created row in the target model with the values we "copied"
targetModel.updateRow(newRow, copiedRow);


</jsitem>
      </javascript>
   </resources>
   <styles>
      <styleitem type="background" bgtype="none"/>
   </styles>
</skuidpage>
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Well....  er ... uh ... why not this? Action framework.
  1. Adopt Row
  2. Update field
  3. Save
Or. Set model to clone on load page load, update field and save.
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Nice work, Barry!

On reading your post, I immediately thought the same thing as Pat... Isn't this just "Adopt Row" with a field update to change the reference to the parent record?

Haha. There I go overcomplicating things...

I'll let you all know how it goes.
Photo of Barry Schnell

Barry Schnell, Champion

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

I considered adoptRow as well when I first looked at this but I don't think you want to use adoptRow.  The reason is that I believe (you can verify) that it makes a shallow reference copy of the row which means any change to the row in either model will effect both models.  The approach you were taking or the one I proposed will make a true copy of the row leaving each model with it's own copy of the row.  You can run a test to validate this by using adoptRow, changing a field in either row and then seeing if the row in both models changed - I believe it will.

If you do use the approach I proposed, you'll also want to add this line of code to make sure the reference value gets removed.:

delete copiedRow['Patient__r'] 

Let us know how it goes!
(Edited)
Photo of Matt Sones

Matt Sones, Champion

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

The action framework wins again!
As Pat said, this sequence works:
  1. Adopt row
  2. Update field on adopted row to change the parent id to the new parent
  3. Save adoptive model

The source and target models are "detail" models (both containing all possible fields on the same object). I also have "list" models which have a few fields on the same objects shown in a table. To avoid any of the potential 'shallow reference copy' issues bought up by Barry, I'm also adding:

     4.  Remove all rows from adoptive (detail) model
     5.  Query list model


Seems to work perfectly!

Thanks for all the help, gentlemen.
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Awesome news Matt and kudos to Pat for the suggestion.  I think the key here is that you are able to "remove rows" from the adoptive model leaving just one "copy" and one "reference" to the row you moved.  If you needed to keep the row in both models, you'd likely have to use one of the other solutions.  Also good that the models are identical :)
Photo of J.

J., Official Rep

  • 7,470 Points 5k badge 2x thumb
This. This is why you four are Skuid Champions.
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
A couple of thoughts just to close the loop on this for others that might stumble upon this thread in the future...

1) I verified that my memory was serving me correctly and that adoptRows does make a reference copy meaning that a change to the row in Model A would also update the row in Model B (assuming you hadn't removed the rows from after the adopt).  If you are unable to remove the row from the source, then using createRow/updateRow would be the desired approach.

2) Using the action framework or the API createRow/updateRow approach will result in separate actions and in the event of an error occurring at some point, would lead to a possible data integrity issue.  For example, let's say the createRow worked but the updateRow failed.  The row would be in the targetModel but not in a correct/valid state.  In the case of using the action framework and adoptRow then updateRow, the same could occur.  The only way to ensure all success or all failure is to use Matt's original idea of createRow passing in conditions.  I haven't ever tried that but assuming the fields were "cleaned" first, I think that approach would work.

3)  In any of the cases, attention should be paid to the fields in the source model and target model.  If they are not always 100% the same, then using model.fieldsMap to manage the copy would be desirable.
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Another warning with Barry's point #1

It seems that when using the action framework method I specified above, the 'remove all rows' action removes the adopted row from both the source and target models. That works for me, but if it's not a desirable outcome for you, try one of the other methods Barry outlined.