Ajax a lokalny javascript

0

Witam!
Tworzę bardzo prostą stronkę z paroma podstronami. Realizuję wszystko tak, że mam parę plików html, każdy ma diva o id #content, w którym znajduje się jego właściwa zawartość. Kiedy user kliknie na jakąś pozycję menu ajaxem ładuję zawartość tej podstrony do aktualnego contentu. Może mały fragment kodu, o co mi chodzi:

$('#nav li a').click(function() {                 
        var toLoad = $(this).attr('href')+' #content';
        $('#content').slideUp('fast',loadContent);
        $('#load').remove();
        $('#wrapper').append('<span id="load">LOADING...</span>');
        $('#load').fadeIn('fast');
        window.location.hash = $(this).attr('href').substr(0,$(this).attr('href').length-5);
        function loadContent() {
            $('#content').load(toLoad,'',showNewContent());
        }
        function showNewContent() {
            $('#content').slideDown('normal',hideLoader());
        }
        function hideLoader() {
            $('#load').fadeOut('normal');
        }
        return false;
    });

No i wszystko jest pięknie, ale pojawia się problem, kiedy na stronie którą chcę załadować jest nie tylko statyczny tekst i obrazki, ale też kawałek jakiegoś javascriptu. W divie #content mam znacznik <script> z właściwym kodem:

$(function() {
    $("#textbox p").css("display", "none");
    $(".active").css("display", "block");
    $(".img").click(function() {
        var alt = $(this).children("img").attr("alt");
        $(".active").hide("normal");
        $("#"+alt).addClass("active").show("normal");
    });
});

Polega on na tym, że na tej stronie jest parę obrazków, jak na jakiś klikne to ma sie pojawić wybrany paragraf tekstu...
No właśnie. Jak otworzę bezpośrednio tę stronę to wszystko działa, ale jak załaduję ją ajaxem to ten kod nie działa... W ogóle się nie wykonuje, jak dodam tam jakiegoś alerta to też nic nie wyskakuje...

Jak sobie z tym poradzić? Proszę o pomoc.

piternet

0

Podbijam.
Ewentualnie, jeśli nikt nie zna rozwiązanie mojego problemu, to jak najprościej zrobić stronkę z kilkoma podstronami, żeby to ładnie wszystko wyglądało? Mam podstawy teoretyczne, ale brak mi doświadczenia. Jest sens używać PHP? Stronka jest raczej statyczna, trochę Javascriptu i tyle.

1

Sorki, ale nie mam teraz czasu na pełniejszą odpowiedź.

Wpadłeś na dobrze znane, udokumentowane zachowanie. Jeśli funkcji load() przekażesz również ID elementu docelowego, to nie odpala skryptów. Zobacz notkę tutaj http://api.jquery.com/load/ :

Note: When calling .load() using a URL without a suffixed selector expression, the content is passed to .html() prior to scripts being removed. This executes the script blocks before they are discarded. If .load() is however called with a selector expression appended to the URL, the scripts are stripped out prior to the DOM being updated, which is why they are never executed.

Możesz spróbować z funkcją .ajax() zamiast .load(), z tego co pamiętam to ona odpala tagi <script>. Tylko ustaw dobrze opcje, tam chyba trzeba było podać dataType: "html".

1

Kiedyś męczyłem się z podobnym problemem i rozwiązania nie znalazłem bo ono chyba nie istnieje. Chyba że wszystkie potrzebne funkcje załadujesz przy pierwszym wczytaniu strony, a potem będziesz ich tylko używał, wtedy powinny działać. No chyba że nie masz takiej możliwości, to ajax'em nie da się strony wczytać tak żeby skrypty się uruchomiły.

1

@emfałsi:
Rzeczywiście jest tak, że jeśli mamy ciąg znaków i chcemy go wstawić jako fragment dokumentu za pomocą innerHTML, to skrypty w tagach <script> nie zostaną wykonane ze względów bezpieczeństwa.

Można o tym przeczytać w szkicu specyfikacji HTML 5:

"script elements inserted using innerHTML do not execute when they are inserted."
(http://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0)

Mimo to, jak najbardziej da się przeforsować wykonanie tych skryptów. W końcu gdy otrzymujemy HTML w odpowiedzi na żądanie ajaxowe, to ów HTML jest zwracany jako ciąg znaków. Możemy go po prostu wrzucić do własności innerHTML elementu docelowego -- jak robi jQuery -- ale możemy również (niekoniecznie "oprócz") przeparsować ciąg znaków z HTML-em, wyciągnąć fragmenty JavaScriptu pomiędzy znacznikami <script> i odpalić te fragmenty funkcją eval(). Czyli nasz zagnieżdżony kod JS zostanie jednak odpalony.

1

@bswierczyński

Szkoda że nie wiedziałem tego 2 miesiące temu jak z tym walczyłem :) Ale dzięki za info, będę wiedział na przyszłość :)

