JSF 2.1 Ajax автозаполнение + поиск на сервере только после того, как пользователь перестал печатать - PullRequest
0 голосов
/ 21 декабря 2018

Я разрабатываю решение для автозаполнения с поиском в базе данных на основе ввода событий (onkeyup), которые пользователь вводит в <h:inputText />.

Проблема в том, что у меня есть два предварительных условия, что приложениедолжен присутствовать, чтобы выполнить поиск на стороне сервера:

  1. Текст сообщения:> = 3 и <= 6 символов </li>
  2. Пользователь прекратил печатать на несколько миллисекунд (предотвращение перегрузки сервера с помощьюк большому поиску)

Без элемента 2) довольно просто решить, просто добавив что-то вроде onkeyup="return checkInputs(this.value) и возвращая false каждый раз, когда длина ввода текста <3 или> 6.

Но для участия в пункте 2) мне кажется, что я должен использовать Javascript setTimeout , в котором в этом случае используется обратный вызов, т. Е. Я не могу использовать какой-либо возврат для удовлетворения события onkeyup измой вводимый текст.

Я боролся с этой проблемой в течение нескольких дней, и у меня нет никаких подсказок, как я могу решить эту проблему, просто потому, что я ограничен моим реальным стеком (JSF 2.1слюбую дополнительную библиотеку, такую ​​как PrimeFaces или OmniFaces и Jquery 1.3)

HTML-код:

<h:inputText id="text" alt="text"
                maxlength="6" size="6" title="text"
                value="#{managedBean.searchText}"
                onkeyup="return checkInputs(this.value)">
                <f:ajax listener="#{managedBean.doSearch}" execute="@this"
                    event="keyup" render="form:searchResult" />
            </h:inputText>

Javascript:

function checkInputs(value) {

let cnsAnterior = sessionStorage.getItem('previousValue');
sessionStorage.setItem('previousValue', value);

if((value.length >= 3 && value.length <= 6) && (previousValue != value)){       
    return true;
}

return false;

}

Некоторыефрагмент, который я нашел на этом сайте , для предотвращения поиска до тех пор, пока пользователь не перестанет печатать:

// Get the input box
var textInput = document.getElementById('test-input');

// Init a timeout variable to be used below
var timeout = null;

// Listen for keystroke events
textInput.onkeyup = function (e) {

    // Clear the timeout if it has already been set.
    // This will prevent the previous task from executing
    // if it has been less than <MILLISECONDS>
    clearTimeout(timeout);

    // Make a new timeout set to go off in 800ms
    timeout = setTimeout(function () {
        console.log('Input Value:', textInput.value);
    }, 500);
};

Есть ли способ, используя JSF, что я могу присутствовать в этих двух пунктах?Выполнять поиск на сервере, только когда пользователь набрал не менее 3 символов И остановил ввод за несколько миллисекунд?

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 22 декабря 2018

Поскольку вы не используете JSF 2.2 или более позднюю версию (пока) и использование PrimeFaces (исключительно для его p:ajax) не является опцией, вам действительно нужен некоторый «обходной путь» (фактически это не обходной путь, а для JSF 2.1 aхорошее решение).

К счастью, многие так называемые «современные» поклонники javascript-интерфейса забывают об этом, клиентская часть JSF - это просто html, javascript и ajax.Ajax JSF, как и любой другой Ajax в любой другой среде, обрабатывается с помощью javascript, и его базовая обработка ajax может быть переопределена базовым способом javascript, как можно увидеть в Прерывание запроса Ajax JSF из jsf.ajax.addOnEvent() .

Я взял это за основу и соединил это с некоторой креативной базовой обработкой тайм-аута javascript, так как вы уже подозревали, что это будет необходимо.

Используется опция для передачи исходных аргументов события неанонимной функции javascript через тайм-аут.Как и в Как передать параметр в функцию обратного вызова setTimeout ()? .Самым сложным было выяснить, как будут передаваться «аргументы».Оказалось, что вы даже можете передать несколько отдельных аргументов в setTimeout вместо одного аргумента 'arguments'

Это выглядит как много кода, но многоэто комментарий / логирование (которое вы можете удалить) и некоторые переменные могут быть уменьшены.

if (jsf) { // test if the 'jsf' javascript is available

    var originalRequest = jsf.ajax.request; // 'save' the original ajax handling
    var timeouts ={}; // keep an 'hash' of timeouts so it can be used for lots of
                      // different elements independently based on element id

    jsf.ajax.request = function(source, oevent, options) {

        // check for a 'delayedEvent' class on an input and only apply delay to those that have one
        delayed = document.getElementById(oevent.target.id).classList.contains("delayedEvent");
        if(delayed) {

            // check if an existing timer for this element is already running and clear it
            // so it will never fire but will be 'superseded' by a new one
            var timeout = timeouts[oevent.target.id];
            if (timeout != undefined) {
                console.log('Delayed event cleared: ' , arguments, timeout);
                clearTimeout(timeout);
            } 

            // create a new timeout with a 350 ms delay. Making the delay 'dynamic' could 
            // be done by creating 'composite' classnames like 'delayEvent500' and delayEvent250'. 
            // Or putting both via html 5 data attributes on a container elelement... 
            // Be creative
            timeout = setTimeout(applyOriginalEvent, 350, source, oevent, options);
            console.log("Delayed event created: ", arguments, timeout);
            timeouts[oevent.target.id]=timeout;
        } else {
            console.log('Direct event fired: ' , arguments);
            originalRequest.apply(null, arguments); // apply the request direcyly
        }
    };


    function applyOriginalEvent(source, oevent, options) {
        var timeout = timeouts[oevent.target.id];
        console.log('Delayed event fired: ' , arguments, timeout);
        // fire an ajax request with the original arguments;
        originalRequest.apply(null, arguments);
        // remove the timeout from the list
        delete timeouts[oevent.target.id];
    }

};

Просто убедитесь, что это загружается после загрузки jsf.js.Я протестировал это локально и на витрине Omnifaces через консоль разработчика браузера.

С чем я не проверял, так это с вашими существующими проверками длины, но их можно не трогать.

0 голосов
/ 22 декабря 2018

Для решения этой проблемы ИМХО вам нужно мыслить за пределами стека JSF.В Angular это обычная ситуация, решаемая с помощью оператора debounceTime из RxJS.Например:

import { AbstractControl } from '@angular/forms';
import { debounceTime, switchMap, map, first } from 'rxjs/operators';
@Injectable({ providedIn: 'root'})
export class UserNotTakenValidatorService {
    constructor(private signUpService: SignUpService) {
}
checkUserNameTaken() {
    return (control: AbstractControl) => {
        return control.valueChanges
            .pipe(debounceTime(300))
            .pipe(switchMap(userName => this.signUpService.checkUserNameTaken(userName)))
            .pipe(map(isTaken => isTaken ? {userNameTaken: true} : null ))
            .pipe(first());
        }
    }
}

В этом случае служба будет вызываться только после того, как пользователь перестанет печатать в течение 300 миллисекунд.

При небольшом исследовании я обнаружил интеграцию RxJS и Java.Проверьте это: https://www.baeldung.com/rx-java. Если эта интеграция по какой-то причине не помогает, я бы попытался импортировать библиотеку RxJS в проект.

...