JDK-8098032 : Add contentFilter property to TextInputControl
  • Type: Enhancement
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: 8
  • Priority: P3
  • Status: Resolved
  • Resolution: Duplicate
  • Submitted: 2013-06-03
  • Updated: 2015-06-12
  • Resolved: 2014-07-01
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8
8u40Resolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-December/004685.html

Mark Claassen proposed an API that seemed really simple and straightforward and allowed developers to add filtering capabilities very easily to TextInputControls. Here is the idea:

> Add the following sub-interface to the Content interface.  No methods are
> added to the Content interface.
>    interface Filter {
>         String filter(int index, String text, String currentContent);
>    }
> 
> Add the following to TextFieldContent and TextAreaContent:
>    private Content.Filter filter;
>    public void setContentFilter(Content.Filter filter) {
>         this.filter = filter;
>    }
> 
> Change the "insert" method of TextFieldContent and TextAreaContent to start
> with:
>    @Override public void insert(int index, String text, boolean
> notifyListeners) {
>         text = TextInputControl.filterInput(text, [boolean], [boolean]);
>         if (filter != null)
>              text = filter.filter(index, text, get());
> 
> Add a method in TextField:
>    public void setContentFilter(Content.Filter filter) {
>         ((TextFieldContent)getContent()).setContentFilter(filter);
>    }
> 
> Add a method in TextArea:
>    public void setContentFilter(Content.Filter filter) {
>         ((TextAreaContent)getContent()).setContentFilter(filter);
>    }
> 
> This would do a few things:
> 1) Use the current "TextInputControl.filterInput" mechanisms of TextField
> and TextArea (like to strip out special chars and newlines).
> 2) By specifying the Filter interface outside of TextField and TextArea, it
> allows the same filter to be used for TextField and TextArea
> 3) Makes it simple to do things like translate all input to upper case or
> limit the overall length
> 4) By not adding a method to Content directly, something like the
> InputMaskContent would not be forced to deal with the Filter interface at
> all.
> 
> This implementation would allow someone creating a filter to "filter" the
> text to have illegal characters.  I thought it was more validate this
> first, so that the implementer would only have to deal with proper input.
> However, TextInputControl.filterInput could be done both before and after
> the Filter.filter() call.
>         text = TextInputControl.filterInput(text, [boolean], [boolean]);
>         if (filter != null) {
>              text = filter.filter(index, text, get());
>              text = TextInputControl.filterInput(text, [boolean],
> [boolean]);
>         }
> 
> Another possible addition to this may be to allow the filter to return
> null, signifying an error input and causing the system to "beep".
Comments
To avoid having two different JIRA for one change, I'm closing this as a duplicate of RT-14000 and moving the discussion there. It's now clear that formatted text field functionality will need a content filter, so this change is just part of a bigger API.
01-07-2014

As Scott Palmer noted, the value can be read-write (and bindable) for the case when somebody want's formatted (and selectable) output by using an non-editable formatted text field.
11-06-2014

Attaching patch with some updates to contentFilter and a formatted text field (RT-14000) prototype.
11-06-2014

Sorry Martin, it was indeed a mistake.
05-06-2014

Simon, did you assign this to yourself on purpose or was this a mistake?
05-06-2014