0

Dzięki wielkie za odpowiedź. Nie miałem wczesniej czasu, teraz do tego siadłem. Zamieniłem funkcję na ajax. Kod javascript wygląda tak:

$(document).ready(function() {

    $('#nav li a').click(function() {                 
        var toLoad = $(this).attr('href')+' #content';
        $('#content').slideUp('fast',loadContent);
        $('#load').remove();
        $('#wrapper').append('<span id="load">LOADING...</span>');
        $('#load').fadeIn('fast');
        window.location.hash = $(this).attr('href').substr(0,$(this).attr('href').length-5);

        function loadContent() {
            $.ajax({
                type: "POST",
                url: toLoad,
                dataType: "html",
                success: function(returnData) {
                    $("#content").html(returnData);
                    showNewContent();
                }
            });
        }
        function showNewContent() {
            $('#content').slideDown('normal',hideLoader());
        }
        function hideLoader() {
            $('#load').fadeOut('normal');
        }
        return false;
    });

}); 

Problem polega na tym, że mimo iż zmienna toLoad zawiera adres wraz z #content to ajax ładuje mi całą tamtą stronę, a nie tylko ten div ;/ I wtedy otwiera mi się strona w stronie. Dlaczego tak się dzieje?

Kombinowałem z czymś w stylu returnData.children("#content"), ale nie działa.

Btw - poleciłby ktoś jakiś sensowny kurs / książkę jquery oraz javascript? Czytałem Head First Javascript, fajna książka, ale czuje, że sporo rzeczy nie wiem.

0

Funkcja ajax() nie obsługuje tego rozszerzenia, które ma funkcja load(), tj. podania na końcu ID (selektora) elementu z odpowiedzi ajaxowej.

Musimy sami wyciągnąć sobie zawartość elementu #content z odpowiedzi. Należy to zrobić w funkcji success. Np. tak (mam nadzieję, że zadziała, piszę na sucho...):

success: function(responseHTML) {
    var $responseWrapper = $("<div />");
        $responseWrapper.append(responseHTML);

        var $responseContent = $responseWrapper.find("#content");
    $("#content").html($responseContent.html());
    showNewContent();
}

Działa to tak, linia po linii:
-Tworzymy tymczasowy kontener jQuery $responseWrapper -- to zwykły <div>.
-Wrzucamy do naszego tymczasowego kontenera cały kod HTML odpowiedzi. Trochę to koślawe, bo do <diva> wrzucamy <body> i całą resztę badziewia z odpowiedzi, ale jest to bezpieczne -- nawet wewnątrz jQuery tak robią.

  • Teraz możemy pracować na naszej odpowiedzi ajaxowej, zawiniętej w kontener, jak na zwykłym obiekcie jQuery. Znajdujemy więc tam element #content, czyli to, co chcemy wyciągnąć z odpowiedzi. Zapamiętujemy kolekcję jQuery z elementem #content z odpowiedzi w zmiennej $responseContent.
    -Ładujemy HTML ze środka elementu #content z odpowiedzi do naszego elementu #content na stronie.

Co do Twojej próby z returnData.children("#content"), to nie działała ona (chyba -- miałbym pewność gdybym mógł odpalić u siebie ten kod) dlatego, że returnData to zwykły HTML, zwykły tekst. On nie ma metody children(). Taką metodę mają kolekcje jQuery. Dlatego powyżej nasz tekst odpowiedzi ładujemy do wewnątrz tymczasowej kolekcji-diva, żeby móc na tej tymczasowej kolekcji wywoływać metody jQuery.

Co do tabulacji na forum, to... po co używasz TAB-ów, jeśli chcesz mieć wszędzie takie same wcięcia? Używasz złego znaku. Przestaw się na spacje, jeśli chcesz osiągnąć taki efekt. Tylko mi nie mów "bo jeden TAB zajmie mi mniej miejsca niż cztery spacje!". Od oszczędzania miejsca to jest kompresor JavaScriptu (np. YUI Compressor). On te spacje usunie.

