How to structure complex, related pages for high performance?

  • 1
  • Question
  • Updated 4 years ago
  • Answered

Hey Team Skuid,

I have a meaty one for you. :)

We have a Skuid page within a Visualforce page that displays full screen in Salesforce (no standard headers or sidebar). The Skuid page has a top nav bar, then panel set below consisting of a fixed left panel and a fluid right panel. The right panel has a tabset in it with ten tabs and a vertical nav bar (the strip of icons). 


Each tab has quite heavy content and the page is bigger than we want (about 6500 lines of XML). We've taken steps to improve page load performance by using Javascript to load models on demand (quite effective) and setting the Visualforce page cache to true (great for speed, but causes problems because it caches Skuid content, which requires more working around). We think the best idea is to split the page up into ten Visualforce and Skuid pages, one each per tab. This will give us faster initial page load (although it'll be slower to move from "tab" to "tab") and will also give us distinct URLs per tab, which is a plus.

The question is: how best to structure the Visualforce and Skuid pages for a seamless experience between the ten pages? The top nav and side nav (the former tab labels) need to be fixed, while the side nav (to the left of the side nav) and the fluid right panel are specific to each page.

The simplest idea is to keep the top and side nav bars within each of the ten Skuid pages. But how can we cache them so they don't "flash" between pages?

An alternative is to create the top and side nav bars in Visualforce, then in each Visualforce page, drop in a left panel Skuid page and main right panel Skuid page. If we set the Visualforce page caches to true, then there's no nav bar flashes between pages, but in the Skuid pages we have to use Javascript to handle the caching (by forcing updateData on models), which is a bit ugly.

Any thoughts? It's a bit meaty, I know. :)

Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
  • optimistic

Posted 4 years ago

  • 1
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
You could use just one page include with the strip of the ten icons on the left that essentially act like tabs. In other words, you don't need to use a tab set. That'll reduce the number of lines from the tabs. Just need to work out a way to set the icon to blue/grey using javascript vs. mustache #^.

So you'd use javascript to set the page include and icon.

That would make for a very small set of XML lines to load. The main page and the current tab.
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
Yep, we're definitely getting rid of the tabset. A page include for the strip of icons would kind of work, but it wouldn't remain between pages, it would "flash" and return with every page change.
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Make the icons part of another panel maybe?
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
Unfortunately that still won't address it. Skuid serves up all content fresh every time, unless we set cache=true on the container Visualforce page. But if we do that, all the Skuid content will cache, even it has changed in the underlying data. It's tricky.
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
So, responsive, no page flashing & contextual URL. Maybe this?

You're Skuid page has fixed left panel for the status bar, pic and contact info.



Add another fixed left panel to the left. Add a template for each icon. Set a Class for each of the template so you change the styling using Javascript and CSS later of each icon specifically. The icons are PNG, so it's transparency and background colour can be set. This is beyond me how, but shouldn't be an issue.



Put page includes for each page into the floating panel to the right.

Add a "Tab" picklist field to the Account to store which Icon is currently clicked. Use javascript to set the field and CSS of the icon.

Then add conditional rendering on each page include based on which icon is clicked (picklist field value).

Use lazy load of the page include to load in the desired order. Load the clicked icon (currently set picklist value) first to get the user going, then load the others that have yet to be rendered. Assuming this is possible. Going to ask this question in a separate post.

Now, the fun part, the contextual URL bit. Set a condition on the Account for the picklist field you created to point to a URL param. That way you can set which account and which "Tab" to view in the URL.

The only for sure issue I can think of is is the URL in the address bar has that URL parameter to select which "Tab" to originally set. Refreshing the page will reset the tab to this.

The URL can be altered using javascript but refreshing pages resets to the original URL. There is a lot of options on how to maybe circumvent this when searching "update url using javascript" & "update browser history using javascript". Again, beyond me.

Pros
Fast VF page load
Fast Skuid Tab page load
No flashing of the page
Contextual URLs 


A few assumptions.
- styling of icon can be managed via CSS and javascript
- pages can load in unrendered page includes
- URL can be updated to circumvent page reload icon/tab reset issue.

