Обновление DatePicker основных компонентов с шаблоном "MMMM yyyy" посредством вызова ajax приводит к исключению "uncaught name at position [...]" - PullRequest
1 голос
/ 25 апреля 2019

У меня есть простой указатель даты в модальном диалоге. Установка начальной даты с шаблоном " ММММ гггг " (немецкий) работает отлично. Когда я пытаюсь обновить компонент с помощью вызова ajax, я получаю исключение javascript "uncaught name at positon [...]".

Я использую Primefaces 7.0 и сам написал конвертер года в месяц.

Язык PrimeFaces:

PrimeFaces.locales['de'] = {
    closeText: 'Schließen',
    prevText: 'Zurück',
    nextText: 'Weiter',
    monthNames: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
    monthNamesShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
    dayNames: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
    dayNamesShort: ['Son', 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam'],
    dayNamesMin: ['S', 'M', 'D', 'M ', 'D', 'F ', 'S'],
    weekHeader: 'Woche',
    firstDay: 1,
    isRTL: false,
    showMonthAfterYear: false,
    yearSuffix: '',
    timeOnlyTitle: 'Nur Zeit',
    timeText: 'Zeit',
    hourText: 'Stunde',
    minuteText: 'Minute',
    secondText: 'Sekunde',
    currentText: 'Aktuelles Datum',
    ampm: false,
    month: 'Monat',
    week: 'Woche',
    day: 'Tag',
    allDayText: 'Ganzer Tag',
};

Преобразователь:

/**
 * The Class YearMonthConverter.
 */
@Slf4j
@SuppressWarnings("common-java:DuplicatedBlocks")
@FacesConverter(value = "yearMonthConverter")
public class YearMonthConverter implements Converter {

    private static final String PATTERN = "MMMM yyyy";

    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String s) {
        String componentPattern = extractPattern(uiComponent);
        String pattern = componentPattern.equals("") ? PATTERN : componentPattern;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, Locale.GERMANY);

        try {
            return YearMonth.parse(s, formatter);
        } catch (DateTimeParseException e) {
            log.warn("conversion of date '{}' expected pattern '{}' failed with {}", s, pattern, e);
            return YearMonth.now();
        }
    }


    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object o) {
        if (o instanceof YearMonth) {
            String componentPattern = extractPattern(uiComponent);
            String pattern = componentPattern.equals("") ? PATTERN : componentPattern;
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, Locale.GERMANY);

            return formatter.format((YearMonth) o);
        } else {
            return null;
        }
    }

    private String extractPattern(UIComponent component) {
        // try to get the pattern from component
        if (component instanceof Calendar) {
            Calendar calendarComponent = (Calendar) component;
            return calendarComponent.getPattern();
        } else if (component instanceof DatePicker) {
            DatePicker datepickerComponent = (DatePicker) component;
            return datepickerComponent.getPattern();
        }

        return "";
    }
}

Пример xhtml-файла:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:p="http://primefaces.org/ui"
      xmlns:composite="http://java.sun.com/jsf/composite"
      xmlns:h="http://xmlns.jcp.org/jsf/html">

<composite:interface>

</composite:interface>

<composite:implementation>

    <style type="text/css">
        .ui-panel .ui-panel-content {
            padding: 0;
        }
    </style>

        <!-- modaler someDialog -->
        <p:dialog id="someDialog"
                  header="some header"
                  widgetVar="someDlg"
                  modal="true"
                  width="500"
                  height="280"
                  resizable="false"
                  closeOnEscape="true">

            <p:panel id="pnlContent" styleClass="ui-noborder">
                <p:panelGrid id="inputArea" columns="2">
                    <p:outputLabel value="Label:" style="width: 12em; display: block;"/>
                    <p:outputLabel value="example"
                                   style="display: block;"/>

                    <p:outputLabel value="Monat:" style="width: 12em; display: block;"/>
                    <h:panelGroup>
                        <!-- Monat zurück -->
                        <p:commandButton
                                id="btnPrevMonth"
                                action="#{someDialogVC.previousMonth}"
                                style="padding: 2px"
                                icon="fa fa-angle-double-left"/>
                        <!-- Monatsanzeige -->
                        <p:datePicker id="dpMonat" view="month"
                                      value="#{someDialogVC.state.selektierterMonat}"
                                      converter="yearMonthConverter" pattern="MMMM yyyy" yearNavigator="true"
                                      yearRange="2000:2050" inputStyle="width: 17em;" readonlyInput="true">
                            <p:ajax event="dateSelect" listener="#{someDialogVC.monthChanged}"/>
                        </p:datePicker>
                        <!-- Monat vor -->
                        <p:commandButton
                                id="btnNextMonth"
                                action="#{someDialogVC.nextMonth}"
                                style="padding: 2px"
                                icon="fa fa-angle-double-right"/>
                    </h:panelGroup>
                </p:panelGrid>

                <p:separator style="border-color: #aaaaaa; margin-bottom: 10px;"/>

                <div style="display: flex; justify-content: flex-end">
                    <p:commandButton id="btnCreate"
                                     value="Anlegen"
                                     action="#{someDialogVC.createAndExit()}"
                                     disabled="#{someDialogVC.doGetAnlegenDisabled()}"/>
                </div>
            </p:panel>

            <p:blockUI block="pnlContent" trigger="btnCreate">
                <p:graphicImage name="images/loader.gif"/>
            </p:blockUI>

        </p:dialog>

