-
Notifications
You must be signed in to change notification settings - Fork 167
fixes issue #84, for pasting with delimiters #90
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
fixes issue #84, for pasting with delimiters #90
Conversation
lib/ReactTags.js
Outdated
|
||
if (data && delimiterChars.length > 0) { | ||
// split the string based on the delimiterChars as a regex | ||
const tags = data.split(new RegExp(delimiterChars.join('|'), 'g')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be simplified a little with a group:
data.split(new RegExp('[' + delimiterChars.join('') + ']'))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make the change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to check for active characters though: delimiterChars
may contain chars that completely change the semantics of the group (such as ^
at the start, or -
anywhere in the group pattern).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing some tests, the original data.split(new RegExp(delimiterChars.join('|'), 'g'))
seems more robust than data.split(new RegExp('[' + delimiterChars.join('') + ']'))
.
Results:
delimiterChars = ['^',',',';']
data = 'green; blue, white'
data.split(new RegExp(delimiterChars.join('|'), 'g'))
// result: ["green", " blue", " white"]
data.split(new RegExp('[' + delimiterChars.join('') + ']'))
// result: ["", "", "", "", "", ";", "", "", "", "", ",", "", "", "", "", "", ""]
I am tempted to leave the original code based on this test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that the original code still needs checks for active characters, too: if someone uses .
to act as tag separator, then suddenly things will go horribly wrong without escaping. To do this safely, you'd have to not use regexp at all, but instead set some restrictions on delimiterChar so that you can safely tokenize with its content:
function splitOnDelimiters(input, delimiterChars) {
const parts = [];
const chars = Array.from(input);
for(let i=chars.length-1; i>=0; i--) {
if (delimiterChars.indexOf(chars[i]) > -1) {
parts.push( chars.splice(i+1, chars.length - i).join('') );
chars.pop();
}
}
if (chars.length > 0) {
parts.push(chars.join(''));
}
return parts.filter(v => v).reverse()
}
(and then of course this code would work because delimiterChars was set to only allow individual characters as delimiters, not "two or more" per delimiting entry)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If things have to stay regexp, that's an option. Though delimiterChars
should probably be made sure to contain only single characters, with a warning or error when improper data is passed like delimiterChars: ['^', '.*\d+\s','lol']
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I didn't realise the can of worms I was opening here.
My expectation is that the delimiters are single characters, though I am not sure if there are use cases that require a sequence? @i-like-robots any thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could probably enforce them in the constructor, with warning when they are an improper format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an escapeForRegExp
function within the lib somewhere that could be pulled in to deal with escaping any special chars. I'm always of the opinion libraries like this should be relatively dumb but easily configurable so I'm not keen on enforcing strict rules like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll do this. Just one thing, is that we may find this causes inconsistent behaviour WRT to handleKeyDown, since the logic there won't deal with multi-character delimiters. I'll compromise and add a note in the readme.
lib/ReactTags.js
Outdated
// due to the context of the operation | ||
if (tags[i].length > 0) { | ||
// look to see if the tag is already known | ||
const matchIdx = this.suggestions.state.options.findIndex((suggestion) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than reaching into another component the suggestions are referenced at this.props.suggestions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I has been using the same logic as handleKeyDown, so we should probably have that updated too eventually, though tempted not to touch handleKeyDown in this change set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot, yes please keep this PR focussed and I'll move in with my sweeping brush after
lib/ReactTags.js
Outdated
handlePaste (e) { | ||
// allow over-ride, if there is a need | ||
if (this.props.onPaste) { | ||
return this.props.onPaste(e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency I think this option should be named handlePaste
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the general React pattern is to call handlers onSomething
though? (as this is the keyword that gets passed in through JSX's <ReactTags onPaste={...} />
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That had been by understanding too. Maybe the way forward would be to go for consistency as suggested and maybe replace and deprecate as a block in a future version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, but have opened issue #91
lib/ReactTags.js
Outdated
if (data && delimiterChars.length > 0) { | ||
// split the string based on the delimiterChars as a regex | ||
const tags = data.split(new RegExp(delimiterChars.join('|'), 'g')) | ||
for (let i = 0; i < tags.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depending on codestyle: since i
is only used for accessors, tags.forEach( tag => { ... })
would not look out of place here
spec/ReactTags.spec.js
Outdated
@@ -371,6 +375,17 @@ describe('React Tags', () => { | |||
|
|||
expect($$('.custom-tag').length).toEqual(2) | |||
}) | |||
|
|||
it('can receive tags through paste, respecting delimiters', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with the regexp update, you'll definitely want to put up some tests against active chars, like delimiterChars = [']','.*[']
. It's not too hard to hijack someone's React application with a user script, so making sure the code doesn't die when an intentionally bad regex is dropped in will be worth the effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added both validation in constructor and escape chars. Also updated test case.
@@ -56,6 +56,43 @@ class ReactTags extends React.Component { | |||
this.setState({ query }) | |||
} | |||
|
|||
handlePaste (e) { | |||
// allow over-ride, if there is a need | |||
if (this.props.handlePaste) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still advocate sticking with React conventions and calling this onPaste
, but take that as just an opinion of a fellow react component dev rather than anything more than that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See issue #91
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a check in the constructor. It will throw an error if any entry is greater than one character in length
// get the text data from the clipboard | ||
const data = e.clipboardData.getData('Text') | ||
|
||
if (data && delimiterChars.length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the expectation if delimiterChars.length is 0? Currently nothing is converted to tag. I am want to see if this is reasonable or whether we should treat it as one block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are no delimiterChars, then ReactTags can't do its job, I think? It needs a list to be able to chunk up input in the first place so if it does not have that list it can't convert string data into tags. Some options are to issue a warning ("no delimiter characters specified for onPaste handling by ReactTags") or to simply break out of the handler function before any code actually gets run.
@i-like-robots is the idea that delimiterChars and delimiters are mutually exclusive (e.g. if you need real letters, don't use numbers, and vice versa) or are they complementary? (in which case I might try to build a map that can tell which key is which keyboard event code, because the difference between those two is still ridiculous =D)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be okay:
else if (!delimiterChars || delimiterChars.length === 0) {
if (console) {
console.warn('no delimiterChars specified, so ignoring paste operation')
}
}
For the evolution of delimiterChars and delimiters, could that be another ticket, given there is probably a lot of subtle use cases? For example the enter key can have two different values, depending on whether it is on the alphanumeric or num-pad section of the keyboard. I don't have a num-pad to test with.
Should that unification work be done as part of issue #81?
Thank you again @ajmas - great testing too! |
Changes include - Not preventing multi-character delimiters - Indicating multi-chracter delimiters are only supported in paste operations, mainly because handleKeyDown will not deal with them - Changing the default delimiterChars to be consistent with the set of default delimiter keycodes
What do you think is missing before the PR can be accepted? In the future we can probably remove |
Note I have discovered a small issue with Chrome 60 on Android 7, which doesn't seem to have implement the KeyboardEvent.key attribute. I'll need to see if 'keyboardevent-key-polyfill' package could address this limitation. |
- Allow example to be access from seconds device, such as allowing to be tested from a mobile - Added 'demiterChars' to example
@ajmas Hey sorry, I've let this slide... a combination of being super busy and also dealing with illness. I'm very surprised to hear about the Will pick this up very soon. |
@i-like-robots no issues. I just wanted to be sure it wasn't something my side blocking things. Take of yourself and let me know when you are ready. |
I am working on a set of changes that leverages onChange, which may mean the onPaste approach is no longer necessary. I’ll make the PR when I get the chance and then we can discuss. |
given the merge conflict, is this PR still slated for review and landing, or has the codebase passed it by already? |
HI @Pomax - I've been leaving it open for reference. I still intend on dealing with delimiters pasted into the input in V6 (#106). I had a chat with one of the UX-ers at work before Christmas and we agreed that it would only make sense when I keep letting this slide... my to-do list is always so long! I'll try my best to get a V6 beta out this weekend. |
This keeps showing up in my work queues as an open PR that has me mentioned, is it possible to close this? You can link to it in an issue about this work and even when closed always be able to find it, but that would at least take it out of my github queues =) |
Changes to support pasting with delimiters, but currently missing a test case, since I am not sure how to write one that support the browser clipboard. I am a little new to Istanbul code coverage. Any suggestions would be appreciated.