Skip to content
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

Change tab completion in the UI to prefer common prefix #6759

Merged
merged 4 commits into from
May 22, 2019
Merged

Conversation

meirish
Copy link
Contributor

@meirish meirish commented May 20, 2019

This changes the autocomplete behavior on routes that use the NavigateInput with the list-controller mixin so that tabbing on the keyboard will first check to see if there is a common prefix. Previous to this, tabbing would complete the first match in the list - this is now only done when there isn't a common prefix for all of the currently listed keys.

In the example gif we see that hitting tab on the full list auto-completes "one" - this behavior is the same as before. What's new is if you filter the list (in the gif typing sec) and then tab, the completion fills to secret- and then will complete secret-one on a second tab. This behavior is closer to how autocomplete works on the Vault CLI.
autocomplete

@meirish meirish marked this pull request as ready for review May 20, 2019 22:25
@meirish meirish requested review from a team May 20, 2019 22:26
@johncowen
Copy link

@opihana, quick tag as this is interesting for us and our upcoming work

Copy link

@johncowen johncowen left a comment

Choose a reason for hiding this comment

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

Hey Matthew,

I only came to nudge @opihana about this as it interesting UX for us to lean on for some upcoming work, then I thought I'd take a quick look and ended up saying quite a bit 😬 .

I've got a feeling I might have missed the point with this, or maybe I'm just looking at it differently, so take a lot of this with a pinch of salt - see what you/others think.

Where's the :bushes: emoji when you need it!

John

import { get } from '@ember/object';

export default function(arr, attribute = 'id') {
let content = arr || [];

Choose a reason for hiding this comment

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

Could you replace this line 4 with a default argument? Nit picky, but you do it with attribute also, up to you.

function(arr = [], attribute = 'id')

Choose a reason for hiding this comment

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

In fact looking over the rest I don't think the rest of the function works if arr/content === []? Maybe I read it wrong?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yep, need an early return - in the app we're checking to make sure there's a match first, but with that adjustment wouldn't need to. Will get that fixed up!

// this assumes an already sorted array
// if the array is sorted, we want to compare the first and last
// item in the array - if they share a prefix, all of the items do
let firstString = get(content[0], attribute);

Choose a reason for hiding this comment

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

Is this ok if content[0] === undefined? Not massively sure. (P.S. Also see comment above)

return re.test(key.get('id'));
});
let matchSet = content.filter(key => re.test(key.id));
let match = matchSet.firstObject;

Choose a reason for hiding this comment

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

Just wondering if you can do matchSet[0] with the new ember proxy things? Does that proxy through properly or do you have to use matchSet.firstObject?

Choose a reason for hiding this comment

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

Also, unless I've misunderstood, you could probably take the opportunity while you are here to make this consistent:

    let filter = this.filter;
    let content = this.model;
    let filterMatchesKey = this.filterMatchesKey;
    let re = new RegExp('^' + escapeStringRegexp(filter));

Again up to you, just something I noticed, pretty sure you can switch out those gets now but maybe you have a reason to keep them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/me nods - I often forget to update all of the surrounds when I'm in an old file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cleaned up a lot of this in e0b4c7e - thanks!

// item in the array - if they share a prefix, all of the items do
let firstString = get(content[0], attribute);
let lastString = get(content[arr.length - 1], attribute);

Choose a reason for hiding this comment

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

I'm confused as to why you only check the last string in the array here, maybe I don't understand the use case? Ah ok hold on I just re-read that comment up above here (lines 5-8). Wonder if it would be nicer to just pass the two strings instead of an array?

function commonPrefix(a = '', b = '') {
...
}
commonPrefix(arr[0].id, arr[arr.length - 1].id)
commonPrefix(arr[0].name, arr[arr.length - 1].name)
// maybe I might want to recur this at some point
function(item) {
  commonPrefix(prev.id, item.id)
}

You also remove your get dependency by doing that and reduces your function to just do one thing. Again, up to you

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 think having the whole array contains the overall logic a bit better - if we later decide to change how we calculate the common prefix, we'll have all of the data here - I don't think it saves us anything because we'd need to pull out those strings somewhere. Good point about the get usage - looking at it, I think we can nix it anyway since we can use ES5 getters - I'll look at that!

// the longest the shared prefix could be is the length of the match
let targetLength = firstString.length;
let prefixLength = 0;
// walk the two strings, and if they match at the current length,
Copy link
Contributor

Choose a reason for hiding this comment

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

great use of comments in this function, btw. thank you!

andaley
andaley previously approved these changes May 21, 2019
Copy link

@johncowen johncowen left a comment

Choose a reason for hiding this comment

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

LGTM

@meirish meirish merged commit 8cdba29 into master May 22, 2019
@meirish meirish deleted the ui-filter-tab branch May 22, 2019 14:28
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.

3 participants