WEEEE! I refer to myself and others when I'm having fun.
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Wait, why would the page flash? Page includes shouldn't do that?

Wait, do you have the icons in the page include pages? Put them in their own panel and the page include into it's own panel.

So you'd have two left fixed panels and one fluid panel. The fluid one would have the page include.
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
That approach would be OK if we just wanted one container Visualforce page (notwithstanding the performance of page includes, which I'm concerned wouldn't be fast enough, but we'd test that). But we want to move to one Visualforce page per "tab" so that we get contextual URLs. If we do that and don't specify cache=true, we'll get flashing of the icon strip.
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Forgot about the contextual URLs. Tricky.

Can you use the URL you build to set the page include? That way you can still have the distinct URL functionality. Use javascript within your main page to set the page include.

Function for "on page load" call Function SetPageInclude
Function for "on icon click" use Function SetPageInclude

SetPageInclude
Set page include using param.

To be honest I'm not sure I completely follow.
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
Any Skuid folks with thoughts on this?
Photo of Rob Hatch

Rob Hatch, Official Rep

  • 44,006 Points 20k badge 2x thumb
My thoughts:  This makes my head hurt.  Great Questions.  I'm not sure there are great answers.  We'll give it a spin at the office... 
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
I'm exploring a design where we have a Visualforce page for every page and shared Visualforce include pages for the top and side nav bars. Then in each VF page we drop in two Skuid pages, one for the side bar and one for the main panel.

However, is it possible to have two Skuid pages within one Visualforce page? When I try it, the page fails to load, with no error given. Just wanted to check that this is possible before I pursue it further.

I'm including Skuid pages with a simple:

<skuid:page page="ClientBasics"/>
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
I'd love to get some direction on including multiple Skuid pages in a Visualforce page. Is it possible? Would be powerful if so.
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,056 Points 20k badge 2x thumb
Glenn, great questions here. Can't answer all of them now, but let's tackle this one: the "cache" and "expires" attributes on <apex:page/>'

This approach is NOT viable if you have any Models on your page that load their data on initial page load. To use the cache approach, you would have to turn OFF "Load Model data on page load" on all your Models whose data might possibly be changed by user input on a regular basis , and you would then have to have these Models load their data asynchronously sometime after initial page load in order to prevent the data from being cached. Essentially the page load approach you would be taking in this scenario is this:

(a) On initial page load: load "static" content --- including the Skuid Page record, JS / CSS resources, images, and, if you set "Load Model data on page load" to be FALSE, any Skuid Models whose data does not need to be fresh.

(b) Asynchronously, after page load: load in transactional data that is likely to change.

This is all our "current state" recommendation. All this being said, we have been considering / researching doing this "asynchronous load" behavior for all Models, for this reason as well as for other infrastructural reasons (including making Skuid Pages more modular, dynamically instantiable, and less presumptuous about being the only Skuid Page included in a given environment). In this scenario, no Models' data would be loaded server-side on initial page load and then sent down with the rest of the page's HTML, all Model data would be loaded immediately upon DOM load. But we're not there yet, and there's definitely questions about whether this is in fact the best performance. 
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Seamless transitions? Like not refreshing the page but updating the components? That would be pretty slick.

Very much a newb in javascript but could this work?
  1. Set the id parameter using history.pushState.
  2. updateData on the Account model.
This next thing I'm thinking of is a bit overkill but I like overkill when it adds a nice touch to the experience. It may be desirable for the user to automatically go to the tab they last were on for the current Account when switching to it.
  1. Create a Junction Object between Account and User called "UserAccountSettings".
  2. Create a field in this object called "LastTab".
  3. Create/update records in this object when necessary.
Now you could use the "LastTab" field to determine which Tab to default to when switching accounts. The field would have to be overridden if the URL has the tab parameter set. Some inline javascript can be used to look to see what is present in order to handle to flow of which source to set the "Tab panel Unique Id". If URL tab param, elseif "LastTab", else first tab.

My thoughts around are somewhat scattered, but I'm still wrapping my head around javascript.

Which ever way you find to work, I'm very curious about the user experience. No page refresh would be awesome.
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
Our tech guru, Dan, is approaching the switch client matter in exactly the way you describe: push the id into the URL and update the SelectedClient model.

I like the rest of your idea, but I'm inclined to agree with your initial summary, that it might be a tad overkill. We find that users don't want to return to where they were for a particular client/page/tab. They want to go back to the first point every time for the sake of consistency. Makes training easier.

If you're into such things, I highly recommend the background reading on Twitter's UX particularly, also LinkedIn. They know their stuff.
Photo of mB Pat Vachon

mB Pat Vachon, Champion

  • 42,714 Points 20k badge 2x thumb
Bookmarking that for sure. Still learning my way through javascript. Firmly in the beginner stage. Really love the new level flexibility that comes with it!!!
Photo of Ben Hubbard

Ben Hubbard, Employee

  • 12,490 Points 10k badge 2x thumb
Hi Glenn,

To answer your quesiton about rendering performance in Superbank.  We were able to make some significant improvements to client side rendering.  It turns out that Sizzle, jQuery's selector engine does not handle switching between searching an XML document and a DOM document in a very performant way.  Skuid uses Sizzle to check your Skuid page XML definition and then builds a portion of your actual Skuid page in the browser.  During the rendering of a typical Skuid page, Sizzle may have to switch between these two documents hundreds, (or even thousands) of times.  Each time Sizzle switches contexts, it does quite a few expensive browser tests to figure out how to handle the quirks of each browser.  With a slight modification of Sizzle, we were able to get Sizzle to cache the browser quirks and only run those expensive tests once per document per page load.

All that to say, you might notice some improvements in client side rendering.  In some of my tests I've taken a large table with hundreds of visible records from 1 second to about 300 milliseconds in client side rendering time.  There's always more room for additional performance improvements in Skuid and we hope to continually get page load time down.
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
Thanks Ben, that's really interesting. I should be clear that everything that I've outlined above was done in 5.21.8; we haven't even tried it in Superbank yet (which we only have installed in a scratchpad org). Perhaps we'll see even better performance than now.
Photo of Barry Schnell

Barry Schnell, Champion

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

Very interesting thinking.  Pros/Cons of course but one benefit of something like this is improving model awareness.

Today, if a model is marked "createnewifnonefound", the skuid runtime creates the row client side but it occurs without any opportunity for row.created events to be captured or actions declared on models to fire.  Same with row.updated.  In fact, I'm not even sure that they are fired because no one would be listening :)

With the above approach, since models will be retrieved client side instead of parsed from server response, it would give an opportunity for event listeners to listen for initial events that occur on models and also provide the action framework events specified on models to fire for initial rows created/updated. Even in today's paradigm, it would still be possible for skuid to publish or make available a queue of events that occurred before third-party code got loaded up (this would be extremely beneficial in our case :)) but retrieving model data asychronously might simplify this.

if you guys do decide to rework some of this, would be great to see a full complement of events added:

skuid.begininit - Prior to skuid initializing its runtime, models, etc.
skuid.endinit - Skuid completed all initialization requirements
skuid.ready - Any event listeners to endinit have confirmed they are ready so now skuid & all custom components are fully ready to go.  Does not require that each component subscribes, only those that care about it.
skuid.rendered - a component was rendered
** I could think of more but will contain my excitement :)
** The above approach could involve true/false/deferred return value from event subscribers or an eventArg.isCancelled concept so that skuid could halt processing

For "normal" skuid pages, it would be a benign change but for advanced pages, having more granular awareness and access would make a big difference.

I've taken an approach along these lines (leveraging skuid.events) in the framework I'm working on and it's resulted in simplified page development.  

Adding flexibility of course comes at a cost/risk (e.g. performance, etc.) so adding in this type of stuff isn't a straightforward decision.  Just something else to consider as you work through decisions/design.
Photo of Irvin Waldman

Irvin Waldman, Champion

  • 9,006 Points 5k badge 2x thumb
Glenn, Good post and responses.