Skip to content

Conversation

@jonnyhork
Copy link
Contributor

What does this PR do?

This PR will add support a single selection mode of the Custom Select component and other improvements. This PR will also replace the standard <select> element with the Custom Select component to search and chose Objects in the SOQL builder.

New Features

  • @api value : string[] can be used to query state of the component for selections made independently of the model. This will help with the WHERE clause when we need to know that a selection has been made but wait until all modifiers have a value before updating the model.
  • If there are multiple Custom Selects on the page then only one will be allowed to display its options at a time.
  • The width of the component will be determined by its parent element up to a max width. NOTE: currently the options menu will still span the full width of the form column.
  • Added a arrow icon that will match standard <select>elements and toggle the options menu open or closed.
  • The placeholder text will fade out when the user clicks on the input
  • Added comments for posterity

Single Select Mode

  • If there is a selected option, then it will be displayed as the value of the component.
  • When the user clicks on the input the value will be highlighted (selected) so the user can start typing to replace it.
Single.Select.Mode.mov

What issues does this PR fix or reference?

@W-8616118@

@codecov
Copy link

codecov bot commented Jan 27, 2021

Codecov Report

Merging #130 (273211c) into feature/custom-select (e7e4e68) will increase coverage by 0.14%.
The diff coverage is 94.11%.

Impacted file tree graph

@@                    Coverage Diff                    @@
##           feature/custom-select     #130      +/-   ##
=========================================================
+ Coverage                  87.96%   88.11%   +0.14%     
=========================================================
  Files                         47       47              
  Lines                       1612     1666      +54     
  Branches                     365      380      +15     
=========================================================
+ Hits                        1418     1468      +50     
- Misses                       192      196       +4     
  Partials                       2        2              
Flag Coverage Δ
language-server 98.65% <ø> (ø)
soql-builder-ui 92.23% <94.11%> (+0.03%) ⬆️
soql-model 80.32% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
.../modules/querybuilder/customSelect/customSelect.ts 92.03% <93.65%> (+0.37%) ⬆️
...l-builder-ui/src/modules/querybuilder/from/from.ts 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e7e4e68...273211c. Read the comment docs.

Copy link
Contributor Author

@jonnyhork jonnyhork left a comment

Choose a reason for hiding this comment

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

Some of the behavior described in the PR comments may be hard to envision so if anyone wants videos I can make them.

I think a lot of this work will set us up to use this component for ODER BY and WHERE, hopefully with only changes needed in the parent components from here until we had multi -select support

Comment on lines +29 to +30
--soql-label-container-width: 80px;
--soql-selection-container-width: 400px;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now these names better reflect how the values are used.

Comment on lines +82 to +84
width: var(--soql-selection-container-width);
min-height: 100px;
max-height: 300px;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will dictate the dimensions of the options menu when visible. I was not sure how tall to make it. I did see some feedback in the issues of the tooling repo that they wish they could see more fields at one time. So maybe we could make it taller if need be?

