@unikalna_nazwa:
"trochę przesadzasz tylko komplikując niepotrzebnie problem - przeglądarki same sobie ustawiają i domykają właściwie tagi przy przypisywaniu do innerHTML - sprawdź sam. Nie widzę realnej potrzeby tracenia na to czasu tym bardziej że to komplikuje problem dość mocno ;)"
Może przesadzam, może nie. Na co dzień jestem raczej tworcą bibliotek niż kodu, który ma rozwiązać jakiś konkretny, dobrze określony problem w mocno ograniczonym środowisku, przy licznych założeniach. Nie zaczynam od wprowadzania założeń i ograniczeń dotyczących kompatybilności czy pracy innych skryptów na stronie. Podchodząc do problemu, raczej na początku (tj. po dowiedzeniu się, że nie ma na to gotowca czy API) myślę o uniwersalnym, kuloodpornym rozwiązaniu -- czasami oznacza to coś w stylu "brute force", tj. robienia pewnych rzeczy ręcznie, bez ułatwiaczy -- i wtedy robię analizę kosztów i optymalizacji.
Mówisz, że "niepotrzebnie komplikuję"? A ja nie jestem pewien, na ile bardziej skomplikowane byłoby skakanie po DOM w porównaniu do przeszukiwania innerHTML
. Zauważ, że i w przypadku innerHTML
nie mógłbyś korzystać po prostu z str.indexOf()
, bo innerHTML
zawiera też inne tagi HTML. Jak byś je parsował/odcedzał? Wyrażeniami regularnymi? Takie coś jest niebezpieczne i zapewne już na tym etapie musiałbyś zrezygnować ze 100% dokładności w przypadkach brzegowych. Ludzie często piszą regexpy, które wykrzaczają się np. na:
<span data-relation="n > 2" class="red">Foobar</span>
A przeglądarki to łykają. Choć akurat tym w przypadku .innerHTML
nie musimy się martwić, bo przeglądarka zwróci >
zamiast pierwszego >
. Ogólnie jednak, trzeba bardzo ostrożnie sprawdzić, czy regexpy do naszych zastosowań wystarczą, czy też aby parsować zawsze wszystko porządkie, potrzebujemy parsera SGML.
Tak czy siak, te tagi, komentarze, sekcje CDATA trzeba odcedzać. A raczej: przeskakiwać. Bo potem chcemy przecież wstawić w kod nasze tagi <mark>
, nie tracąc formatowania. Więc samo wycięcie nie-tekstu nic nam nie daje: znaleźlibyśmy pozycję w tekście, gdzie chcemy wstawić podkreślenie, ale ma się ona nijak do pozycji w HTML-u.
Operowanie na innerHTML
-u nie musi być więc aż tak prostsze jak skakanie po drzewie, gdzie całkowicie odpada nam parsowanie HTML-a, bo robi to za nas przeglądarka.
Na szybko przychodzą mi jeszcze do głowy dwa ograniczenia metody z innerHTML
.
Wspomniałem wyżej o różnicach wynikających z renderowania elementów, do których należą fragmenty szukanego ciągu. Że jeśli szukana fraza rozciąga się na kilka elementów wierszowych, to powinniśmy ją podświetlić. A jeśli na kilka blokowych, to już nie. Jak to sprawdzisz, nie odwołując się do aktualnych, wyliczonych stylów danego elementu, dostępnych z poziomu DOM (CSSOM)? CSS może zmieniać typ ramki generowanej przez element, więc nie wystarczy samo rozgraniczenie, że jeśli to <p>
to blokowy, a jeśli <span>
to wierszowy. Za pomocą innerHTML-a
tego po prostu nie sprawdzisz, więc w kolejnych przypadkach brzegowych (?) kod zawiedzie.
I drugi problem: mapilulowanie innerHTML
-em danego elementu wywala z niego wszystkie elementy i wstawia nowe. Wszystkie dane doczepione do starych elementów za pomocą JS (zła praktyka, ale ludzie tak piszą) przepadają. Wszystkie event-handlery też -- a doczepianie ich to już normalna praktyka. Trzeba więc nałożyć na programistę-użytkownika kolejne ograniczenia. Np. ograniczenie czasowe "odpal skrypt podświetlający tylko raz, zanim inne skrypty zaczną działać" (i wtedy przepada możliwość zmiany szukanej frazy w locie, tracimy więc funkcjonalność). Albo ograniczenie jakościowe/API: "wewnątrz elementów, w których ma dzialać podświetlanie, używaj wyłącznie delegacji zdarzeń". Oczywiście, niekiedy frazy możemy szukać w całym dokumencie i wtedy musielibyśmy delegować wszystkie zdarzenia (!) aż do <body>
. Oczywiście, twórcy bibliotek i skryptów JS-owych -- lightboxów, menu itd. -- tego nie robią, więc część z tych skryptów nie działałaby w części przypadków.
Co z tego wynika? Nie wiem, nie musi wynikać ostateczna decyzja. Przyznaję się np. bez bicia, że nie przeanalizowałem API dostępnego w przeglądarkach i nie chciało mi się nawet sprawdzić, gdzie jest to window.find()
-- nie interesuje mnie to w tym momencie. Gdybym to ja miał napisać tę funkcjonalność, na pewno dogłębnie bym to sprawdził.
Nie znamy też ograniczeń, jakie autor tematu może sobie narzucić. Jeśli chce napisać coś, co zadziała tylko w jednej aplikacji, pisanej przez niego, w dodatku tylko w określonym miejscu zawartości, to użycie innerHTML
może okazać się wystarczające. Tego jednak na tę chwilę nie wiemy -- gdyby autor był naszym klientem, musielibyśmy przeprowadzić odpowiedni wywiad i jeszcze wziąć poprawkę na to, na ile jego zdanie może się zmienić i na ile opłacalne biznesowo byłoby zaproponowanie ogólniejszego rozwiązania. To mnie jednak też w tym momencie nie interesuje.
Zainteresowało mnie, jak może wyglądać porządne, ogólne rozwiązanie tego problemu i postanowiłem zrobić sobie tutaj małą analizę, która może, ale nie musi przydać się autorowi i innym forumowiczom. Choćby jako eksperyment myślowy czy wgląd w problemy, z jakimi muszą zmagać się twórcy bibliotek.
Czasami opłaca się zrobić coś porządnie i ogólnie (choć... nie zawsze!). Ja np. pracuję w kilkunastoosobowym zespole przy relatywnie sporych projektach, tworzonych przez kilkuosobowe (2-5) "podzespoły". Napisanie paru tysięcy linii kodu bibliotek do jednego projektu nie jest tu niczym niespotykanym. Jeśli rozwiązania te są ogólne, są potem reużywane na kilku (czy więcej) rodzinach portali. Jeśli ogólności brakuje, przy nowym portalu są po prostu wyrzucane i robione od nowa -- czy to w wersji ogólnej, czy kolejnej wersji szczególnej.