Attaching diff for 8u20 repo (Richard's original code only)
04-06-2014

It looks like this got forgotten about? Too bad, I was just looking for this API.
09-04-2014

Hi Richard, just so you know, I open source the MaskTextField and FormattedTextField at https://github.com/jidesoft/jidefx-oss. Just in case you want to give it a try yourself, you can do it. Just comment out the overridden replaceText and replaceSelection method in the two classes and set your own contentFilter. I actually wrote a setContentFilter in MaskTextField (which is currently commented out). You can uncomment it. It should work to certain extend although not polished.
08-07-2013

API itself looks great. If I remember correctly, undo still has a minor issue as I emailed you last time.
01-07-2013

I'm just working through the ant-to-gradle transition and then will get back to this. Needs unit tests. How is the patch working for you right now? If things are in decent shape, we just need to add the unit tests and then we can add the feature in.
01-07-2013

Richard, Will you include this patch in the JDK 8 early releases soon? I plan to open source my MaskTextField and FormattedTextField implementations. Using this new API will make it much cleaner.
01-07-2013

I can't wait to have this in Lombard so that I can use it in the Scene Builder inspector :-)
19-06-2013

Updated to support using the filter when you set text or use binding. I'm not sure that the semantics around using the filter with binding is right, but could use with some verification from folks. I also made a bunch of bug fixes around undo / redo. I need to write unit tests to catch regressions when I do this, but it seems to be working rather well at the moment.
08-06-2013

Webrev of the patch with support for undo / redo.
07-06-2013

David Qiao found a bug where if you type illegal input, and then undo, it throws an exception. This is because the UndoManager in TextInputControlBehavior is remembering the "wrong" indexes for the change, because it is trying to do it externally. Really this logic needs to be moved into TextInputControl itself such that it can record the modifications itself, and expose undo() and redo() methods (and canUndo() and canRedo() or whatnot). So that will have to be added as part of this patch, or this patch won't work. Incidentally, this same error is reported when the developer just overrides the replaceText / replaceSelection methods.
07-06-2013

The patch should trivially apply to a recent OpenJFX workspace, if you have a chance to try this out let me know. I'd like to see more complex examples built on this API to see if it is sufficient or if I need to rework it, before I put it into mainline. Thanks!
07-06-2013

I've worked up an implementation and API for this which I think is actually capable of virtually all the extended use cases for filtering, not just the simple case, yet is pretty simple to use. Here are some worked examples, including auto-complete: TextField accept = new TextField(); accept.setPromptText("Always Accept"); accept.setContentFilter((change) -> { return change; }); TextField reject = new TextField(); reject.setPromptText("Always Reject"); reject.setContentFilter((change) -> { return null; }); TextField integer = new TextField(); integer.setPromptText("Integer"); integer.setContentFilter((change) -> { try { final String t = change.getProposedControlText(); if (t.length() > 0) Integer.valueOf(t); return change; } catch (NumberFormatException e) { return null; } }); TextField numeric = new TextField(); numeric.setPromptText("Numeric"); numeric.setContentFilter((change) -> { try { final String t = change.getProposedControlText(); if (t.length() > 0) Double.valueOf(t); return change; } catch (NumberFormatException e) { return null; } }); TextField allCaps = new TextField(); allCaps.setPromptText("All Caps"); allCaps.setContentFilter((change) -> { change.setText(change.getText().toUpperCase()); return change; }); TextField alpha = new TextField(); alpha.setPromptText("Alpha"); alpha.setContentFilter((change) -> { final String replacement = change.getText(); for (int i=0; i<replacement.length(); i++) { if (!Character.isAlphabetic(replacement.charAt(i))) { return null; } } return change; }); TextField alpha2 = new TextField(); alpha2.setPromptText("Stripping non-alpha"); alpha2.setContentFilter((change) -> { final String replacement = change.getText(); final StringBuffer buffer = new StringBuffer(); for (int i=0; i<replacement.length(); i++) { if (Character.isAlphabetic(replacement.charAt(i))) { buffer.append(replacement.charAt(i)); } } change.setText(buffer.toString()); return change; }); TextField auto = new TextField(); String[] dictionary = new String[] { "Awesome", "Wonderful", "Fantastic", "Dynamite", "Terrific" }; auto.setPromptText("Auto Complete"); auto.setContentFilter((change) -> { if (!change.isDeleted()) { final String newText = change.getProposedControlText(); for (String s : dictionary) { if (s.startsWith(newText)) { // Found a match! String additional = s.substring(newText.length()); String replacement = change.getText() + additional; change.setNewAnchor(change.getStart() + replacement.length()); change.setNewCaretPosition(change.getStart() + change.getText().length()); change.setText(replacement); return change; } } } return change; });
07-06-2013

Patch with the proposed API
07-06-2013

I don't know that this is the right method: String filter(int index, String text, String currentContent); This is useful when characters are *added* since you have the currentContent, the index into the current content, and the text that is being inserted. However when content is removed (or changed) there is no way to map these methods. This implies that the filter is for filtering on input only. This does allow for some use cases (such as making everything upper case like Mark suggested), however some other use cases (ensure there is always a minimum number of characters?) isn't possible. Perhaps by modifying the method so that instead of an index, it took a start & end index. If they are the same, then it was an insertion. If they are different, then the new text replaced some range in the currentContent.
06-06-2013

Also TextField and TextArea need to have full blown properties for contentFilter. Also, do we want to move this up to TextInputControl as opposed to defining it in TextField and TextArea?
06-06-2013

I think the setContentFilter methods on TextAreaContent and TextFieldContent can be private (or package) vs. public.
06-06-2013

One question is, should the filter start off as null and have an additional filter (newlines and such) always applied, or should it start out as non-null where the built in filter handles newlines (and such) and new filters also will have to either wrap the existing filter or add support for newlines (and such!) or not as necessary? The safest thing for an implementor would be to delegate in case we change the built-in behavior.
03-06-2013

I'd like to do this tweak if we can since it is very straightforward and was proposed by a developer using our stuff way back in 2012, and it would be beyond a shame to not act on it for 8 which is released in 2014.
03-06-2013