Skip to main content

I have a tabset in which I lazy load all but the first tab, which is nice for performance. But I see through the console that the model data for those deferred tabs gets returned on the initial page load. Is there a way to only run a tab’s model when the tab is displayed? My theory is that this should improve the initial page load even further.

Thx!


Do you have this checked on the Tab Set properties?


Sorry if that’s an obvious question but it’s a little confusing because as far as I know, Lazy Load can be set on:

1. the Model
2. the Tab Set
3. the individual Tab

There might be even more places to set it.


Hey Moshe … nothing wrong with asking the obvious questions, especially with me. 🙂  Yep, I’ve set “defer rendering” on the tabset and set “lazy loading” on the tabs themselves:


I’m not aware how to lazy load models. I can set “Load Model Data on Page Load” to false, but that means that when the user goes to that tab, it’ll have no data in it. 

What I’m thinking of is setting that to false, then programmatically retrieving the model data when the tab is clicked. That’s not too hard to do, just wondering whether anyone has done something without code.


I had a similar issue once. The way I solved it was by outsourcing the entire tab including the models, into a Page Include. Then you just have to put the page include into the tab. The tricky part is to pull conditions from the actual page, to your page include models. You can use something like “$Param.id” to get the parameter id. You also have to make sure that all the models have unique names.


That’s interesting and thanks for the suggestion. Did you find that that made a difference to page load performance? I have several onClick functions that click through from one tab to another and do things like adjust model conditions (and hence table filters). I suspect that might get more complex if each tab was a page include. Worth exploring, though.


So just for fun, I tried this today.


As mentioned above, my page has a tabset and I’m looking to lazy load not just the content of each tab (apart from the first one, obviously), but also lazy load the models that feed those tabs. See tabset here:



So I set all of those other tabs’ models “Load Model Data on Page Load” option to false:



Then I created an inline Javascript resource. It contains a function for the “on tab show” event for each tab and updates the relevant models:



//TAB MODEL INITIALISER

//Runs the relevant models when a tab is activated (to avoid the models needing to

//run on the initial page load).)

skuid.$(function() {

skuid.$('#relationships').on('tabshow', function(){

var theModel = skuid.model.getModel('Relationships');

theModel.updateData();

});

skuid.$('#agreements').on('tabshow', function(){

var theModel = skuid.model.getModel('Agreements');

theModel.updateData();

});

skuid.$('#insurance').on('tabshow', function(){

var insuranceModel = skuid.model.getModel('Insurance');

insuranceModel.updateData();

var benefitsTModel = skuid.model.getModel('Benefits');

benefitsTModel.updateData();

var otherIinsuranceModel = skuid.model.getModel('OtherInsurance');

otherIinsuranceModel.updateData();

});

});


It works fine and anecdotal testing suggests the initial page load is substantially quicker. When you click through to each tab, there’s a very small delay while the table’s model is refreshed, but it’s perfectly fine.


Seems a useful option thus far.


An extra question here for Team Skuid, if I may. 

The above approach works well and we want to use it extensively (in fact I really love it, but I dig this sort of stuff disproportionately). The only missing piece is that each of our tabs has a Skuid table in it and there’s a short delay between the tab and table displaying and the table then being populated with data. The delay is perfectly acceptable, but just needs a visual cue to the user that something is going on.

I think the best way would be to reuse the native Skud approach taken when filtering a table, that is, to grey out the table and display the “Applying Filter…” label.



Can we programmatically reuse that effect? That would be better than us coding up something additional.

Muchas gracias.


Glenn, your use of on(‘tabshow’) and updateData is great! Way to make use of the API’s available.

I have three optimizations that I think you’ll appreciate here:

1. the “visual cue” / “loading effect” — yes this can absolutely be reused, we highly encourage this. Skuid makes extensive use of the jQuery blockUI plugin internally and we make this available to you guys as well, and it’s a great paradigm to employ with any server-side transaction to help users know what’s going on. Basic use: $.blockUI({message: ‘Loading…’}); and then $.unblockUI(); to remove the message.

There’s a few extra parameters you can use, like timeout: 5000 (which will make the message go away after 5 seconds), but you can find all these at the plugin’s website.

2. Consolidate updateData() calls into one, using skuid.model.updateData(emodelsList]);

3. Bind only a single on(‘tabshow’) event to the document body itself, and then check the event object for the current tab panel that fired the event. This lets you keep your code cleaner and abstract the Ids of the Tabs you want to perform this behavior on, as well as the Models you want to update, into a Map/Dictionary — as you can see below in the “modelsToLoadByTab” object.

4. BONUS: Purely for Skuid geeks: skuid.$M(modelName) is a shortcut for skuid.model.getModel(modelName).

Here are my optimizations:


<br>//TAB MODEL INITIALISER<br>// Runs the relevant models when a tab is activated<br>// (to avoid the models needing to&nbsp;<br>//run on the initial page load).) <br>(function(skuid){<br>var $ = skuid.$;<br>&nbsp;&nbsp;<br>var modelsToLoadByTab = {<br>&nbsp; &nbsp; 'relationships': g'Relationships'],<br>&nbsp; &nbsp; 'agreements':;'Agreements'],<br>&nbsp; &nbsp; 'insurance':t'Insurance','Benefits','OtherInsurance']<br>};<br>var loadingMessage = 'Loading...';<br>// If true, then we'll only try to load our models<br>// if there's no data in them,<br>// which presumably won't be true&nbsp;<br>// if we've already queried for data in them<br>var onlyLoadIfEmpty = true;<br>$(function() {<br>&nbsp; &nbsp; $(document.body).on('tabshow',function(event){<br>&nbsp; &nbsp; &nbsp; &nbsp; var tabPanel = $(event.target),<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tabPanelId = tabPanel.prop('id'),<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; modelsToLoad = b];<br>&nbsp; &nbsp; &nbsp; &nbsp; if (tabPanelId in modelsToLoadByTab) {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $.each(modelsToLoadByTab;tabPanelId],function(i,modelId){<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var model = skuid.$M(modelId);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!onlyLoadIfEmpty || !model.data.length) {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; modelsToLoad.push(model);&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (modelsToLoad.length){<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tabPanel.block({<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message: loadingMessage,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; css:{<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; top:'50px'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $.when(skuid.model.updateData(modelsToLoad))<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .then(function(){<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tabPanel.unblock();&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; });<br>});<br>})(skuid);

That looks awesome. We’ll give it a spin!


Yep, this works really nicely. We got picky and made an adjustment to the “block” call so that it only greys out the table within the tab panel, rather than the whole panel.


//TAB INITIALISER //Runs when each tab in the side nav is selected. Populates the model //for the table(s) in the tab (avoiding the need for the models to load //their data on the initial page load.) (function(skuid){ var $ = skuid.$; var modelsToLoadByTab = { 'team': m'Users'], 'clients': s'Clients'], 'clientpeople': e'ClientPeople'], 'providers': s'Providers'], 'relationships': s'Relationships'], 'agreements':t'Agreements'], 'tasks':k'OpenTasks'], 'completedtasks':k'CompletedTasks'], 'assetsliab': b'LonsecProducts'], 'holdings': s'AssetsLiab'], 'meetings': s'Meetings'] }; var loadingMessage = 'Getting Your Info...'; // If true, then we'll only try to load our models // if there's no data in them, // which presumably won't be true // if we've already queried for data in them var onlyLoadIfEmpty = true; $(function() { $(document.body).on('tabshow',function(event){ var tabPanel = $(event.target), tabPanelId = tabPanel.prop('id'), tabPanelLastChild = tabPanel.prop('lastChild'), modelsToLoad = d]; if (tabPanelId in modelsToLoadByTab) { $.each(modelsToLoadByTabytabPanelId],function(i,modelId){ var model = skuid.$M(modelId); if (!onlyLoadIfEmpty || !model.data.length) { modelsToLoad.push(model); } }); //Runs the grey-out effect as the data is loading. Sets it //to the table within the tab because each table has the //'tabtable' class applied to it. if (modelsToLoad.length){ $('#' + tabPanelId + ' .tabtable').block({ message: loadingMessage, css:{ top:'50px' } }); $.when(skuid.model.updateData(modelsToLoad)) .then(function(){ $('#' + tabPanelId + ' .tabtable').unblock(); }); } } }); }); })(skuid);<br>

@Zach - would you be so kind as to tell me how I might modify this bit of code to make it work in a wizard, rather than a tabset? 


The Wizard Component fires a “stepchange” event, which tells you what the former step and the current step are — so you can do something like this:


//WIZARD MODEL INITIALISER // (to avoid the models needing to //run on the initial page load).) (function(skuid){ var $ = skuid.$; var modelsToLoadByWizardStep = { 'step2':t'Contacts'], 'step3':t'Opportunities'] }; var loadingMessage = 'Loading...'; // If true, then we'll only try to load our models // if there's no data in them, // which presumably won't be true // if we've already queried for data in them var onlyLoadIfEmpty = true; $(function() { $(document.body).on('stepchange',function(event,data){ var currentStep = data.currentStep, modelsToLoad = o]; if (currentStep.id in modelsToLoadByWizardStep) { $.each(modelsToLoadByWizardSteprcurrentStep.id],function(i,modelId){ var model = skuid.$M(modelId); if (!onlyLoadIfEmpty || !model.data.length) { modelsToLoad.push(model); } }); if (modelsToLoad.length){ currentStep.element.block({ message: loadingMessage, css:{ top:'50px' } }); $.when(skuid.model.updateData(modelsToLoad)) .then(function(){ currentStep.element.unblock(); }); } } }); }); })(skuid);<br>

thanks Zach!


This works well, except for some reason one of the models I’m late-loading on step 2 relies on a condition from a model I am capturing and saving on step 1. Despite various attempts, the model load on step 2 refuses to apply the condition based on the field saved in step 1… Any suggestions?

Brad



Actually, i got this to work by setting onlyLoadIfEmpty = false. I could probably optimise it further to only do this for the model in question, but it is working as needed… Cheers.

B


I tried to use the above javascript but the tabPanelId returned was something like “skuid-component-tab-15” instead of the label of the tab. I found the following link that has an e.g. of how to retrieve the label name.

https://community.skuid.com/t/access-label-of-currently-active-tab

Let me know if there is a more straightforward way to retrieve the tab label.


As of the Skuid Banzai release, there will be a much easier way to “lazy load Models” by waiting until a user first navigates to a Tab — there is a new Tab “Actions” property category where you can define sequences of Actions to run whenever a tab is shown, or just when a tab is first shown:



This is our recommended way to achieve the functionality requested in this post.


Hey Zach … I’ve been trying this out. I’ve put a query action on a tab “when first shown” property as in your screenshot. But when I click the tab and the action runs, I get an error “Attempting to de-reference a null object” (see screenshot). Can’t work out why. Any ideas?



I created a new page from scratch to test this again. Same error. It even occurs when trying to run updateData() on the model from the console. I’ve attached the XML. I’m running v6.18.


<skuidpage unsavedchangeswarning="yes" showsidebar="false" showheader="false" tabtooverride="Account" personalizationmode="server" theme="Modern"> <models> <model id="Account" limit="1" query="true" createrowifnonefound="false" sobject="Account"> <fields> <field id="Name"/> <field id="CreatedDate"/> </fields> <conditions> <condition type="param" enclosevalueinquotes="true" operator="=" field="Id" value="id"/> </conditions> <actions/> </model> </models> <components> <pagetitle model="Account" uniqueid="sk-195UgU-68"> <maintitle> <template>{{Name}}</template> </maintitle> <subtitle> <template>{{Model.label}}</template> </subtitle> <actions> <action type="delete"/> <action type="clone"/> <action type="share"/> <action type="savecancel" window="self"/> </actions> </pagetitle> <tabset rememberlastusertab="false" defertabrendering="true" uniqueid="sk-195kqS-110" renderas=""> <tabs> <tab name="Blank Landing Tab" loadlazypanels="true"> <components/> </tab> <tab name="Basics"> <components> <basicfieldeditor showsavecancel="false" showheader="true" model="Account" mode="read" uniqueid="sk-195UgU-69" buttonposition=""> <columns> <column width="50%"> <sections> <section title="Basics"> <fields> <field id="Name"/> </fields> </section> </sections> </column> <column width="50%"> <sections> <section title="System Info"> <fields> <field id="CreatedDate"/> </fields> </section> </sections> </column> </columns> </basicfieldeditor> </components> <oninitialshowactions> <action type="requeryModels" behavior="standard"> <models> <model>Account</model> </models> </action> </oninitialshowactions> <onshowactions/> </tab> </tabs> </tabset> </components> <resources> <labels/> <css/> <javascript/> </resources> <styles> <styleitem type="background" bgtype="none"/> </styles> </skuidpage>


Glenn, this is a known bug. Already fixed in dev. The next package will have this fixed.


Thanks Ben. Where are you publishing the updated Banzai release candidates?


We are working on getting out our final private release candidate this week, this will be delivered to private beta participants. We will be moving into public beta within the next 2 weeks, at which point release candidates will be available from the Skuid Releases page.