</composite:implementation>

</html>

установка начальной даты в бобе:

    @PostConstruct
    public void init() {
        data = new someDialogVO();
        state = new someDialogVS();

        state.setSelektierterMonat(YearMonth.now());
    }

код фрагмента bean:

    public void previousMonth() {
        state.setSelektierterMonat(state.getSelektierterMonat().minusMonths(1L));
        updateMonat();
    }

    public void nextMonth() {
        state.setSelektierterMonat(state.getSelektierterMonat().plusMonths(1L));
        updateMonat();
    }

    private void updateMonat() {
        PrimeFaces.current().ajax().update("contentForm:someDialog:dpMonat");
    }

После вызова обновления ajax я вижу, что новый месяц выбирается визуально, но через несколько миллисекунд я получаю описанное исключение javascript. При использовании шаблона " MM.yyyy " все работает нормально.

1 Ответ

2 голосов
/ 18 июня 2019

Похоже, это проблема с компонентом datePicker. Я попытался воспроизвести это с помощью минималистического теста:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.org/ui">

<h:head>
    <script>
    PrimeFaces.locales ['de'] = {
            closeText: 'Schließen',
            prevText: 'Zurück',
            nextText: 'Weiter',
            monthNames: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ],
            monthNamesShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez' ],
            dayNames: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
            dayNamesShort: ['Son', 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam'],
            dayNamesMin: ['S', 'M', 'D', 'M ', 'D', 'F ', 'S'],
            weekHeader: 'Woche',
            firstDay: 1,
            isRTL: false,
            showMonthAfterYear: false,
            yearSuffix:'',
            timeOnlyTitle: 'Nur Zeit',
            timeText: 'Zeit',
            hourText: 'Stunde',
            minuteText: 'Minute',
            secondText: 'Sekunde',
            currentText: 'Aktuelles Datum',
            ampm: false,
            month: 'Monat',
            week: 'Woche',
            day: 'Tag',
            allDayText: 'Ganzer Tag'
        };
    </script>
</h:head>
<h:body>

    <p:datePicker view="month" locale="de"
        pattern="MMMM yyyy" value="#{monthOverviewController.currentDate}">
    </p:datePicker>

</h:body>
</html>

И, конечно же, происходит то же самое uncaught exception: Unknown name at position 0, которое, скорее всего, происходит от здесь . Я проверил это с парой переводов, предоставленных на PrimeFaces 'Wiki , все с одинаковым результатом.

Проблема заключается в функции _setInitValues в компоненте datePicker:

_setInitValues: function () {
    var parsedDefaultDate = this.parseValue(this.options.defaultDate);

    this.value = parsedDefaultDate;
    this.viewDate = this.options.viewDate ? 
                this.parseValue(this.options.viewDate) 
                :
                ((((this.isMultipleSelection() || this.isRangeSelection()) && parsedDefaultDate instanceof Array) ? parsedDefaultDate[0] : parsedDefaultDate) || this.parseValue(new Date()));
    this.options.minDate = this.parseOptionValue(this.options.minDate);
    this.options.maxDate = this.parseOptionValue(this.options.maxDate);
    this.ticksTo1970 = (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000);

    if (this.options.yearRange === null && this.options.yearNavigator) {
        var viewYear = this.viewDate.getFullYear();
        this.options.yearRange = (viewYear - 10) + ':' + (viewYear + 10);
    }

    if (this.options.userLocale && typeof this.options.userLocale === 'object') {
        $.extend(this.options.locale, this.options.userLocale);
    }

    if (this.options.disabledDates) {
        for (var i = 0; i < this.options.disabledDates.length; i++) {
            this.options.disabledDates[i] = this.parseOptionValue(this.options.disabledDates[i]);
        }
    }
},

defaultDate - это значение свойства компонента (currentDate), отформатированное с использованием шаблона компонентов и локали, что в нашем случае приводит к немецкой строке. Теперь самая первая инструкция в _setInitialValues пытается разобрать эту дату. Тем не менее, пользовательский перевод еще не был загружен (см. $.extend(this.options.locale, this.options.userLocale);, и поэтому название месяца (в большинстве случаев) не может быть разрешено. Это, на мой взгляд, ошибка в компоненте, и лучшее / наиболее стабильное решение должно ждать подходящее исправление. Промежуточное решение (имейте в виду, что в лучшем случае это просто взлом) было бы скопировать файл JavaScript и перенести загрузку перевода выше первого разбора:

_setInitValues: function () {
    if (this.options.userLocale && typeof this.options.userLocale === 'object') {
        $.extend(this.options.locale, this.options.userLocale);
    }
    var parsedDefaultDate = this.parseValue(this.options.defaultDate);
...