Flutter TextInputFormatter does not do what is intended - dart

I have tried to extend the Flutter's TextInputFormatter to add thousand comma separator on the fly as the user types.
import 'package:flutter/services.dart';
class ThousandSeparatorTextInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue) {
return newValue.copyWith(
text: _addThousandsSeparator(newValue.text)
);
}
// This adds thousand comma separator
String _addThousandsSeparator(String value) {
RegExp reg = RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))');
Function determine = (Match match) => '${match[1]},';
return value.replaceAllMapped(reg, determine);
}
}
The problem is when used in the TextField the comma separator appears at the right position the first time, as the user keep typing the separator appears after every digit from the last three.
When the _addThousandSeparator function is used on its own, i.e. on some values, it does work. What am I doing wrong?

Related

What's the equivalent to this[x] in Dart?

For instance, in Javascript I can do something like:
class Foo {
x = 'baz';
bar() {
const someVar = 'x';
console.log(this[someVar]);
// Output: 'baz';
}
}
Hopefully that's relatively clear - it boils down to accessing a member variable by another variable's contents. How is this achieved in Dart?
This is not trivial in Dart. Dart doesn't have a syntax to access class properties with [].
There are a couple of approaches though:
Mirrors:
https://api.dartlang.org/stable/2.6.1/dart-mirrors/dart-mirrors-library.html
Basically you have access to everything and offers the biggest freedom. You can check what properties a class has, access them via names and so on. Big disadvantage is that the generated JS (if targeting web) will be huge. Flutter doesn't support it at all.
Reflectable
To deal with the large generated JS, you can use package:reflectable. Never tried it with Flutter. It's a bit more to set up and start using bit it works.
Dart only solution 1
You can overload [] operator on a class:
class Foo {
final _backing = <String, String>{
'foo': 'bar'
};
operator [](String val) {
return _backing[val];
}
}
void main() {
final inst = Foo();
print(inst['foo']);
}
Dart only solution 2
Just use a map :) Well sort of... If you are dealing with complex types and you want to add some extra functionality to your map, you can do something like this:
import 'dart:collection';
class StringMap extends Object with MapMixin<String, String> {
final _backing = <String, String>{};
#override
String operator [](Object key) {
return _backing[key];
}
#override
void operator []=(String key, String value) {
_backing[key] = value;
}
#override
void clear() {
_backing.clear();
}
#override
Iterable<String> get keys => _backing.keys;
#override
String remove(Object key) {
return _backing.remove(key);
}
}

Allowed values for a property

Is there a way in dart to only allow a number of values for a property?
class Kana {
final String kana;
final String romaji;
final type = 'hiragana' | 'katakana';
Kana({this.kana, this.romaji, this.type});
}
I'd like to make those values the only allowed values, preventing me to put a wrong value when initializing a Kana class.
You could use enums.
void main() {
var kana = Kana('x', 'y', Style.hiragana);
print(kana);
}
class Kana {
final String kana;
final String romaji;
final Style style;
Kana(this.kana, this.romaji, this.style);
}
enum Style { hiragana, katakana }
You can use assert to check the value of your type.
Kana(this.kana, this.romaji, this.type)
: assert(type == 'hiragana' || type == 'katakana');

How to bind property using a formatter