Comment on lines +225 to +230
/*
During testing the getter for the displayValue() is called once
& before the renderedCallback that caches the inputSelectEl.
The only way I have been able to trigger another render of the component
is to click the search bar.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might want to post a question to the LWC framework channel asking about where the getters fit into the lifecycle of the component. I'm am surprised to see the getter called before the rendered callback and that an element is not in the DOM when we query for it in the renderedCallback()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For now I can do a click event to get everything in place for the test but its not ideal.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if the gitter is called during the render process. And then when it's finished rendering, we get the renderedCallback()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could see that. Does that mean that we are not using the right pattern to capture or access elements in the DOM between the getter and rendered callback? Something to consider.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if there's other options. besides we introduce an isRendered boolean and then check for that everywhere. that could also be messy. i think it's just something we have to deal with.

});

describe('Dropdown Arrow', () => {
it('should toggle the options menu when clicked, with directional arrow', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about breaking this up into 3 tests but found I repeating 90% of the code so I just combined it.

Comment on lines +54 to +75
/*
1. If the user is typing, display the searchTerm
2. If singleSelect
- display the selected value (default)
- if there is no searchTerm
& the input is in focus
& the input is empty
display the placeholder
*/
get displayValue(): string {
if (this.hasSearchTerm) {
return this.searchTerm;
}

if (this.isSingleSelect && this.selectInputEl) {
if (!this.selectInputIsFocused || this.selectInputEl.value.length) {
return this._value[0] || '';
}
}

return '';
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method is used to control what displayed in the search bar as the value. I need to control the display logic and the state of the component separately.

I found that getting the behavior I wanted was tricky, and this might be something that is just inherently not intuitive. Without || this.selectInputEl.value.length in line 69, when the user would delete the last character of the input then the selected value would immediately repopulate in the input instead of showing the placeholder.

Comment on lines +182 to +187

if (this.isSingleSelect) {
this._value = [optionValue];
} else {
this._value = [...this._value, optionValue];
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This might need to change when we want to implement true multi selection but the intention is to maintain state in the component with having to read/write to the model.

Comment on lines +23 to +24
set selected(objectName: string) {
this._selectedObject = objectName ? [objectName] : [];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The custom select component accepts an array of selections so I needed to re package the values from the model.

Comment on lines +20 to +21
--soql-label-container-width: 80px;
--soql-selection-container-width: 400px;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed this now so that the dataView and the builder UI are in sync.

Comment on lines +93 to +98
get isSingleSelect() {
return !this.multiple;
}

get isMultipleSelect() {
return this.multiple;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realized this api as it is might be a bit odd.
this.multiple is set as boolean value false in the component. However, because LWC won't let us use literals in the template multiple={true} we have to pass in a string multiple="true".
Unfortunately, as it stands, that means that I could put anything as the the string value and it would be coerssed as boolean true so multiple = ''false" passed in from parent to child is also true.

To fix this I would have to set the default value to string "false" and only work with string values "true" and "false"

get isSingleSelect() {
    return this.multiple === "false" || this.multiple === undefined;
  }

Or it would depend on the parent to use a getter to pass a boolean value to the child

// in the parent
get isMultiple() {
  return true;
}
// html
multiple={isMultiple}

I do not like the option with a getter in the parent. Sicme we are the only ones using this component at the moment I can see leaving it as is for now. I also don't mind adding logic to check explicitly for strings "true" or "false" but it might be a but verbose for a simple check, idk.
Thoughts @dehru?

Copy link
Contributor

Choose a reason for hiding this comment

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

Meh, it's currently just us using it. and messing that up would be just us using it wrong.


.select__dropdown-arrow {
transform: rotate(180deg);
margin-right: 2px;
Copy link
Contributor

Choose a reason for hiding this comment

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

for some reason, I feel the arrow is uncomfortably close to the right. Just a tad. Not a change request, just stating a strange gut reaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ya know I had it at 5px originally but I noticed it was further away from the standard element so I changed it to match. I also feel like its a little close but the other thought it that we may want more room once the component is smaller in the WHERE form.

</template>
<template if:true={noResultsFound}>
<!-- i18n -->
<p class="option option--placeholder">No results found.</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

placeholder is a loaded term. how 'bout option--empty or option--noresults

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with option--disabled to be more generalizable. That way if we use it for something else we don't have to change the class again.

searchBar.focus();
return Promise.resolve().then(() => {
const classList = Array.from(searchBar.classList);
expect(classList).toContain('select__input-placeholder--fadeout');
Copy link
Contributor

Choose a reason for hiding this comment

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

It's weird to have css classname changes to cause failing tests. I had that case in my work this week. it's a valid test...not much we can do about it.

return Promise.resolve()
.then(() => {
optionsWrapperClassList = Array.from(optionsWrapper.classList);
expect(optionsWrapperClassList).toContain('options--open');
Copy link
Contributor

Choose a reason for hiding this comment

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

might think about making this a const since it's used thrice in this test case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thanks

})
.then(() => {
optionsWrapperClassList = Array.from(optionsWrapper.classList);
expect(optionsWrapperClassList).not.toContain('options--open');
Copy link
Contributor

Choose a reason for hiding this comment

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

very nice

Comment on lines +3 to +16
/** CUSTOM SELECT API
* @attr multiple: string - Toggle single-select or multi-select behavior.
* NOTE --> Will be interpreted as true if there is any string value passed in.
* @attr is-loading: boolean - Will display 'Loading...' when true.
* @attr all-options: string[] - This the list of all possible options
* the user can select from.
* @attr selected-options: string[] - If present in single-select, the selectedOption
* will be displayed as the value of the input.
* The list of options rendered will be all-options - selected-options
* @attr placeholder-text: string - This is the value to be displayed as a placeholder for the input.
* @property value: string[] - Will return the currently selected value(s).
* @event option__selection - This is emitted everytime a valid option is selected.
* detail { value: optionValue }
* **/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this to help future dev know how to use this component.

@dehru
Copy link
Contributor

dehru commented Jan 28, 2021

This is so good i gotta get it merged asap.

@dehru dehru merged commit e8f890d into feature/custom-select Jan 28, 2021
@dehru dehru deleted the jhork/custom-select-from branch January 28, 2021 21:05
dehru added a commit that referenced this pull request Feb 1, 2021
* Jhork/custom select fields (#111)
* Jhork/custom select from (#130)
* Jhork/custom select order by (#134)
jgellin-sf pushed a commit that referenced this pull request Feb 2, 2021
* Fix preview not updating when autosave is off - issue 120 (#125)

* add soql-statement to model
* verify that we include the soql statement in the model
* update the package version so we can publish and consume

* Add smarter completion on GROUP BY clause and WHERE literals (#126)

* Initial support for Aggregate functions.

Also:
 * Fix: Propose "COUNT()" only if right after "SELECT"
 * Remove special-case for "SELECT (SELECT ), | FROM Foo". Not worth it

* Add completion inside function expression

Example: SELECT AVG(|) FROM

Also:
 * Improve ANTLR error recovery to better extract FROM expresssions

* Make completions inside function expression type-aware

* Add smarter completion on GROUP BY clause

Also:
 * Complete only with "groupable" fields.
 * Give priority to complete GROUP BY with fields which appear
   on SELECT and are not aggregated or groupped.
 * On ORDER BY: Complete only with "sortable" fields
 * Add completion on semi-join SELECTs (in WHERE clauses)
 * Fix defect when cursor is around multiple newlines
 * Improve query analysis impl
 * Pre-select "WHERE" option if provided

* Improve support for queries with semi-joins

* Refactor: move soql operator semantics to LSP server-side

* Add more code-completion proposals for literal values

Including:

* Range date literals (LAST_YEAR, YESTERDAY, LAST_N_DAYS:n, etc.)
* Simple templates for: int, double, string, currency

* Quiet completion on HAVING clause

* Add a space before opening parenthesis after INCLUDES/EXCLUDES

* New soql lsp version 0.3.3 (#140)

* Feature/custom select (#148)

* Jhork/custom select fields (#111)
* Jhork/custom select from (#130)
* Jhork/custom select order by (#134)

* set the error overlay to be on top of all other form elements (#150)

* set the error overlay to be on top of all other form elements
* add z-index to .unsupported-syntax

* prepare to publish 0 .0.30 (#151)

Co-authored-by: Fernando Dobladez <fernandodobladez@salesforce.com>
Co-authored-by: Jonny Hork <jhork@salesforce.com>
dehru added a commit that referenced this pull request Feb 5, 2021
* Jhork: Support for Simple WHERE Expressions (#101)
* move rule to css common (#108)
* Preventing webview re-rendering when soql statement has not changed (#105)
* Revert "Disable WHERE in model (#113)"
* Type-aware where (#109)
* Fix preview not updating when autosave is off - issue 120 (#125)
* Recognize recoverable where errors (#116)
* Operator and Criteria Error Tooltips (#122)
* Support IN, NOT IN, INCLUDES, EXCLUDE operators (#123)
* Add smarter completin on GROUP BY clause and WHERE literals (#126)
* Feature/custom select (#148)
* Jhork/custom select fields (#111)
* Jhork/custom select from (#130)
* Jhork/custom select order by (#134)
* set the error overlay to be on top of all other form elements (#150)
* prepare to publish 0 .0.30 (#151)
* Feature/where (#154)
* Jhork: Support for Simple WHERE Expressions (#101)
* move rule to css common (#108)
* Preventing webview re-rendering when soql statement has not changed (#105)
* Revert "Disable WHERE in model (#113)"
* Type-aware where (#109)
* Recognize recoverable where errors (#116)
* Operator and Criteria Error Tooltips (#122)
* Support IN, NOT IN, INCLUDES, EXCLUDE operators (#123)
* Revert "Feature/where (#154)" (#157)
* Add support for line comments ( `//` ) at the top of SOQL files (#132)
* Jhork/where custom select (#161)
* Fix: 'undefined' in SOQL string (#162)
* Clear tooltip when object changes, fix empty soql statement error blocker (#165)
* Track to force render on condition change - fix rebase conflict (#166)
* do not send event when user still adding input' (#169)
* Jhork/clear modifier field bug fix (#168)
* handle a option selection event (#178)
* cler tooltip error when X is clicked (#180)
* Jhork/workflow updates (#163)
* Bump versions for release (#181)

Co-authored-by: Jonny Hork <jhork@salesforce.com>
Co-authored-by: dehru <599418+dehru@users.noreply.github.com>
Co-authored-by: Dehru Cromer <dehru.cromer@salesforce.com>
Co-authored-by: Fernando Dobladez <fernandodobladez@salesforce.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants