Using the 'Field Editor' Component kills any javascript referencing the same model

  • 1
  • Question
  • Updated 3 years ago
Hopefully the title makes sense to you.

I'm using the field editor component to update a model called 'household'.  It works great.  The issue is I'm also accessing the 'household' model with inline javascript:
var household = skuid.model.getModel('Household');
I'm showing specific data from that model.  For example:
$('#preferenceTotal').text(household.fields.length);

The issue is that when I change any field using the field editor component (before or after I save it) the value rendered in the div#preferenceTotal disappears.  I have to reload the page and then I'll see the updated value.

Can anyone tell me how I can update that value in real time?

I've even tried:

skuid.events.subscribe('models.saved',function(saveResult){ $('#preferenceTotal').text(household.fields.length);
});
But it doesn't make any difference.  The value disappears.

Thanks for the help!
Photo of Jay Cogen

Jay Cogen

  • 162 Points 100 badge 2x thumb

Posted 3 years ago

  • 1
Photo of Rob Hatch

Rob Hatch, Official Rep

  • 44,006 Points 20k badge 2x thumb
If you are only showing the lenght of the household model,  why are you not using a rich text field, or a template field with the global merge syntax {{$Model.Household.data.length}}?  No Javascript required.  If you use the Rich Text Component you don't really even need to write any CSS. 

If you need to use the javscript I think you will need to find the element ('#preferenceTotal') and append a merge statement to it.   Done as a custom component, it would look like this: 
skuid.componentType.register('ComponentName',function(element){
var $ = skuid.$;
var m = skuid.model.getModel('Household');
var length = m.fields.length;
var text =
"<div class= 'foo'> You have "
+ length
+"Households </div>";
element.append(
m.mergeRow(row,text)
);
});

I believe that will be an improvement. 
Photo of Jay Cogen

Jay Cogen

  • 162 Points 100 badge 2x thumb
Well I tried to give as a concise description of my issue but I probably left out some crucial information in order to make it as easy to understand as possible.  I apologise for that.  But it's a deeper issue than your solution fixes I think. Below is a screenshot of a preview of the skuid page.  

The top 4 fields are tied to the Household Model.  The only one that I'd like to draw your attention to is the client preferences field.  As you can see it says '7/13'.  It updates to how many of the fields at the bottom are filled out. 

The issue is, as you update any field, all of the fields at the top disappear.   So to give you a bit more context this is what is getting the client preferences numbers:
var render = function(){

  //calculate client preferences
  var clientPreferences = household.data[0];
  var preferenceNumber = 0;
  for (var pref in clientPreferences) {
     if (clientPreferences.hasOwnProperty(pref)) {          
        if(clientPreferences[pref] != ''){
            preferenceNumber++ 
        }        
     }
  }

  $('#preferenceNumber').text(preferenceNumber);
  $('#preferenceTotal').text(household.fields.length);}

$.when(skuid.model.load([household])).then(function(){
render(); });
skuid.events.subscribe('models.saved',function(saveResult){
   render();
});


So the question is, why would these fields totally disappear when the field editor component is updated?  I'm just really confused.  Thanks!
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Hi Jay -

If I'm understanding what you are trying to do, you want to "count" the number of fields that have a value and update the field "Client Preferences" with that value.

The reason you are likely seeing a problem with rendering is that it appears that from within the renderer itself, you are loading the model which is going to cause another rendering cycle to occur thus creating an infinte loop.  If you were to open up the Developer Console I'm guessing you will find javascript errors which will start to lead you to the problem.  When a javascript error occurs, in most cases, page functionality will not behave as expected because code that still remains to be executed will not be executed.  

You could rework your code to achieve the desired result all through javascript, however I think there is a simpler solution for you.

Try the following:

1) Add a UI Only field to the household model called "ClientPreferencesCompleted".  This will contain the count of field that are filled out
2) Add a UI only field to the household model called "ClientPreferencesTotal".  This will contain a count of the total number of possible fields
3) Create a snippet called "updatePreferences" that iterates the fields and determines the "Completed" and "Total" counts and then calls updateRow on the household model updating the two UI fields
4) In the household model, register a model action to listen for "model loaded" and "model updated" and call a snippet called "updatePreferences".  This will trigger your snippet to run and update the UI fields in the model anytime the model is loaded (e.g. when the page first loads) and anytime a field in the model is updated.  You could even filter the model updated event to just the fields that you care about being counted.
5) Add a template to the field editor and with something along the lines of the following syntax {{ClientPreferencesCompleted}} / {{ClientPreferencesTotal}}

This should achieve what you want to do with minimal code.  Also, in the snippet where you are counting the fields, you are iterating each field in the model where you likely only want to actually be checking a certain subset of the fields to count.  There are a few ways to go about doing this but just be careful you are counting only the fields that you want since the code as you have it will go through every field in the model.

If you'd like to take the pure javascript path, you could.  To do that, the first step would be to remove the "load" in the render function and check the developer console for error if any still exist.

Hope this helps!
(Edited)
Photo of Jay Cogen

Jay Cogen

  • 162 Points 100 badge 2x thumb
Hi Barry,
Thank you so much for reaching out.  One large issue I'm having has to do with your 'step 4'.

'In the household model, register a model action to listen for "model loaded"...'.

I'm not seeing a way to trigger an action based on a model loading.  So my snippet isn't getting fired.  The only options I'm seeing are: model saved, model required, model cancelled, new row created in model, row in model updated, row in model marked for deletion and row in model un-marked for deletion.  I even tried to check all but the snippet doesn't fire on load.

