TinyMCE – dodawanie walidacji w popupie

Gdy tworzymy popup z ustawieniami dla naszego przycisku w TinyMCE, prawdopodobnie szybko odkryjemy, że przydałaby się jakaś walidacja danych wprowadzanych w polach popupa, aby użytkownik nie mógł wprowadzić błędnych danych. Prosta walidacja danych jest możliwa w łatwy sposób. Podświetlenie błędnie wypełnionych pól także da się zrobić, choć wymaga ono już trochę pracy i paru mniej eleganckich chwytów.

Zacznijmy od kodu bazowego – przyjmijmy, że nasz popup z dwoma polami: width i height, ma zdefiniowaną akcję onsubmit w następujący sposób:

onsubmit: function(e) {
    editor.insertContent('Lorem ipsum: ' + e.data.width + ' x ' + e.data.height + '.');
}

Zaimplementowanie podstawowej walidacji jest w tym wypadku bardzo proste – sprawdzamy wartości pól i w zależności od spełnienia kryteriów wysyłamy dane z popupa lub nie korzystając z return false do blokowania wysyłania popupa:

onsubmit: function(e) {
    if(e.data.width === '' || e.data.height === '') {
        return false;
    }
    editor.insertContent('Lorem ipsum: ' + e.data.width + ' x ' + e.data.height + '.');
}

Niestety taki sposób walidacji ma poważną wadę – użytkownik nie wie tak naprawdę co się stało i dlaczego formularz nie został wysłany. Dlatego powinniśmy wyświetlić mu odpowiedni komunikat – możemy skorzystać z funkcji alert ale dużo zgrabniej będzie skorzystać z wbudowanego w TinyMCE okna komunikatu:

onsubmit: function(e) {
    if(e.data.width === '' || e.data.height === '') {
        editor.windowManager.alert('Proszę wypełnić wszystkie pola w popupie.');
        return false;
    }
    editor.insertContent('Lorem ipsum: ' + e.data.width + ' x ' + e.data.height + '.');
}

Użytkownik po takiej informacji powinien już być uświadomiony, że coś jest nie tak z wprowadzonymi przez niego danymi, ale warto na wszelki wypadek jeszcze pokazać mu, które pola są wypełnione nieprawidłowo. W tym celu będziemy musieli już stworzyć trochę bardziej wyszukany kod, który pozwoli nam dodać obramowanie wokół źle wypełnionych elementów.

Szukałem możliwie najbardziej eleganckiej metody, niestety nie obyło się bez odwoływania do właściwości obiektów, które patrząc po nazwach powinny być prywatne, ale niestety nie doszukałem się w kodzie TinyMCE innej równie wygodnej metody.

Zacznijmy od tego, że musimy zdobyć uchwyt do pól formularza wyświetlanego w popupie – w tym celu musimy się odwołać do właściwości zwracającej ID wyświetlanego popupa:

var window_id = this._id;

Dzięki temu ID możemy pobrać pola formularza:

var inputs = jQuery('#' + window_id + '-body').find('.mce-formitem input');

Mając uchwyty do pól formularza możemy nadać im kolor obramowania:

if(e.data.width === '') {
    $(inputs.get(0)).css('border-color', 'red');
}

W powyższym wypadku nadamy czerwone obramowanie pierwszemu polu tekstowemu w formularzu.

Żeby wszystko zachowywało się sensownie musimy też sprawić by to obramowanie zniknęło w momencie kliknięcia pola. Zamieniamy zatem:

{
    type: 'textbox',
    name: 'height',
    label: 'Height'
},

na:

{
    type: 'textbox',
    name: 'height',
    label: 'Height',
    onclick: function(e) {
        jQuery(e.target).css('border-color', '');
    }
},

Niestety z moich obserwacji wynika, że onclick to chyba jedyne sensowne zdarzenie, które działa – zatem nie możemy się posłużyć tutaj bezpośrednio zdarzeniami onblur czy onfocus.

Finalnie nasz kod w onsubmit wyglądać może następująco:

onsubmit: function(e) {
    if(e.data.width === '' || e.data.height === '') {
        var window_id = this._id;
        var inputs = jQuery('#' + window_id + '-body').find('.mce-formitem input');

        editor.windowManager.alert('Proszę wypełnić wszystkie pola w popupie.');

        if(e.data.width === '') {
            $(inputs.get(0)).css('border-color', 'red');
        }

        if(e.data.height === '') {
            $(inputs.get(1)).css('border-color', 'red');
        }

        return false;
    }
    editor.insertContent('Lorem ipsum: ' + e.data.width + ' x ' + e.data.height + '.');
}

A co jeśli potrzebujemy więcej zdarzeń dla elementów formularza?

Nie ma co ukrywać, że domyślnie dostępne zdarzenia w TinyMCE nie pozwalają nam tworzyć zbyt rozbudowanych popupów z bardziej złożoną logiką. Na szczęście da się ten problem rozwiązać – jeżeli potrzebujemy dodać obsługę np. zdarzeń onblur czy onfocus do naszych pól możemy skorzystać z małego tricku.

Popup tworzony w TinyMCE wywołuje co najmniej dwa razy zdarzenie repaint – umiejętnie podpinając się pod to zdarzenie możemy uzupełnić braki funkcjonalne naszego popupu.

Przykładowa implementacja, dodająca obsługę zdarzeń onfocus i onblur do pierwszego pola w formularzu:

onrepaint: function(e) {
    var window_id = this._id;

    if(!jQuery('#' + window_id).hasClass('form-initialized')) {
        jQuery('#' + window_id).addClass('form-initialized');

        var inputs = jQuery('#' + window_id + '-body').find('.mce-formitem input');

        jQuery(inputs.get(0)).blur(function() {
            console.log('blur');
        });

        jQuery(inputs.get(0)).focus(function() {
            console.log('focus');
        });
    }
},

Chciałbym w tym miejscu uczulić na fakt wywoływania się zdarzenia repaint kilkukrotnie – dlatego stworzyłem w powyższym kodzie prosty mechanizm sprawdzania czy wywołał się on tylko raz – po pierwszym wywołaniu tej funkcji dodawana jest do popupu klasa form-initialized, która powoduje, że już więcej nasz kod nie zostanie wywołany. Dzięki temu unikniemy między innymi problemów z podwójnie dodanymi zdarzeniami.

Korzystając z powyższej metody możemy w zasadzie stworzyć dowolną logikę dla naszego formularza w popupie. Główną uciążliwością jest tutaj dostęp do pól formularza – różne typy pól mają różne klasy i musimy wszystko odpowiednio wyselekcjonować a potem poprawnie się w tym wszystkim poruszać.

Nie zmienia to faktu, że stworzenie walidacji zawartości popupu w TinyMCE jest możliwe, choć w wypadku bardziej skomplikowanych formularzy wymaga dość skomplikowanego kodu, ze wzgledu na pewne braki w API TinyMCE, które trzeba samodzielnie obejść.