How to retrieve the client (browser) timezone in Vaadin Flow? - timezone

I need to determine the browser timezone. I tried to follow this post but does not work (Vaadin 20). Here is my code:
ZoneId myZoneId;
...
UI.getCurrent().getPage().retrieveExtendedClientDetails(extendedClientDetails -> {
myZoneId = ZoneId.of(extendedClientDetails.getTimeZoneId());
});
// here myZoneId has value null.
So I tried to do it myself, initially simply displaying it.
UI.getCurrent().getPage()
.executeJs("return Intl.DateTimeFormat().resolvedOptions().timeZone;")
.then(value -> Notification.show(value.asString()));
It works and I read "Europe/Rome", but its value does not seem something that I can map to a ZoneId in Java.
I could explore a little more the Javascript zone object but I also was unable to find where my code actually went to debug it with chrome debugger (there is no mention in Vaadin doc where the code goes).
I could work on the returned value and try to interpret it but I would like to avoid reinventing the wheel.
Do anybody has any code that works?

retrieveExtendedClientDetails is an asynchronous call, thus the result is available inside the callback and not right after firing the callback (on the place you commented).
Also, UI.getCurrent() could return null if called too soon, f.e. in a constructor.
retrieveExtendedClientDetails makes a client roundtrip the first time it is run (docs).
If already obtained, the callback is called directly. Otherwise, a client-side roundtrip will be carried out.
That means you could try to run it when initialising the UI and later you should have it ready right away after firing.
#SpringComponent
public class MyVaadinServiceInitListener implements VaadinServiceInitListener {
#Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addUIInitListener(uiEvent -> {
var ui = event.getUI();
ui.getPage().retrieveExtendedClientDetails(detail -> {});
});
}
}
Another solution is to read the zone inside the callback.

Europe/Rome is fine for Java.
You can simply call:
ZoneId.of("Europe/Rome");

Thanks to everybody!
Finally the main problem was not retrieving the time zone from the client but matching it against the list of available time zones returned by ZoneId.getAvailableZoneIds(). The fact is that there were multiple timezone for CEST (Central Europe) as well as any other country. I counted 7 for Brazil that only has 5 time zones!
Honestly I am little ignorant about time zones (world would be quite simpler if we could use only GMT and adjust our schedules accordingly). The solution was to digest the timezone list using the name. I do not know if this is right or wrong, but it is simple and works, despite the fact that if I choose CEST I do not know if it will be Rome, Berlin or whatever else. Does it matter?
Here is the complete code if somebody should one day need a
package net.cbsolution.scc.vaadin.comps;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.renderer.TemplateRenderer;
import java.time.ZoneId;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;
public class TimeZoneSelector extends ComboBox<ZoneId> {
private Locale locale = UI.getCurrent().getLocale();
public TimeZoneSelector() {
// Digest the timezone list
final Map<String, ZoneId> cleanMap = new HashMap<>();
ZoneId.getAvailableZoneIds().stream().map(z -> ZoneId.of(z)).forEach(z -> cleanMap.put(print(z), z));
cleanMap.values().stream().collect(Collectors.toList());
setWidth("20em");
setDataProvider(new ListDataProvider<>(cleanMap.values().stream().collect(Collectors.toList())));
setRenderer(TemplateRenderer.<ZoneId>of("<span>[[item.print]]</span>")
.withProperty("print", tz -> print(tz))
);
setItemLabelGenerator((ItemLabelGenerator<ZoneId>) item -> print(item));
}
protected String print(ZoneId zoneId) {
return zoneId.getDisplayName(TextStyle.FULL, UI.getCurrent().getLocale());
}
#Override
protected void onAttach(AttachEvent attachEvent) {
UI.getCurrent().getPage().retrieveExtendedClientDetails(details -> {
TimeZone uiTimeZone = TimeZone.getTimeZone(details.getTimeZoneId());
setValue(ZoneId.of(uiTimeZone.getID()));
});
}
}

try this
//Read the timezone and store in into the session class
if(UI.getCurrent() != null) {
UI.getCurrent().getPage().retrieveExtendedClientDetails(details -> {
// Set the time zone
TimeZone uiTimeZone = TimeZone.getTimeZone(details.getTimeZoneId());
});
};

Related

How to use scoped_model with Shared Preferences?

