4

I've built a page where you can filter results by typing into an input box.

Basic mechanics are:

  • Start typing, input event is fired, elements without matching text begin hiding
  • If input becomes empty (or if you click a reset button), all elements are shown again

I have noticed a problem, though, when highlighting text. Say I type "apple" into the input. Then I highlight it, and type "orange."

If an element exists on the page containing "orange," but it was already hidden because I filtered for "apple," it does not show up. I have gathered this is because the input never truly empties; rather, I simply replace "apple" with the "o" from orange before continuing with "r-a-n-g-e." This means I get a subset of "apple" results that contain "orange," as if I had typed "apple orange."

What I really want to do is clear my input on the keypress for the "o" in "orange" before hiding nonmatching elements, so I'm effectively searching the whole page for "orange."

What I've tried so far

1: Set input value to '' on select event:

$('.myinput').on('select', function(){
  $(this).val('');
});

This doesn't work because it just deletes my highlighted text, which is unexpected. I only want to reset the input on the keypress following the highlight.

2: Include an if statement in my input event that checks if there is a selection within the input:

$('.myinput').on('input', function(){
  var highlightedText = window.getSelection();

  if($(highlightedText).parent('.myinput')) {
    //reset my input
  }
});

This doesn't work because it seems to fire on every keypress, regardless of if there is any actual selection. (Are user inputs always treated as selected?)

3: Add a select event listener to the input element, and set a variable to true if there's a selection. Then, in my input event, check if the variable is true on keypress.

$(function(){
  var highlightedText = false;

  $('.myinput').on('input', function(){
    if(highlightedText = true) {
      //reset my input
    }

    //do stuff

    highlightedText = false;
  });

  $('.myinput').on('select', function(){
    highlightedText = true;
  });
});

I really thought this one would work because a basic console log in the select function only fires when I want it to – when text in the input is highlighted, but not when other text is highlighted and not when text is entered into the input. But alas, when I change that to a variable toggle, it seems to fire on every keypress again.

So the question is: How can I fire a function on input only if text in my input is highlighted?

I have found this question that suggests binding to the mouseup event, but it seems like overkill to check every single click when I'm only worried about a pretty particular situation. Also, that solution relies on window.getSelection(), which so far isn't working for me.

I've also found another question that suggests to use window.selectionEnd instead of window.getSelection() since I'm working with a text input. I tried incorporating that into option 2 above, but it also seems to fire on every keypress, rather than on highlight.

cjl750
  • 4,380
  • 3
  • 15
  • 27
  • Maybe instead of trying to capture the mouse highlight, you could revisit how you populate the subset. It seems like the more natural way to me. When changing the input from apple to o the subset should update as expected. Here is another idea. https://stackoverflow.com/questions/686995/catch-paste-input – arodjabel Jul 28 '17 at 19:39
  • 1
    why not save the value of the input in a variable. and check if the input has changed. this way you can check if the old value is a substring of the newe value. – Bee157 Jul 28 '17 at 19:41

2 Answers2

4

This answer is not about text selection at all.

But still solve your problem to refilter text when highlighted text is being replaced with new input.

var input = document.getElementById('ok');
var character = document.getElementById('char');
var previousCount = 0;
var currentCount = 0;

input.addEventListener('input', function(){
    currentCount = this.value.length;
    if (currentCount <= previousCount){
        /*
            This will detect if you replace the highlighted text into new text.
            You can redo the filter here.
        */
        console.log('Highlighted text replaced with: ' + this.value);
    }
    previousCount = currentCount;
    char.innerHTML = this.value;
});
<input type="text" id="ok">

<div id="char"></div>
yqlim
  • 6,898
  • 3
  • 19
  • 43
  • Thanks for the answer. I like this approach in general; however your function seems to fire twice every time. That means that in my case, where the function to use inside the if statement is to reset the input value back to empty, the first letter of my new input after highlighting gets chopped off. For example, if I type "test," then highlight and type "new," I'm left with "ew." Got an edit to fix that problem? – cjl750 Jul 28 '17 at 20:46
  • @cjl750 I edited the snippet a bit. Tested by replacing "test" with "new" and show the new string on replace. It's also tested to only fire once. If it still works strangely in your original code base then there might be another problem in your original code base. – yqlim Jul 29 '17 at 06:54
  • Thank you! After reading others' comments about changing how I do the filtering, I may not end up actually using this approach after all, but it does give me a nice option if I decide to stick with my current filtering method. And it's nice and simple. Appreciate your time. – cjl750 Jul 30 '17 at 05:24
0

I'll agree with others that you will save yourself some trouble if you change your filtering strategy - I'd say you should filter all content from scratch at each keypress, as opposed to filtering successively the content that remains.

Anyway, to solve your immediate problem, I think you can just get the selection and see if it is empty. You can modify your second attempt:

$('.myinput').on('input', function(){
  // get the string representation of the selection
  var highlightedText = window.getSelection().toString();

  if(highlightedText.length) {
    //reset my input
  }
});

EDIT

As this solution seems to have various problems, I can suggest another, along the lines of the comment from @Bee157. You can save the old search string and check if the new one has the old as a substring (and if not, reset the display).

var oldSearch = '';
$('.myinput').on('input', function(){
  var newSearch = $('.myinput').val();
  if (newSearch.indexOf(oldSearch) == -1) {
    // reset the display
    console.log('RESET');
  }
  oldSearch = newSearch;
  // filter the results...
});    

This approach has the added benefit that old results will reappear when you backspace. I tried it in your codepen, and I was able to log 'RESET' at all the appropriate moments.

arbuthnott
  • 3,819
  • 2
  • 8
  • 21
  • I have considered filtering from scratch each time, but I think that would lose me the ability to do more fine-grained subset filtering by clicking tags (shown in the pen, not mentioned in this question), which I think could be helpful in my actual use case because I will have a pretty long list of things to filter. But I'll consider it, thanks. As for your solution, it never seems to fire. Also, you've got a typo in "highlighted" in the if statement; there's an extra "t" in there. – cjl750 Jul 28 '17 at 20:30
  • Hmm, sorry it didn't work, I'll follow up in a bit. One note - if you filter from scratch each time, it solves this current problem (of unfiltering when input changes). – arbuthnott Jul 28 '17 at 21:00
  • I think I will probably end up going the route of changing the filtering strategy, so if you would rather revise your answer to that effect, I would accept that answer. Or if you can find a way to do what I initially asked, I would accept that as well. – cjl750 Jul 28 '17 at 22:11
  • Appreciate the approach; however, unfortunately, replacing the console log with the actual function I want to use (`$('.myinput').val('');`) results in a bug where I just get stuck repeatedly clearing my own input, which I can only fix by hitting the reset button in the pen. Maybe I'm just not implementing it how you were imagining. If you are able to fork my pen and make it work, I'd gladly upvote this. Thanks for your time either way! – cjl750 Jul 30 '17 at 05:31