Is there something I'm missing?

Thanks a lot!
(Edited)
Photo of Barry Schnell

Barry Schnell, Champion

  • 18,076 Points 10k badge 2x thumb
Hi Jay -

My apologies, no there is nothing that you are missing.  I had forgotten that there is not a model loaded event, sorry about that.

Unfortunately, Skuid doesn't expose a way to hook the initial loading of data during page load/initialization.  Don't fret though, there is a solution.

The solution is to hook the jQuery document.ready or pageload event.  This will be fired on initial page load once the DOM is ready.  Skuid will already have loaded up the model by this point so its safe to update the UI fields.

I've created a very basic sample of all the steps in my previous post plus the approach for hooking pageload.  I haven't tested this code in all scenarios and there is definitely room for improvement in the code itself.  In short, it's not production ready :)  That said, it should give you a good baseline from which to build on.

Hope this helps.  Let me know if you have any questions and good luck!

<skuidpage unsavedchangeswarning="yes" personalizationmode="server" showsidebar="true" showheader="true" tabtooverride="Account">   <models>
      <model id="Account" limit="1" query="true" createrowifnonefound="false" sobject="Account" adapter="" type="" doclone="">
         <fields>
            <field id="Name"/>
            <field id="CreatedDate"/>
            <field id="CompletedFields" uionly="true" displaytype="DOUBLE" label="Completed Fields" precision="9" scale="0"/>
            <field id="TotalFields" uionly="true" displaytype="DOUBLE" label="TotalFields" precision="9" scale="0"/>
            <field id="Type"/>
            <field id="AccountSource"/>
            <field id="Industry"/>
         </fields>
         <conditions>
            <condition type="param" enclosevalueinquotes="true" operator="=" field="Id" value="id"/>
         </conditions>
         <actions>
            <action>
               <actions>
                  <action type="custom" snippet="updateCompletedFields"/>
               </actions>
               <events>
                  <event>models.loaded</event>
                  <event>row.created</event>
                  <event>row.updated</event>
               </events>
               <fields>
                  <field>AccountSource</field>
                  <field>Industry</field>
                  <field>Name</field>
                  <field>Type</field>
               </fields>
            </action>
         </actions>
      </model>
   </models>
   <components>
      <pagetitle model="Account" uniqueid="sk-KTA6A-68">
         <maintitle>
            <template>{{Name}}</template>
         </maintitle>
         <subtitle>
            <template>{{Model.label}}</template>
         </subtitle>
         <actions>
            <action type="savecancel" window="self"/>
         </actions>
      </pagetitle>
      <basicfieldeditor showsavecancel="false" showheader="true" model="Account" mode="read" uniqueid="sk-KTA6A-69">
         <columns>
            <column width="50%">
               <sections>
                  <section title="Basics" collapsible="no">
                     <fields>
                        <field id="Name" valuehalign="" type=""/>
                        <field id="AccountSource" valuehalign="" type=""/>
                        <field id="Type" valuehalign="" type=""/>
                        <field id="Industry" valuehalign="" type=""/>
                        <field type="COMBO" valuehalign="" editmodebehavior="autopopup" readonly="true">
                           <label>Completed / Total</label>
                           <template>{{CompletedFields}} / {{TotalFields}}</template>
                        </field>
                     </fields>
                  </section>
               </sections>
            </column>
            <column width="50%">
               <sections>
                  <section title="System Info">
                     <fields>
                        <field id="CreatedDate"/>
                     </fields>
                  </section>
               </sections>
            </column>
         </columns>
      </basicfieldeditor>
   </components>
   <resources>
      <labels/>
      <css/>
      <javascript>
         <jsitem location="inline" name="CompletedFields" cachelocation="false" url="">(function(skuid){
    // shortcut to jQuery and
    // an array of all the fields we are tracking by their API name
var $ = skuid.$
   , fieldsToTrack = ['Name','AccountSource','Type', 'Industry'];
// helper function so we can initiate the process from multiple places
    var updateFieldInfo = function(model, row, initiatorId) {
        // if a row was supplied, put it in to an array
        // if no row was provided, use all the rows in the model (model.data is an array)
        var rows = row ? [row] : model.data;
        
        // iterate all the rows
        $.each(rows, function(rowIndex, rowData) {
            // initialize our counter for this row
            var completedFields = 0;
            
            // iterate all the fields we are tracking
            $.each(fieldsToTrack, function(fieldIndex, fieldName) {
                // if the field has a value, increment the counter
                if (rowData[fieldName]) {
                    completedFields++;
                }
            });
            
            // update the UI field values in the row
            model.updateRow(rowData, { TotalFields: fieldsToTrack.length, CompletedFields: completedFields }, { initiatorId: initiatorId });
        });
    }
    
    // register a skuid snippet called updateCompletedFields
skuid.snippet.registerSnippet('updateCompletedFields', function(eventArg) {
   // call our helper passing the model, row and initiatorId
   updateFieldInfo(eventArg.model, eventArg.row, eventArg.initiatorId);
});
    // Capture the page load event so we can iterate the account model and update 
    // the UI fields for each row in the model
$(document.body).one('pageload', function() {
   // call our helper passing the model and null for row &amp; initiator since we want to update all rows in the model on page load
   updateFieldInfo(skuid.$M('Account'), null, null);
});
})(skuid);</jsitem>
      </javascript>
   </resources>
   <styles>
      <styleitem type="background" bgtype="none"/>
   </styles>
</skuidpage>