Skip to content

Admin select component performance #6213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

chaimann
Copy link
Contributor

@chaimann chaimann commented Apr 10, 2025

Summary

Allows to fetch options from remote resource, rather than rendering all options in place.

Usage

:src attribute

When initialising select component pass :src attribute with a url to JSON resource, omit :choices or pass them as nil.

Mapping fields

Maps fields "id" and "name" from JSON data to be value and text of an option accordingly. If the fields have different names, map them with :"data-option-value-field" and :"data-option-label-field" when initialising select component, e.g.:

// JSON response
[
  { "key": "ruby", "text": "Ruby" },
  { "key": "js", "text": "JavaScript" }
]
# to render these as options use:
component("ui/forms/select").new(
  ...,
  "data-option-value-field": "key",
  "data-option-label-field": "text",
)

Nested JSON data

If data in response JSON is nested, you can specify :"data-json-path" parameter to retrieve the data by given path signature, e.g.:

// JSON response
{
    "request": "https:\/\/whatcms.org\/API\/List",
    "result": {
        "code": 200,
        "msg": "Success",
        "list": [
            {
                "id": 1,
                "label": "WordPress",
                "type": [
                    "Blog",
                    "CMS"
                ]
            },
            {
                "id": 2,
                "label": "Joomla",
                "type": [
                    "Other CMS",
                    "CMS"
                ]
            },
    ...
# to access data specify path:
component("ui/forms/select").new(
  ...,
  src: "https://whatcms.org/API/List"
  "data-option-label-field": "label",
  "data-json-path": "result.list"
)

Loading template

A nice little "Loading" template placeholder is there to be displayed while the options are being loaded.

Screen.Recording.2025-04-10.at.16.14.46.mov

Checklist

Check out our PR guidelines for more details.

The following are mandatory for all PRs:

Copy link

codecov bot commented Apr 10, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.79%. Comparing base (dad997b) to head (8c45c12).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6213      +/-   ##
==========================================
+ Coverage   86.52%   88.79%   +2.27%     
==========================================
  Files         518      840     +322     
  Lines       11934    18227    +6293     
==========================================
+ Hits        10326    16185    +5859     
- Misses       1608     2042     +434     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fthobe
Copy link
Contributor

fthobe commented Apr 10, 2025

This will be very healthy when trying to deal with zipcode zones and I really appreciate this change.

@chaimann chaimann marked this pull request as ready for review April 10, 2025 14:25
@chaimann chaimann requested a review from a team as a code owner April 10, 2025 14:25
@kennyadsl
Copy link
Member

@chaimann nice! Which select(s) do we need this for?

@tvdeyen
Copy link
Member

tvdeyen commented Apr 10, 2025

@chaimann nice! Which select(s) do we need this for?

This is very useful for Product-, Variant- and Taxon-Selects.

@kennyadsl
Copy link
Member

Right, we were doing something similar with select2 on the legacy admin.

Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing. Thanks. Can we make the src argument a bit more approachable? It's too important to "hide" it in a Hash :)

@@ -25,22 +25,40 @@ class SolidusAdmin::UI::Forms::Select::Component < SolidusAdmin::BaseComponent
},
}.freeze

def initialize(label:, name:, choices:, size: :m, hint: nil, tip: nil, error: nil, **attributes)
def initialize(label:, name:, choices: nil, size: :m, hint: nil, tip: nil, error: nil, **attributes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to have src as named argument instead of "hiding" it in the attributes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense! 😄

Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we please not use data- attributes on our custom element? Sorry, missed that during previous PR review

async fetchOptions() {
const response = await fetch(this.getAttribute("data-src"));
return await response.json();
const [url, dataPath] = this.getAttribute("data-src").split("::");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a dedicated attribute for that? something like json-result-path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tvdeyen are you strongly against this one? 😄 I did it this way because it's more "convention over configuration" and saves us from specifying one more attribute on the select element. Also it felt natural in a way that src is not just a URL but a full "waypoint" to where the relevant data can be accessed. Or do you think it's way too implicit/inconvenient?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. But it seems a bit weird. I think another attribute is nothing bad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. I had another thought and guess being more explicit about this feature might be a better choice

@chaimann
Copy link
Contributor Author

@chaimann nice! Which select(s) do we need this for?

@kennyadsl also initially this was a spin-off from PR for zones add/edit where when a zone is state based there is a select with all the states. TomSelect handles dynamically loaded options pretty good, without any noticeable impact in the browsers; there is also a setting maxOptions which limits the number of options shown in the dropdown, further improving the performance. I set it to 500 (which I think might even be reduced to 100, but there does not seem to be any difference in performance), and for large option collections I believe the user will still be relying on search filtering rather than manually scrolling to find desired option.

@fthobe
Copy link
Contributor

fthobe commented Apr 10, 2025

@chaimann it's more complicated than that. You can expect on Zip based zones up to thousands of values.

The issue is outlined in #6187,
you can expect thousands of values and no effective way to manage them other than fully copy and pasting or csv import (which is not a default option on Solidus). What you did is a great step forward in allowing effective management of zones.

@chaimann
Copy link
Contributor Author

@fthobe amazing! I'm super glad that it's going to be useful :)

@chaimann chaimann force-pushed the admin-select-component-performance branch from 9de8feb to f9e3557 Compare April 11, 2025 12:33
@chaimann chaimann requested a review from tvdeyen April 11, 2025 13:07
Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like that! I also worry about the 500 results we pull. Can we utilize the pagination for that? Does TomSelect support it, like select2 does?

controlClass: "control",
dropdownClass: "dropdown",
dropdownContentClass: "dropdown-content",
optionClass: "option",
wrapperClass: "wrapper",
maxOptions: null,
maxOptions: 500,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to utilize the pagination feature from our API, like we do in the productPicker and variantAutoComplete?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tvdeyen The results are actually preloaded in full, and this maxOptions does not control how many results we pull, but how many results we display on dropdown open. It's like a small "hack" that allows to reduce the lag on TomSelect side (when displaying more than 1k options the search and select/unselect starts to have a visible delay). As I mentioned previously, having it in place helps to build a visual of a "full" dropdown, while expecting the user to use search instead of selecting options by manually scroll-finding them.

We also should be able to make pagination work with TomSelect, there is a plugin for that which I haven't tested yet but I believe it should work. I agree that it would be better in the long term to have paginated results instead of full preload, as the larger a dataset is the longer the request would be processed. The only downside I can see is that the search function would now need to hit the server as well, but I think I can configure it to search the "cached" fetched data first.

@chaimann chaimann marked this pull request as draft April 16, 2025 17:04
This change covers the problem that was discovered while
working on zone creation and modification update (states
select). The select box performance dropped significantly when
there were thousands of options rendered for native select.
So instead of rendering regular options, it's now possible to
load them from the server and let TomSelect construct available
options. For convenience, it also leverages TomSelect "loading"
template option to show feedback to the user while request is
being processed.
When json response contains other data than just array of option
data, allow to access given array by specifying its path in "src"
attribute.
Uses "virtual_scroll" plugin to implement loading more options on scroll.
Retrieves next page URL from response header "Link".

Also patches an existing bug with dropdown scroll:
orchidjs/tom-select#556
orchidjs/tom-select#867
Separates existing functionality from "solidus_select"
into plugins:
- patch_scroll.js - patches mentioned bug with dropdown
scroll;
- stash_on_search.js - hide and store selected item in a
single select when typing, restore if nothing selected;
- remote_with_pagination.js - core functionality to load
options from remote source;
@chaimann chaimann force-pushed the admin-select-component-performance branch from 808bcdc to 0e9fb3b Compare April 22, 2025 18:19
@github-actions github-actions bot added the changelog:solidus_core Changes to the solidus_core gem label Apr 22, 2025
@chaimann chaimann force-pushed the admin-select-component-performance branch from 44c726d to 2f4eae1 Compare April 22, 2025 19:32
@chaimann
Copy link
Contributor Author

Screen.Recording.2025-04-23.at.09.26.17.mov

Added infinite scroll for remote select, also patched the bug with dropdown scroll, and redesigned single select search to show search term when typing in the field, restoring selected option if user does not select anything after search.
Also cleaned up "solidus_select" by separating different pieces of logic into separate plugins, courtesy of TomSelect plugins system :)

@chaimann chaimann marked this pull request as ready for review April 23, 2025 07:34
@chaimann chaimann requested a review from tvdeyen April 23, 2025 07:34
For preselected options to work correctly these two
settings need to be initialized along with TomSelect
rather than inside the remote_with_pagination plugin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants