Добрый день/вечер. В продолжении своего вопроса про
DigitalClock
.
Друзья, подскажите, пожалуйста, как вы
динамически
работаете с установкой локали с последующим форматированием на её основе даты (например) в виджете с помощью DateFormat.
Не могу ничего найти, ни в url (к примеру ?locale=ru), ни в head ().
Как быть? Голова от документации уже кипит.
Вы этот add-on имеете ввиду ?
http://vaadin.com/addon/digitalclock
Если этот, то без модификации самого виджета Вам, боюсь, не обойтись.
Вот код виджета: http://askvikrant.com/wp-askvikra-content/uploads/DigitalClockWidget.java_.txt
Как Вы можете видеть про локаль здесь никто не думает. Ипользуется дефолтная. Именно замена дефолтной
даёт возможность Вам использовать meta тег с заменой дефолтной локали на ту что вам нужно при загрузки страницы.
Для того чтобы менять локаль на лету (програмно) нужно использовать локаль, кот. передаётся сервером
через коннектор (rpc или через State) . Тот коннектор что там есть сейчас про локаль ничего знает :
http://askvikrant.com/wp-askvikra-content/uploads/DigitalClockConnector.java_.txt
метод setLocale() есть в AbstractComponent, так что он передаётся от сервера к клиету скорее всего через State.
Нужно только его считать в коннекторе и выставить в виджет для использования в методах форматирования.
Да, Денис, всё верно, именно он. Я просматривал код, расширял локаль в gwt.xml, типа такого:
<inherits name="com.google.gwt.i18n.I18N" />
<extend-property name="locale" values="ru" />
<extend-property name="locale" values="en" />
там на клиентской стороне дата форматируется через com.google.gwt.i18n.shared.DateTimeFormat. С url и meta-tagами это всё прекрасно работает, но хотелось бы что-нибудь вроде этого.
DigitalClock clock = new DigitalClock();
clock.setLocale(new Locale("ru", "RU");
//Меняется язык и соответственно формат отображаемой даты и времени.
метод setLocale() есть в AbstractComponent, так что он передаётся от сервера к клиету скорее всего через State.
Нужно только его считать в коннекторе и выставить в виджет для использования в методах форматирования.
Хорошо, без проблем можно переопределить setLocale() от AbstractComponent в DigitalClock и передать его через состояние в клиентский виджет.
Но как уже на клиентском месте установить локаль программно при условиях:
Date date = new Date();
DateTmeFormat dateFormat = DateTimeFormat.getFormat("EEEE, dd MMMM, yyyy");
//наша переданная сюда через rpc локаль
Locale currentLocale;
setLocale(Locale locale){
currentLocale = locale;
//далее должна быть например логика обновления dateFormat - локаль же изменили.
}
//ну и собственно
dateFormat.format(date);
Извините, если слишком сумбурно или непонятно объясняю. В общем цель - имея Locale locale на клиентском месте, иметь возможность программно изменять формат даты с учетом изменения этой локали. При том, что мы имеем возможность делать это только с помощью библиотеки com.google.gwt.i18n.
Спасибо, что откликнулись.
UPDATE:
через просмотр кода класса Label и учитывая свой неудачный опыт - State не может принимать объекты класса Locale Надо конвертировать как-то. Ну вообщем-то это самая меньшая из бед. Можно тег “ru” или “en” передавать.
А, так у Вас проблема не с Vaadin-вской коммуникацией, а с GWT.
Тут я не возмусь советовать что-то определённое. Насколько я понимаю, GWT локализацию собирает статически
(то есть знает только про те локали, кот. были прописаны в файле модуля при сборке). Так что произвольную локаль на лету использовать не получится. Доступны будут только те, с кот. было сконфигурировано приложение.
По всей видимости не предполагается также и замены локали в загруженном приложении . Нужно его перегружать полностью. Так что нужно, видимо, использовать что то вроде этого :
http://stackoverflow.com/questions/6275975/how-can-i-get-all-date-time-format-pattern-in-gwt-for-different-locales
То есть конкретные имплементации классов для каждой локали (соот-но нужно знать заранее список нужных локалей).
Всё равно спасибо за совет, я впринципе и пытаюсь через DateTimeFormatInfo установить формат. Надеюсь всё получится.
Можно тогда вопрос в догонку? Есть ли возможность в Vaadin динамически устанавливать или параметры в URL в стиле ?locale=ru тот же. Всё что нашел на данный момент - BootstrapListener (устанавливает доп. html при иницилизации сервлета) и setFragmentUrl (позволяет добавить якорь в url в стиле #main).
“Штатными” средствами Vaadin манипулировать HTML-ем на таком уровне не получится. Но всегда можно влезть в клиентскую часть. Там можно использовать GWT и чистый JS.
Посмотрите, например вот это: https://vaadin.com/blog/-/blogs/vaadin-7-loves-javascript-components
Да, и вот ещё что. Используя Vaadin, обычно не принято использовать локализацию на уровне GWT. Поскольку всегда есть серверная часть, используют локализацию с сервера, а на клиент пересылают уже локализованные данные. В лоб с датой так неполучится, потому что нужна не дата сервера, а клиента, но можно посмотреть в сторону формирования строки формата для даты на сервере, а на клиенте просто подставить в строку формата уже нужные значения. Как то так.
Спасибо, Денис.
Т.е. если я правильно понял по сути весь механизм наших часов(в перспективе если мы бы их создавали) должен был бы выглядеть следующим образом:
- таймер на клиент-коннекторе каждую секунду обращается к серверу через rpc;
- сервер реализует метод rpc форматирует текущую дату в строку, в зависимости от текущего состояния.
- и отсылает её обратно (через state или rpc) на клиент вызывая какой-нибудь метод в стиле на setText(String s);
Т.е. подход всю логику отгружать на сервер? Я просто первоначально таким образом их переделывал - сказали что изобретаю велосипед.
ps я совсем начинающий программист - многое ещё не знаю или не понимаю как работает. не судите строго
Нет, это слишком сложно, не нужно и будет неправильно работать: время, полученное с сервера будет временем сервера в момент отсылки, а не время клиента в момент получения. Разница будет во времени пересылке данных по сети. Это ненулевая величина, да и запросов будет слишком много.
Я предлагаю (всего лишь как вариант/предложение)
ОДИН
раз послать строку формата даты с севера на клиент (в смысле тогда когда надо поменять локаль, например как реакцию на какую-нибудь кнопку) . И использовать эту строку формата до следующего изменения. А в эту строку формата уже подставлять данные клиентского времени.
То есть, например , посылать строку вроде “DD-MM-YYYY HH:MM:SS” ( день-месяц-год час:минута:секунда).
А на клиенте подставлять уже цифры вместо переменных DD, YYYY, HH, …
Ещё раз говорю : это просто предложение. Я бы посмотрел в эту сторону. Неясен в деталях сходу вопрос как получать нужную строку формата на сервере (в виде переменных, как я описал) и как подставлять потом значения в эти переменные на клиенте. Но, вроде бы это всё должно решаться.
Да вообщем-то решилось оно на самом деле такими образом(пишу только основные важные части кода):
Перегрузил метод установки локали в самом компоненте от AbstractComponent:
@Override
public void setLocale(Locale locale) {
super.setLocale(locale);
getState().languageTag = locale.toLanguageTag();
}
в State:
public class DynamicClockState extends com.vaadin.shared.AbstractComponentState {
public long time = 0;
[b]
public String languageTag;
[/b]
public boolean isWorking = true;
public boolean isWorking(){
return isWorking;
}
}
в Connector:
@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
if (getState().isWorking()) {
final long time = getState().time;
getWidget().setTime(time);
[b]
getWidget().setLanguage(getState().languageTag);
[/b]
} else {
timer.cancel();
getWidget().getTimer().cancel();
}
}
В виджете поддержка двух языков:
public class DynamicClockWidget extends HTML {
public static final String CLASSNAME = "clockcomponent";
private long time = 0;
private Timer timer = null;
private Date date = new Date();
private DateTimeFormat dateFormat=DateTimeFormat.getFormat("EEEE, dd MMMM, yyyy");
private DateTimeFormat timeFormat=DateTimeFormat.getFormat("hh:mm:ss");
private final DateTimeFormat ruDateFormat = new DateTimeFormat(
"EEEE, dd MMMM, yyyy", new DateTimeFormatInfoImpl_ru()) {
};
private final DateTimeFormat ruTimeFormat = new DateTimeFormat("hh:mm:ss",
new DateTimeFormatInfoImpl_ru()) {
};
private final DateTimeFormat enDateFormat = new DateTimeFormat(
"EEEE, dd MMMM, yyyy", new DateTimeFormatInfoImpl_en()) {
};
private final DateTimeFormat enTimeFormat = new DateTimeFormat("hh:mm:ss",
new DateTimeFormatInfoImpl_en()) {
};
private String currentLanguageTag;
public void setLanguage(String languageTag) {
if (!currentLanguageTag.equals(languageTag)) {
if (languageTag.equalsIgnoreCase("ru-ru")) {
dateFormat = ruDateFormat;
timeFormat = ruTimeFormat;
} else {
dateFormat = enDateFormat;
timeFormat = enTimeFormat;
}
currentLanguageTag = languageTag;
}
}
public Timer getTimer(){
return timer;
}
}
не самый элегантный способ, но по крайней мере это уже работает.
Теперь трудности погасить таймер. Который остаётся висеть на клиенте после того, как мы обновляем контент главной панели с панелью с часами. Таймер на клиенте продолжает каждый 5 минут присылать запрос на сервер и в результате:
июн 27, 2013 1:40:39 PM com.vaadin.server.AbstractCommunicationManager parseInvocation
WARNING: RPC call to ru.***.widgets.client.dynamicclock.DynamicClockServerRpc.getServerTime received for connector 14 but no such connector could be found. Resynchronizing client.
Как можно увидеть в коде, я пытаюсь его погасить через изменения состояния методом cancel() но не работает
Ну это ровно то, что я Вам предлагал в своём втором посте про использование конкретных имплементаций для каждой локали в отдельности. Но это ограничивает код теми локалями, под кот. написана импл-ия. Нельзя взять и добавить новую на лету. Это не расширяемо.
Насчёт таймера: cancel должен работать. Если только Вы его не на null вызываете (потому что я не вижу у Вас кода таймера, при том что у Вас их аж целых два).
Ну и самое главное Henri Sara сказал мне что мы тут изобретаем велосипед. Всё уже сделано до нас.
Есть класс DateTimeService. Это то что Вам нужно вместо GWT-ных форматов. Так что GWT Вам не нужен.
Этому классу можно установить локаль через метод : setLocale() (или в конструкторе). Класс клиентский (на стороне броузера). И дальше можно использовать его для форматирования даты.
Теперь, вопрос получения локали - это тоже изрбретения велосипеда. Она и так передаётся при вызове метода setLocale() (у серверной компоненты). Не нужно писать свой кустарый код для её передачи и получения на
стороне клиента. Вопрос: как её получить в клиенте. Можно посмотреть пример: AbstractDateFieldConnector.
Используется старый (от Vaadin 6) способ обработки полученных данных от сервера, через метод updateFromUIDL().
Для того чтобы его задействовать Вам нужно имплементировать интерфейс Paintable в коннекторе.
Он как раз и заставит импл-ть метод updateFromUIDL().
В его импле-ии нужно только вызвать в самом начале :
if (!isRealUpdate(uidl)) {
return;
}
а потом забрать локаль:
DateTimeService service = new DateTimeService();
if (uidl.hasAttribute("locale")) {
final String locale = uidl.getStringAttribute("locale");
service.setLocale(locale);
}
service здесь, конечно должна быть не локальной переменной, а тем, что используется внутри виджета. Его и используйте для форматирования даты.
В общем, спасибо Henri.
Спасибо, Денис и Henri. За терпение и информацию.
Попробую в ближайшие дни со всем этим до конца разобраться. cancel() пока очень странно работает.
Буду пытаться искать косяки у себя. Хотя и в этом определенный положительные сдвиги есть.
p.s.Если бы ещё компилятор не собирал клиентский виджет по 1000 секунд, было бы всё проще.
Компилятор долго работает, это да. Но он не только ваш виджет собирает, а всё что найдёт в класспасе. Потому и долго. Если бы можно было скормить ему только один виджет, то это не было бы так долго.
Насчёт cancel(), как вариант, таймер может заново в планировку ставиться где то в коде. То есть cancel() то вызывается и отрабатывает, но потом, в какой то момент, вызывается schedule() у таймера опять в виде реакции на какое-нибудь событие.
Henri вот ещё насчёт компиляции справедливо заметил, что:
- Можно ограничиться сборкой только одной permutation (для конктертного броузера), выставив свойство в gwt.xml, например
- Убрать оптимизацию, используя draft флаг : добавить -draftCompile в строку аргументов компилятора
- Использовать рецепты отсюда:
[list]
-
Can-I-speed-up-the-GWT-compiler
-
Can-I-speed-up-the-GWT-compiler-Part-II
-
Can-I-speed-up-the-GWT-compiler-Part-III
-
how-do-i-speed-up-the-gwt-compiler
[/list] - Наконец, использовать Dev mode во время разработки конкретного виджета. Это вообще то стоит поставить самым первым пунктом. Здесь всё будет собираться на лету и работать прямо в JVM. Это существенно ускоряет разработку.
Всё это можно делать для ускорения разработки виджетов. Потом нужно будет всё таки скомпилировать без всех этих настроек, включив всё назад. Но это нужно сделать будет только один раз для финального результата.
Чисто серверную же часть уже можно писать без компиляции виджетов в нормальном окружении.
Денис, спасибо за ссылки. Очень круто помогаете мне быстро въехать во все библиотеки.
Ускорил компиляцию до 400 секунд (почистив локали и установив режим разработки).
Что касаемо DateTimeService, набросал тестовую программку(панелька с отображением форматированной даты + 3 кнопки: 1) устанвливает рус локаль; 2)англ. 3)просто пишет привет):
@SuppressWarnings("serial")
public class TestprojectdocUI extends UI {
private VerticalLayout mainLayout;
private MyFormatter formater;
private final Locale ruLocale = new Locale("ru", "RU");
private final Locale enLocale = new Locale("en", "US");
@Override
protected void init(VaadinRequest request) {
mainLayout = new VerticalLayout();
setContent(mainLayout);
final Panel panel = new Panel();
formater = new MyFormatter();
panel.setContent(formater);
mainLayout.addComponent(panel);
Button enButton = new Button("English");
enButton.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
formater.setLocale(ruLocale);
panel.setContent(formater);
}
});
Button ruButton = new Button("Russian");
ruButton.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
formater.setLocale(enLocale);
panel.setContent(formater);
}
});
Button testButton = new Button("Hello");
testButton.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
panel.setContent(new Label("Hello"));
}
});
mainLayout.addComponent(ruButton);
mainLayout.addComponent(enButton);
mainLayout.addComponent(testButton);
}
}
Вообщем всё логическое движение происходит на клиенте (метод setLocale не переопределял):
public class MyFormatterWidget extends HTML implements Paintable {
private Date date = new Date();
private final String CLASSNAME = "myformatter";
private final DateTimeService service = new DateTimeService();
public MyFormatterWidget() {
// setText("MyFormatter sets the text via MyFormatterConnector using MyFormatterState");
setStyleName(CLASSNAME);
paint();
}
public void paint(){
String result = "<div>" + service.formatDate(date, "hh:mm:ss") + "</div>";
String result2 = "<div>" + service.formatDate(date, "EEEE dd MMMM, yyyy") + "</div>";
setHTML(result + result2);
}
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if(uidl.hasAttribute("cached")){
return;
}
final String locale = uidl.getStringAttribute("locale");
try {
service.setLocale(locale);
paint();
} catch (LocaleNotLoadedException e) {
e.printStackTrace();
}
}
}
Результат:
при нажатии на Hello - получаем Hello.
при нажатии на Russian или English получаю одно и то же, причём симбиоз какой-то русско-англ. неправильный:
11:51:34
Friday 28 июнь, 2013
UPD: после прописал в настройках один браузер - получил 100 секунд. в 10 раз быстрее
UPD2: нашел сам свой же косяк - делал всё в виджете, а надо в коннекторе - извиняйте за невнимательность.
Подправил свой же код.
В коннектор перенёс всё что касается Paintable:
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if(uidl.hasAttribute("cached")){
return;
}
final String locale = uidl.getStringAttribute("locale");
try {
getWidget().setLocale(locale);
} catch (LocaleNotLoadedException e) {
e.printStackTrace();
}
}
В виджет добавил метод setLocale(String labnTag)
piblic void setLocale(String langTag) throws LocaleNotLoadedException{
service.setLocale(langTag);
paint();
}
Результат к сожалению точно такой же