I have a label and a double property. I'd like to output the value with the postfix "$", i. e. "200.50 $". How can I do this with JavaFX? I thought about using binding like this:
#FXML Label label;
DoubleProperty value;
...
Bindings.bindBidirectional( label.textProperty(), valueProperty(), NumberFormat.getInstance());
But haven't found a way to append the " $" to the text.
Thank you very much for the help!
Something like this should work.
Bindings.format("%.2f $", myDoubleProperty.getValue());
Now you can bind it
label.textProperty.bind(Bindings.format("%.2f $", myDoubleProperty.getValue());
If you want to use the NumberFormat instance, just format the value of the myDoubleProperty with it.
NumberFormat formatter = NumberFormat.getInstance();
formatter.setSomething();
formatter.format(myDoubleProperty.getValue());
Now add it to our Bindings.format.
Edit:
Included inputs of ItachiUchiha.
Thanks to ItachiUchiha and NDY I got on the right track to the answer. I'll have to use a StringConverter like this:
public class MoneyStringConverter extends StringConverter<Number> {
String postFix = " $";
NumberFormat formatter = numberFormatInteger;
#Override
public String toString(Number value) {
return formatter.format( value) + postFix;
}
#Override
public Number fromString(String text) {
try {
// we don't have to check for the symbol because the NumberFormat is lenient, ie 123abc would result in the value 123
return formatter.parse( text);
} catch( ParseException e) {
throw new RuntimeException( e);
}
}
}
And then I can use it in the binding:
Bindings.bindBidirectional( label.textProperty(), valueProperty(), new MoneyStringConverter());
You can just concat it to the value using
label.textProperty().bind(Bindings.concat(value).concat("$"));

Displaying Many Jira issues using Issue Navigator

My question is similar to "Displaying Jira issues using Issue Navigator" But my concern is that sometimes my list of issues is quite long and providing the user with a link to the Issue Navigator only works if my link is shorter than the max length of URLs.
Is there another way? Cookies? POST data? Perhaps programmatically creating and sharing a filter on the fly, and returning a link to the Issue Navigator that uses this filter? (But at some point I'd want to delete these filters so I don't have so many lying around.)
I do have the JQL query for getting the same list of issues, but it takes a very (very) long time to run and my code has already done the work of finding out what the result is -- I don't want the user to wait twice for the same thing (once while I'm generating my snazzy graphical servlet view and a second time when they want to see the same results in the Issue Navigator).
The easiest way to implement this is to ensure that the list of issues is cached somewhere within one of your application components. Each separate list of issues should be identified by its own unique ID (the magicKey below) that you define yourself.
You would then write your own JQL function that looks up your pre-calculated list of issues by that magic key, which then converts the list of issues into the format that the Issue Navigator requires.
The buildUriEncodedJqlQuery() method can be used to create such an example JQL query dynamically. For example, if the magic key is 1234, it would yield this JQL query: issue in myJqlFunction(1234).
You'd then feed the user a URL that looks like this:
String url = "/secure/IssueNavigator.jspa?mode=hide&reset=true&jqlQuery=" + MyJqlFunction.buildUriEncodedJqlQuery(magicKey);
The end result is that the user will be placed in the Issue Navigator looking at exactly the list of issues your code has provided.
This code also specifically prevents the user from saving your JQL function as part of a saved filter, since it's assumed that the issue cache in your application will not be permanent. If that's not correct, you will want to empty out the sanitiseOperand part.
In atlassian-plugins.xml:
<jql-function key="myJqlFunction" name="My JQL Function"
class="com.mycompany.MyJqlFunction">
</jql-function>
JQL function:
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.JiraDataType;
import com.atlassian.jira.JiraDataTypes;
import com.atlassian.jira.jql.operand.QueryLiteral;
import com.atlassian.jira.jql.query.QueryCreationContext;
import com.atlassian.jira.plugin.jql.function.ClauseSanitisingJqlFunction;
import com.atlassian.jira.plugin.jql.function.JqlFunction;
import com.atlassian.jira.plugin.jql.function.JqlFunctionModuleDescriptor;
import com.atlassian.jira.util.MessageSet;
import com.atlassian.jira.util.MessageSetImpl;
import com.atlassian.jira.util.NotNull;
import com.atlassian.query.clause.TerminalClause;
import com.atlassian.query.operand.FunctionOperand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MyJqlFunction implements JqlFunction, ClauseSanitisingJqlFunction
{
private static final String JQL_FUNCTION_NAME = "myJqlFunctionName";
private static final int JQL_FUNCTION_MIN_ARG_COUNT = 1;
private static final int JQL_FUNCTION_MAGIC_KEY_INDEX = 0;
public MyJqlFunction()
{
// inject your app's other components here
}
#Override
public void init(#NotNull JqlFunctionModuleDescriptor moduleDescriptor)
{
}
#Override
public JiraDataType getDataType()
{
return JiraDataTypes.ISSUE;
}
#Override
public String getFunctionName()
{
return JQL_FUNCTION_NAME;
}
#Override
public int getMinimumNumberOfExpectedArguments()
{
return JQL_FUNCTION_MIN_ARG_COUNT;
}
#Override
public boolean isList()
{
return true;
}
/**
* This function generates a URL-escaped JQL query that corresponds to the supplied magic key.
*
* #param magicKey
* #return
*/
public static String buildUriEncodedJqlQuery(String magicKey)
{
return "issue%20in%20" + JQL_FUNCTION_NAME + "(%22"
+ magicKey + "%22%)";
}
#Override
public List<QueryLiteral> getValues(#NotNull QueryCreationContext queryCreationContext,
#NotNull FunctionOperand operand,
#NotNull TerminalClause terminalClause)
{
User searchUser = queryCreationContext.getUser();
MessageSet messages = new MessageSetImpl();
List<QueryLiteral> values = internalGetValues(searchUser,
operand,
terminalClause,
messages,
!queryCreationContext.isSecurityOverriden());
return values;
}
private List<QueryLiteral> internalGetValues(#NotNull User searchUser,
#NotNull FunctionOperand operand,
#NotNull TerminalClause terminalClause,
#NotNull MessageSet messages,
#NotNull Boolean checkSecurity)
{
List<QueryLiteral> result = new ArrayList<QueryLiteral>();
if (searchUser==null)
{
// handle anon user
}
List<String> args = operand.getArgs();
if (wasSanitised(args))
{
messages.addErrorMessage("this function can't be used as part of a saved filter etc");
return result;
}
if (args.size() < getMinimumNumberOfExpectedArguments())
{
messages.addErrorMessage("too few arguments, etc");
return result;
}
final String magicKey = args.get(JQL_FUNCTION_MAGIC_KEY_INDEX);
// You need to implement this part yourself! This is where you use the supplied
// magicKey to fetch a list of issues from your own internal data source.
List<String> myIssueKeys = myCache.get(magicKey);
for (String id : myIssueKeys)
{
result.add(new QueryLiteral(operand, id));
}
return result;
}
#Override
public MessageSet validate(User searcher, #NotNull FunctionOperand operand, #NotNull TerminalClause terminalClause)
{
MessageSet messages = new MessageSetImpl();
internalGetValues(searcher, operand, terminalClause, messages, true);
return messages;
}
#Override
public FunctionOperand sanitiseOperand(User paramUser, #NotNull FunctionOperand operand)
{
// prevent the user from saving this as a filter, since the results are presumed to be dynamic
return new FunctionOperand(operand.getName(), Arrays.asList(""));
}
private boolean wasSanitised(List<String> args)
{
return (args.size() == 0 || args.get(JQL_FUNCTION_MAGIC_KEY_INDEX).isEmpty());
}
}

set text(number) to Edit Field through its setchangelistener

Hi I want to accept number in edit field up to two decimal.So I am setting listener to it Then I
am checking whether number is two decimal or more and if it is more than two decimal then i am
truncating the number and again trying to set the truncated number.But it showing 104 error at this place interestRate.setText(text).My code is
interestRate=new EditField();
interestRate.setChangeListener(new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
String text=interestRate.getText().toString();
code here--
interestRate.setText(text);
}
};
So my question is whether text can be set from its listener or not
Looks like you just need to use some condition check in order not to get in an infinite loop:
interestRate=new EditField();
interestRate.setChangeListener(new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
String text = interestRate.getText().toString();
// code here to create a truncated text
if (!truncated.equals(text)) {
// next time we will not get here
// because truncated will be equal to text
interestRate.setText(text);
}
}
});

Resources