Skip to content

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

Closed

Conversation

ajmas
Copy link
Contributor

@ajmas ajmas commented Aug 14, 2017

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.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage decreased (-9.8%) to 89.56% when pulling d364403 on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage decreased (-1.6%) to 97.802% when pulling 5fae01c on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

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'))
Copy link
Owner

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('') + ']'))

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'll make the change

Copy link
Contributor

@Pomax Pomax Aug 14, 2017

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).

Copy link
Contributor Author

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.

Copy link
Contributor

@Pomax Pomax Aug 14, 2017

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)

Copy link
Contributor

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'].

Copy link
Contributor Author

@ajmas ajmas Aug 14, 2017

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?

Copy link
Contributor

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.

Copy link
Owner

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.

Copy link
Contributor Author

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) => {
Copy link
Owner

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

Copy link
Contributor Author

@ajmas ajmas Aug 14, 2017

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?

Copy link
Owner

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)
Copy link
Owner

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

Copy link
Contributor

@Pomax Pomax Aug 14, 2017

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={...} />

Copy link
Contributor Author

@ajmas ajmas Aug 14, 2017

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?

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, 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++) {
Copy link
Contributor

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

@@ -371,6 +375,17 @@ describe('React Tags', () => {

expect($$('.custom-tag').length).toEqual(2)
})

it('can receive tags through paste, respecting delimiters', () => {
Copy link
Contributor

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.

Copy link
Contributor Author

@ajmas ajmas Aug 14, 2017

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.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.06%) to 99.451% when pulling 89b68b3 on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@@ -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) {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See issue #91

@ajmas
Copy link
Contributor Author

ajmas commented Aug 14, 2017

@Pomax for the user specified event naming conventions, I have opened issue #91

Copy link
Contributor Author

@ajmas ajmas left a 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) {
Copy link
Contributor Author

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?

Copy link
Contributor

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)

Copy link
Contributor Author

@ajmas ajmas Aug 15, 2017

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?

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.08%) to 99.468% when pulling 88a33b2 on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@i-like-robots
Copy link
Owner

Thank you again @ajmas - great testing too!

@i-like-robots i-like-robots added this to the 5.5.0 milestone Aug 15, 2017
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
@coveralls
Copy link

coveralls commented Aug 15, 2017

Coverage Status

Coverage increased (+0.07%) to 99.457% when pulling 56b2d10 on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@ajmas
Copy link
Contributor Author

ajmas commented Sep 7, 2017

What do you think is missing before the PR can be accepted? In the future we can probably remove delimiters in favour of delimiterChars, though that will probably require a major version change?

@ajmas
Copy link
Contributor Author

ajmas commented Sep 8, 2017

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
@coveralls
Copy link

coveralls commented Sep 8, 2017

Coverage Status

Coverage increased (+0.08%) to 99.465% when pulling 4a6962c on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@coveralls
Copy link

coveralls commented Sep 8, 2017

Coverage Status

Coverage increased (+0.08%) to 99.465% when pulling 52cad74 on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@i-like-robots
Copy link
Owner

@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 KeyboardEvent.key issue... I think it can be worked around but irritating nonetheless.

Will pick this up very soon.

@coveralls
Copy link

coveralls commented Sep 8, 2017

Coverage Status

Coverage increased (+0.08%) to 99.465% when pulling 52cad74 on ajmas:issue-84-pasting-and-delimeters into 837e480 on i-like-robots:master.

@ajmas
Copy link
Contributor Author

ajmas commented Sep 9, 2017

@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.

@ajmas
Copy link
Contributor Author

ajmas commented Sep 25, 2017

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.

@i-like-robots i-like-robots modified the milestones: 5.5.0, 6.0.0 Nov 15, 2017
@Pomax
Copy link
Contributor

Pomax commented Jan 18, 2018

given the merge conflict, is this PR still slated for review and landing, or has the codebase passed it by already?

@i-like-robots
Copy link
Owner

i-like-robots commented Jan 19, 2018

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 allowNew was true.

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.

@Pomax
Copy link
Contributor

Pomax commented May 31, 2018

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 =)

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.

4 participants