Ja sam używam w pracy tabulacji, ale nic mi się nigdy nie rozjeżdża. Po pierwsze, stosując tabulacje godzę się na to, że ktoś może mieć ustawioną inną szerokość. I będzie miał większe szczęścia. Ale -- i to po drugie -- nie oznacza to, że coś mu się rozjedzie. Bo TABów używam do WCINANIA, a do WYRÓWNYWANIA używam już spacji.

Widać to na poniższym przykładzie, gdzie każdy TAB oznaczyłem jako >>, a każdą spację jako .:

function fun(arg) {
>>  var foo = arg,
>>  ....bar,
>>  ....baz;
>>  if (foo) {
>>  >>  alert("Foo!");
>>  >>  return;
>>  }
>>  alert("Nie foo.");
}

Wcięcia są robione TAB-ami, bo nie obchodzi mnie, jak będą szerokie. Ale pod deklaracją var wciętą o jeden poziom, zmienne bar ibazsą także wcięte o jeden poziom, a następnie WYRÓWNANE czterema spacjami ze zmiennąfoo. Bo tu nie chodzi o wcięcie, tylko dokładne -- co do znaku! -- wyrównanie nazw zmiennych. Jak ktoś ustawi szerokość tabulacji na 8, to wcięcia się zwiększą, ale nic mi się nie rozjedzie: zmiennebaribaznadal zostaną wyrównane zfoo`.

0

u mnie dziala dynamicznie wczytany kod w znacznikach script
przyklad w zalaczniku

chyba ze zle zrozumialem problem :P

0

@greenmag:
Nie do końca zrozumiałeś problem.

Pisałem już w pierwszym poście, że skrypty nie działają jeśli funkcji load() poda się ID elementu z odpowidzi ajaxowej, z którego to elementu należy zaciągnąć zawartość, TO WTEDY skrypty nie są odpalane. Tak też mówiła cytowana przeze mnie notka.

Zauważ, że Ty nie podałeś tego ID (w ogólności: selektora, nie musi to być ID). Pakujesz do elementu docelowego CAŁĄ odpowiedź. A autor tematu chciał tam zapakować jedynie fragment. Dostawał w odpowiedzi cały dokument HTML (wraz z <head>, <body>, paskami bocznymi itp.), a chciał jedynie wybrać z tego zawartość elementu #content. Więc musiał przekazać funkcji load() selektor -- którego Ty nie musiałeś przekazać) -- i przez to skrypty nie były odpalane.

Czemu autor w ogóle wysyła sobie odpowiedź w formie całego dokumentu HTML, a nie od razu interesującego go fragmentu, tak jak w Twoim kodzie? Ano robi się tak np. wtedy, gdy mamy na stronie menu z niby normalnymi linkami do poszczególnych podstron. Na razie nie mówimy o ajaxie: to zwykłe linki do zwykłych podstron. Oczywiście, linki prowadzą do pełnych dokumentów, wraz z <body> i wszystkim innym. I to działa normalnie: jak ktoś kliknie w taki link, to wczytywana jest kolejna podstrona (z przeładowaniem całego dokumentu!). Na to nakładamy JavaScript z Ajaxem. Nasze skrypty przelatują po linkach menu i dołączają się do zdarzenia click. W momencie kliknięcia, skrypt wyciąga URL z linku i ściąga go asynchronicznie, Ajaxem, blokując jednocześnie normalne zachowanie związane z kliknięciem w link, tj. przeładowanie strony. Przychodzi odpowiedź -- pełny dokument HTML -- skrypty wyciągają z niej obszar zawartości (#content) i ładują znajdujące się w nim elementy do zawartości aktualnej strony.

Rozwiązanie o tyle kuloodporne, że gdy ktoś ma wyłączony JavaScript/nie rozumie ajaxa -- czyli np. jest crawlerem wyszukiwarki -- to widzi normalną, bezajaxową wersję naszej strony. I normalne, działające linki. A jak ktoś ma włączony JS (większość użytkowników), to strona przeładowuje mu się szybciej, dzięki ajaxowi.

Tylko tutaj musimy wyciągać z pełnej odpowiedzi interesujący nas fragment. To wszystko w JavaScripcie. Alternatywne rozwiązanie jest takie, żeby nasze skrypty dołączały do żądania ajaxowego jakiś parametr, np. ajax=true. Serwer, widząc go, zwracałby nie pełną odpowiedź, tylko jej fragment -- pozbawiony znaczników <head>, <body>, pasków bocznych itp., zawierający jedynie zawartość elementu #content.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0