I am add state management to my chat app using scoped_model.
My question is how use scoped_model with shared preferences. So on app startup Model state is fill with values from shared preferences. For example stored username will be retrieve from shared preferences and then store in UserModel username state.
I have look but no find tutorial how to do.
I have find this sample from FlutterCinematic main.dart:
void main() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
runApp(ScopedModel<AppModel>(
model: AppModel(sharedPreferences), child: CinematicApp()));
}
Is this best way to do?
What is best way?
Thanks!
I know it's been a while since this question was asked. But I am still gonna' answer it anyway.
I had the same problem when I was developing an android app using shared preferences and scoped model. My idea was to initialize the values in scopedmodel when the app starts. And the way I did it was to call the function within the constructor of the model.
Let's say my scoped model is:
class AppModel extends Model {
String variable1;
String variable2;
int variable3;
//Getter functions go here
//Setter function (though not a pure setter but a function that sets the values)
void setValues() {
SharedPreferences.getInstance().then(
(prefs) {
variable1 = prefs.getString('var1');
variable2 = prefs.getString('var2');
variable3 = prefs.getInt('var3');
));
}
AppModel(
setValues();
);
}
Now when you initialize the model before the Material app, its values will get initialized.

Select-all shortcut (Ctrl-A) in Vaadin Table?

