Asked  6 Months ago    Answers:  5   Viewed   29 times

In my experience, input type="text" onchange event usually occurs only after you leave (blur) the control.

Is there a way to force browser to trigger onchange every time textfield content changes? If not, what is the most elegant way to track this “manually”?

Using onkey* events is not reliable, since you can right-click the field and choose Paste, and this will change the field without any keyboard input.

Is setTimeout the only way?.. Ugly :-)

 Answers

84

Update:

See Another answer (2015).


Original 2009 Answer:

So, you want the onchange event to fire on keydown, blur, and paste? That's magic.

If you want to track changes as they type, use "onkeydown". If you need to trap paste operations with the mouse, use "onpaste" (IE, FF3) and "oninput" (FF, Opera, Chrome, Safari1).

1Broken for <textarea> on Safari. Use textInput instead

Tuesday, June 1, 2021
 
OMGKurtNilsen
answered 6 Months ago
22

You could use a compound primary key on Connections, using both columns (id_1, id_2) if you're having problems with uniqueness.

Saturday, May 29, 2021
 
ramdemon
answered 7 Months ago
94

Use setCustomValidity:

document.addEventListener("DOMContentLoaded", function() {
    var elements = document.getElementsByTagName("INPUT");
    for (var i = 0; i < elements.length; i++) {
        elements[i].oninvalid = function(e) {
            e.target.setCustomValidity("");
            if (!e.target.validity.valid) {
                e.target.setCustomValidity("This field cannot be left blank");
            }
        };
        elements[i].oninput = function(e) {
            e.target.setCustomValidity("");
        };
    }
})

I changed to vanilla JavaScript from Mootools as suggested by @itpastorn in the comments, but you should be able to work out the Mootools equivalent if necessary.

Edit

I've updated the code here as setCustomValidity works slightly differently to what I understood when I originally answered. If setCustomValidity is set to anything other than the empty string it will cause the field to be considered invalid; therefore you must clear it before testing validity, you can't just set it and forget.

Further edit

As pointed out in @thomasvdb's comment below, you need to clear the custom validity in some event outside of invalid otherwise there may be an extra pass through the oninvalid handler to clear it.

Tuesday, June 1, 2021
 
dmp
answered 6 Months ago
dmp
84

The hack is to use type="tel" instead of type="number".

This solves the 2 main issues:

  1. It pulls up a number keypad on mobile devices
  2. It validates (and is not empty) with numbers or non-numbers as input.

Please see this JSFiddle: http://jsfiddle.net/timrpeterson/CZZEX/6/

Friday, June 25, 2021
 
PLPeeters
answered 6 Months ago
83

If you can target NET Framework 3.5 and above, you don't need to scan the document on every change: Just subscribe to the TextChanged event and use the TextChangedEventArgs.Changes property to get a list of changes.

Whenever you receive a TextChanged event, iterate through the Changes collection and construct a TextRange from the Offset, AddedLength, and RemovedLength. Then expand this TextRange as appropriate for recalculating formatting, then do the formatting calculation and update as a separate step (in a Dispatcher.BeginInvoke callback) so you don't end up having recursive TextChanged events.

richTextBox.TextChanged += (obj, e)
{
  var document = richTextBox.Document;
  var length = document.ContentStart.GetOffsetToPosition(document.ContentEnd);
  int totalAdd = 0;
  int totalRemove = 0;
  foreach(var change in e.Changes)
  {
    var expandBy = Math.Max(totalAdd,totalRemove);

    var startIndex = change.Offset - expandBy;
    var endIndex = changed.Offset + expandBy + Math.Max(totalAdd, totalRemove);

    startIndex = Math.Max(startIndex, 0);
    endIndex = Math.Min(endIndex, length);

    var startPointer = document.ContentStart.GetPositionAtOffset(startIndex);
    var endPointer = startPointer.GetPositionAtOffset(endIndex - startIndex);

    var range = new TextRange(startPointer, endPointer);
    Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
    {
      DoParsingAndFormatting(ExpandRangeToUnitOfParsing(range));
    });
    totalAdd += change.AddedLength;
    totalRemove += change.RemovedLength;
  }
};

If you want to find the paragraph where a change begins or ends, you can use range.Start.Paragraph and range.End.Paragraph.

Also, for many situations it will be helpful to store a copy of all the text in the document separately from the FlowDocument itself. Then as you apply changes to that document you can update the formatting as you go without having to reread the document. Note that the text should not be stored in a single large array, but rather snipped into small pieces (perhaps around 1000 characters) and accessed through a tree that organizes the pieces by index. The reason is that inserting a character at the beginning of a huge array is very expensive.

Friday, September 17, 2021
 
Muhamed Huseinbašić
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share