I have the following snippet in my UI Builder code:
table.addShortcutListener(new ShortcutListener("Select all", null, KeyCode.A, ModifierKey.CTRL) {
#Override
public void handleAction(Object sender, Object target) {
AbstractSelect t = (AbstractSelect) target;
if (t.isMultiSelect()) {
t.setValue(t.getItemIds());
}
}
});
return table;
This allows to press Ctrl+A to select all items in a table. This usually works the first time I load a view until I make one of the tables invisible (setVisible(false)). After making the tables visible again, it no longer works (not even on reloading the page) and I get the following console output whenever I press Ctrl+A:
WARNING: Ignoring action for disabled connector c.b.a.web.ui.builder.table.TranslatedHeaderTable
Nov 03, 2014 11:15:00 AM com.vaadin.event.ConnectorActionManager handleAction
What is wrong with my code? How would I achieve my goal?
I'd suggest this modification, works fine for me (i suppose comp is the Vaadin Table)
comp.addShortcutListener(new ShortcutListener("Select all", null, KeyCode.A, ModifierKey.CTRL) {
private static final long serialVersionUID = 1L;
#Override
public void handleAction(Object sender, Object target) {
if (comp.isMultiSelect()) {
comp.setValue(comp.getItemIds());
}
}
});
The problem might be, while testing locally, the failure of serialization of some component, so a static reference to comp (you'll need to make it final) and a defaut UID should do the trick.
tested multiple times and the error never occured.
Cheers.
EDIT
I understood that the problem occured when making invisible and then visible the table.
It came to my mind just now that you could have tried CTRL+A on an invisible Table: if this is the case so it's correct, when a Component is made invisible every listener is put in "Standby" until you make it visible again. So for me:
setVisible(false);
setVisible(true);
"CTRL+A"
works, while
setVisible(false);
"CTRL+A"
gives me
nov 03, 2014 2:19:34 PM com.vaadin.event.ConnectorActionManager handleAction
WARNING: Ignoring action for disabled connector com.vaadin.ui.Table
It's meant to be this way, nothing wrong in your code, you have to change your functionality and do not have a "CTRL+A" on an invisible Table (which seems a bad thing to do imho). On the other hand you could overwrite the setVisible method but I discourage it.
Cheers.
It seems there are some problems with the Table implementation of the Action.Notifier interface. In this Vaadin Forum Post, the Vaadin Devs suggest to add the ShortcutListener not to the Table itself but to a Panel that the Table is enclosed within.
My new implementation:
private void addCtrlAHandler(final AbstractSelect table) {
Panel panelEnclosingTable = table.findAncestor(Panel.class);
Preconditions.checkArgument(panelEnclosingTable != null, "Table is not enclosed in a panel; cannot add shortcut handlers");
panelEnclosingTable.addShortcutListener(new ShortcutListener("Select all", null, KeyCode.A, ModifierKey.CTRL) {
#Override
public void handleAction(Object sender, Object target) {
if (table.isMultiSelect()) {
table.setValue(table.getItemIds());
}
}
});
}
With this workaround, I get the expected behavior.

How to force log4j2 rolling file appender to roll over?

To my best knowledge, RollingFileAppender in log4j2 will not roll over at the specified time (let's say - at the end of an hour), but at the first log event that arrives after the time threshold has been exceeded.
Is there a way to trigger an event, that on one hand will cause the file to roll over, and on another - will not append to the log (or will append something trivial, like an empty string)?
No there isn't any (built-in) way to do this. There are no background threads monitoring rollover time.
You could create a log4j2 plugin that implements org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy (See the built-in TimeBasedTriggeringPolicy and SizeBasedTriggeringPolicy classes for sample code.)
If you configure your custom triggering policy, log4j2 will check for every log event whether it should trigger a rollover (so take care when implementing the isTriggeringEvent method to avoid impacting performance). Note that for your custom plugin to be picked up, you need to specify the package of your class in the packages attribute of the Configuration element of your log4j2.xml file.
Finally, if this works well for you and you think your solution may be useful to others too, consider contributing your custom triggering policy back to the log4j2 code base.
Following Remko's idea, I wrote the following code, and it's working.
package com.stony;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.rolling.*;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
#Plugin(name = "ForceTriggerPolicy", category = "Core")
public class ForceTriggerPolicy implements TriggeringPolicy {
private static boolean isRolling;
#Override
public void initialize(RollingFileManager arg0) {
setRolling(false);
}
#Override
public boolean isTriggeringEvent(LogEvent arg0) {
return isRolling();
}
public static boolean isRolling() {
return isRolling;
}
public static void setRolling(boolean _isRolling) {
isRolling = _isRolling;
}
#PluginFactory
public static ForceTriggerPolicy createPolicy(){
return new ForceTriggerPolicy();
}
}
If you have access to the Object RollingFileAppender you could do something like:
rollingFileAppender.getManager().rollover();
Here you can see the manager class:
https://github.com/apache/logging-log4j2/blob/d368e294d631e79119caa985656d0ec571bd24f5/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java

Vaadin - Disable Column reordering for particular column

I'm doing a project in Vaadin 7.
In my project, I need to disable column reordering feature for particular columns in Treetable?
I'm really searching for function like this 'setColumnReorderIds()'.
Is it possible to do it in Vaadin 7.
Or else I need to write some code with 'ColumnReorderListener()'?
Update
This code is to set the first column fixed in a TreeTable. I want to disable reordering in Hierarchy column in the tree table.
public class CustomTreeTable extends TreeTable {
private static final long serialVersionUID = 1L;
private Object[] visibleColumns;
private KeyMapper<Object> columnIdMap = new KeyMapper<Object>();
#Override
public void paintContent(PaintTarget target) throws PaintException {
super.paintContent(target);
paintColumnOrder(target);
}
private void paintColumnOrder(PaintTarget target) throws PaintException {
visibleColumns = this.getVisibleColumns();
final String[] colorder = new String[visibleColumns.length];
int i = 0;
colorder[i++] = columnIdMap.key("Column 1"); // Logic to keep the first column fixed
for (Object colId : visibleColumns) {
if(!colId.equals("Column 1")) {
colorder[i++] = columnIdMap.key(colId);
}
}
target.addVariable(this, "columnorder", colorder);
}
}
Update 2
I tried what Oskar said..
In addition to
paintColumnOrder(target).
I'm calling
paintVisibleColumnOrder(target),
paintAvailableColumns(target),
paintVisibleColumns(target).
i'm able to stop reordering only for the table headers. But, the body is still reordering. Any guesses on this issue?
In the documentation there is only setColumnReorderingAllowed() which allows to control reordering of all columns. So if your case is to control particular ones it looks to me as a very custom behaviour and I would go with own implementation. Also ColumnReorderEvent is generated after processing the action itself therefore implementing own ColumnReorederListener won't help us here I think.
All actual magic which we want to change happens in private Table.paintColumnOrder() called from public Table.paintContent(), called from public TreeTable.paintContent() (see sources of Table and TreeTable). The solution would be:
extend TreeTable
override paintContent() with merged copies of Table.paintContent() and TreeTable.paintContent()
replace paintColumnOrder() call with your custom logic.
Update
Ok, now I see it's more tricky then I thought at the beginnig, since there is no easy way to access most of required fields and methods after subclassing TreeTable... Moreover, columns are reorered on the client side and only the change event status is sent to inform the server. I don't know how to handle custom reordering without creating custom gwt widget :(

How use registerCustomEditors for multiple dates?

Well, here is the code I've tried so far.
public class CustomDateEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy hh:mm a"), true))
registry.registerCustomEditor(Date.class, new StructuredDateEditor(new SimpleDateFormat("MM/dd/yyyy"), true))
}
}
I am using a calendar jQuery plugin and a regular grails DatePicker, if I just use the first one, I always get an error with the regular grails Date Picker. If I use both, I don't get an error anymore.. However, the calendar saves just the date and not the time? Anyway to fix this? :(
You may want to take a look at this:
Grails Date unmarshalling
and this
Binding a Grails date from params in a controller
I don't think you can register multiple editors for the same type, so I would instead write my own MultiDateEditor subclass of CustomDateEditor (or implement PropertyEditorSupport directly) which tries to bind a date using multiple formats. Then you only need to register MultiDateEditor:
public class CustomDateEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class, new MultiDateEditor(
["MM/dd/yyyy hh:mm a", "MM/dd/yyyy"]))
}
}

Resources