Tovább gondolkodtam a Weboldal "AJAX"-osítása egyszerűen bejegyzéssel kapcsolatban, és rájöttem, hogy maradt pár hiányosság, amit nem kezeltem le. Az egyik ilyen az, hogy mi van akkor ha a linkek nem a gyökértől indulnak, a másik pedig az, hogy mi van, ha <base> elemet használnak az oldalon. Most ezeket is megoldom, és szerintem egy eléggé szép, ugyanakkor majd minden problémára kiterjedő megoldást találtam.
Az egyik probléma, ha <a href="blog.html">Blog</a> jellegű linkeket használnak az oldalon, azaz az linkelt oldal URL-je a mostanihoz képest relatív. Ezt úgy küszöbölhetjük ki, hogy ha ilyen linkekkel találkozunk, akkor azokat átalakítjuk a megfelelő formára. És ha már itt vagyunk, akkor az abszolút URL-lel megadott linkeket is átalakítjuk, így később már csak egyetlen esetet kell kezelni. Ezzel nem csorbul a használhatóság, csupán közös formára hoztuk az összeset.
Ezzel kapcsolatban eszembe jutott, hogy mi van akkor, ha az oldalon <base> elemmel befolyásolják ezen URL-eket. A <base href="http://example.com/blog/"> elem annyit tesz, hogy a <a href="blog.html">Blog</a> formában megadott linkek a <base> által megszabott URLhez képest relatívak, míg <base> nélkül pedig az aktuális oldalhoz képest lennének relatívak. Ami még előjött probléma ezzel kapcsolatban, hogy mit tekintünk aktuális oldalnak.
Amennyiben még nem volt AJAX eseményünk, akkor az aktuális oldal a tényleges aktuális oldal, míg ha volt, akkor a tárolónkon belüli a tároló maga lesz az. Mint látszik ez eléggé megbonyolította a kód működését, ezért majdnem az egészet teljesen újra kellett építeni. Készítettem is két segédfüggvényt. Az egyik megnézi, hogy van-e <base> elem a megadott elemben, és ha igen, akkor annak visszaadja a belső részét. Ebből csinálhattam volna egyszerűen egy jQuery kiegészítőt, de nem éreztem szükségét. A másik pedig az átadott elemben átalakítja a linkeket, hogy azok a gyökérhez képest relatívak legyenek.
Igazából csak ezek változtak, többi működés nagyjából maradt a régi, de kód szintjén így is nagyok a változások.
jQuery(function ($) {
var reInternal = $.urlInternalRegExp(),
id = '#main',
tag = 'a',
title = document.title,
l = window.location,
baseRe = /[^\/]+$/,
body = $(document.body),
// Az alap URL lekérdezése, amennyiben van <base> elem.
getBase = function (elem) {
var docBase = elem.find('head > base[href]'),
match = docBase.length && docBase.attr('href').match(reInternal);
if (match) {
return match[1];
}
},
base = getBase(body) || l.pathname.replace(baseRe, ''), // Relatív URN
containerBase = base, // A tároló relatív URNje
container = $(id), // A tároló
defaultValue = container.html(), // Eltároljuk az eredetit
// Az elem összes linkjét relativvá tesszük a gyökértől.
makeRelative = function (elem) {
elem.find(tag + ':urlInternal(href)').attr('href', function (index, href) {
var match, urn, link = this;
// jQuery 1.3 támogatás
if (typeof href === 'undefined') {
href = $(this).attr('href');
}
// Megnézzük hogy abszolút URI-nek van-e álcázva,
// ha igen, akkor leszedjük róla az abszolút részt.
match = href.match(reInternal);
if (match) {
urn = href.substring(match[0].length - 1);
}
else if (href.charAt(0) === '/') {
// A domainhez képest abszolút
urn = href;
}
else {
// Az aktuális oldalhoz képest relativ
urn = (container.find(tag).filter(function () {
return this === link;
}).length ? containerBase : base) + href;
}
return urn;
});
};
// Bármilyen linkre kattintunk, lekezeljük
$(tag).live('click', function (event) {
var link = $(this),
href = link.attr('href');
// Ha a link belső URL, akkor ahelyett hogy odaugranánk,
// csak megváltoztatjuk a hash-t.
if ($.isUrlInternal(href)) {
// Megváltoztatjuk a hash-t az URI-re
l.hash = '!' + href.replace(/#.*/, '');
// Megakadályozzuk az eseményt.
event.preventDefault();
}
});
// Kezeljük a megváltozott hash eseményt, és kiváltjuk is, hátha már van hash.
$(window).hashchange(function (e) {
var hash = l.hash,
match = hash && hash.match(/^#?!(\/.*)/),
urn = match && match[1];
// Ha megfelelő formátumú a hash, akkor betöltjük a tartalmat a tárolóba.
if (urn) {
// A gyermekekre van csak szükségünk.
$.get(urn, function (data) {
var rscript = /<script(.|\s)*?\/script>/gi, // Script elemek nem maradnak
page = jQuery("<div />").append(data.replace(rscript, "")),
// Betöltődött a tartalom, megnézzük van-e cím, és frissítjük az oldal címét
containerBase = getBase(page) || urn.replace(baseRe, '');
document.title = page.find('head > title').text() ||
page.find('h1:first').text() || title;
makeRelative(container.html(page.find(id).html()));
});
}
else if (!hash || hash === '#') {
// Visszaállítjuk az eredeti tartalmat
container.html(defaultValue);
}
}).hashchange();
// Módosítjuk a linkeket, hogy relatívok legyenek
makeRelative(body);
});A működés megtekinthető a tesztoldalon.






getBase kieg.
Hali!
Először is köszi szépen a kód publikálását!
Nemsokára ki is próbálom, de a kód fejlesztőkörnyezetbe történő bemásolása után tűnt fel egyből, hogy a getBase-nek nincs visszatérési értéke, amennyiben az
if (match)ág nem teljesül (strict warning: anonymous function does not always return a value), tehát az if ág után nem ártana betenni pl. egy
return '';sort, vagy ehhez hasonlót.
Köszi!
Üdv.:
Pete
Ellenőrzés
Mivel meghíváskor egyébként is ellenőrízve van, hogy jött-e vissza érték, ezért erre nincs szükség. Minden függvény alapértelmezetten
undefinedértéket ad vissza, és amennyiben nem truty az visszakapott érték, akkor jön az alapértelmezett:base = getBase(body) || l.pathname.replace(baseRe, ''), // Relatív URNtruty?
truty? Valamiről lemaradtam? :) Mi az?
És nem lenne szebb mégis, ha nem undefined-dal térne vissza, hanem a szerepének megfelelő stringgel? Most ezt ne vedd kötekedésnek, nem olyan céllal írom, csak ha már elemezgetjük, akkor ez sem értelmetlen vita, nem árt megbeszélni, ki milyen gyakorlatot alkalmaz programozás során.
Azért furcsállom, mert más nyelvekben erősen büntet a fordító azért, ha egy olyan függvény nem tér vissza valamilyen értékkel, aminek mégis vissza kellene adnia valamit, hiszen mondjuk ezt deklaráltuk. Tudom, a JavaScript ilyen szempontból más (megengedőbb?), de azért szerintem vannak programozói gyakorlatok, amiket nem árt betartani, hogy logikusabb, követhetőbb legyen a kód.
Félreértés ne essék, a többi rész többnyire követhető.
Erőforrásigény...
Hali!
Csak még egy kérdés.
Amennyire a kódból kihámoztam, tulajdonképpen a kód azt csinálja, hogy a hash megváltozásakor betölti a kívánt oldalt teljes egészében a háttérben, kihámozza belőle a
#mainid-del rendelkező elemet, ami nyilván a főtartalom, majd ezt tölti be a korábbi#main-tartalom helyébe.Ez rendben is van, működik is, köszi szépen!
(Egy dologra érdemes figyelni, ha valakinek mégsem működik, hogy az ajaxSetup metódussal (http://api.jquery.com/jQuery.ajaxSetup/) ne legyen beállítva globálisan a dataType (pl.:
dataType: 'xml'), mert itt ugyetext/htmlMIME-típusú fájlt fogadunk válaszként, így parser errort kapunk, ha mégis XML-re állítjuk a doksit (én így jártam).)Nem túl erőforrásigényes így, hogy minden menüt, minden egyéb oldaltartalmat be kell töltenie ismét (ami nem tartozik a
#main-hez)? Ha már AJAX, esetleg el lehetne küldeni a megfelelő feldolgozó fájlnak ezeket a címeket (ami a hash-ben van), és úgy feldolgozni, hogy csak a kívánt részt tölti be a#main-be, így lehetne némi sávszélességet megspórolni, és a betöltés akár gyorsabb lehetne.Egyáltalán nem kötekedésként mondom, csak esetleges javaslatként/kérdés-felvetésnek.
A kód viszont nagyon hasznos, köszi szépen még egyszer! (Természetesen ahol felhasználom, feltüntetem az oldalad címét a kódban forrásként!) :)
Üdv.!
Az is igaz, hogy...
Igaz, ez már egyéni megvalósítás, saját optimalizáció kérdése.
Fejléc
Mivel a jQuery és még sok más JavaScript keretrendszer is egy speciális HTTP fejlécet küld a kéréssel ezért természetesen megoldható, hogy csak azt a darab HTML-t adjuk csak vissza, amire tényleg szükség van. Ez a fejléc pedig:
X-Requested-With: XMLHttpRequestFejléc + kétszeri oldalletöltés? + JavaScript hiánya?
Igen, és emiatt - ahogy többek közt itt is írják - a következő működik is PHP-kódban:
<?phpdefine('IS_AJAX', isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
?>
Legalábbis jQuery használata esetén valóban megkapjuk ezt a fejlécet (bár persze elvileg ez nincs benne a $_SERVER tömbben).
Nálam eddig működik. :)
=========================
DE van pár számomra égető probléma:
-----
1.) amennyiben valaki a böngésző címsorába beírja példának okáért a következő címet:
http://ezatesztoldal.hu/egyikoldal/valami/#!/masikoldal/masikvalamiakkor a következő történik: először betöltődik az
/egyikoldal/valami/tartalma, majd mivel megtörténik a hashchange event (jQuery plugin szerint), egyből betöltődik a/masikoldal/masikvalamitartalma is, ami egy elég csúnya villanást eredményez a főtartalom keretében, ezenfelül plusz erőforrásokat emészt fel teljesen feleslegesen: kétszeri oldalletöltés, úgy, hogy a felhasználó nyilván csak a hashmark utáni címre kíváncsi.Pl. mi van, ha az adott aloldalunk címét egy felhasználó átküldi egy másiknak a hashmarkot tartalmazó címmel, vagy a felhasználó így könyvjelzőzte az oldalt, esetleg simán új fülön ilyen módon, a kettőskeresztes címmel kiegészítve nyitja meg - minden alkalommal kétszeri oldalletöltés történik, amennyiben nem teszünk ez ellen.
Facebook esetén is alkalmazzák ezt a kettőskeresztet (plusz felkiáltójel) használó AJAX-os címet és betöltést, de ahogy ott a Firebug vagy Dragonfly vagy Chrome Developer stb. "Hálózat" (Net) fülén megnéztem, náluk nem történik felesleges kétszeri oldalletöltés (tehát a hashmark előtti rész betöltése, MAJD az azutánié).
Vajon utólag töltik be a tartalmat a megfelelő keretbe, és ennyi lenne a megoldás?
Ez a módszer viszont megint problémás, a JavaScriptet nem használó felhasználók miatt.
Pont ehhez kapcsolódik a következő kérdésem:
-----
2.) a JavaScriptet nem használó felhasználók ezen címhasználat esetén "ki vannak zárva"? Tudtommal ugyanis a szerver nem kapja meg a hashmark-os címeket, és sajnos ez be is igazolódott a próbálkozásaim során - következésképp a felhasználó nem azt az oldalt fogja látni, amire kíváncsi volt (JS hiánya esetén).
Tehát ha valaki átküldi ezt az "AJAX-osított" címet olyan felhasználónak, akinek a böngészőjében nincs engedélyezve a JavaScript, akkor az nem is kapja meg a megfelelő tartalmat. Mellesleg kipróbáltam, hogy egy hashmarkos címet bepakoltam a másik böngészőbe, ahol szándékosan letiltottam a JS-t, és nem működött még Facebooknál sem (gondolom ott már volt elég idejük gondolkodni a dolgon - persze tény, hogy ott szinte semmi nem működik JS nélkül), tehát gyanítom, hogy erre egyelőre nincs is megfelelő megoldás. De nagyon örülnék, ha mégis lenne, és esetleg ezt megosztanád. :)
Elég zavaró, hogy még mindig gondolni kell a JS-t nem használó felhasználókra is, dehát ez van. :)
-----
3.) Az utolsó: amikor betöltöm az oldalamat, akkor a JS-fájlomban egy switch-case szerkezettel döntöm el, hogy az adott oldal esetén milyen elemekre milyen eseményeket szeretnék kötni, mi az, amit el szeretnék rejteni, megjeleníteni, stb. Tehát a switch-ben az adott oldalcímtől függően hajtok végre bizonyos dolgokat.
Amikor AJAX-szal betöltöm a következő oldalt, akkor ott vannak olyan elemek, amikre az "új" szabályok lennének érvényesek (az oldalcím megváltozása miatt új tartalom lesz, amikre új szabályok vonatkoznak, mint pl. hogy mit jelenítsek meg, mit tüntessek el, milyen gombra legyen kötve egy bizonyos esemény, stb.), ami miatt újból bele kéne menni a switch-case szerkezetbe. De ez csak akkor történne meg, ha újból betölteném az oldalt, az új címmel... Nem tudom, eddig követhető-e. :)
Tulajdonképpen úgy is lehetne intézni, hogy a #main-en belülre pakolom ezeket a script-részeket, és akkor mindig újból érvényessé válna (úgy, hogy kiszedem a kódból azt a részt, ahol eltávolításra kerülnek a scriptek) az új szabály, de az számomra elég csúnya megoldásnak tűnik.
Erre mi lehetne a kerülő megoldás? (Már ha egyáltalán érthető, amit akartam mondani. :P)
Csak pakoljam bele mondjuk egy függvénybe ezeket a dolgokat, amik a switch-case-en belül szerepelnek, és minden hashchange esetén hívjam meg ezt a függvényt?
--------------------
Poetro, bocsánat, hogy ilyen hosszú hsz.-eket írok, tele kérdés-felvetésekkel, de nagyon érdekel a téma, és szeretném minél jobban kivesézni, és valószínűleg ezerszer több tapasztalatod és tudásod van ezen a téren, mint nekem. Jobb a tapasztaltabbtól kérdezni hülyeségeket, mint bukdácsolni. :)
A válaszokat előre is nagyon köszönöm!
Üdv.:
Potyka
Válaszok
Az első kérdésre az a válasz, hogy a Facebook ezen oldalai nem működnek JavaScript nélkül, azaz ott a felhasználó egy teljesen üres oldalt kap, illetve egy
metarefresh-el újratöltődik a nem JavaScriptes változat.<noscript><meta content="0; URL=/?_fb_noscript=1" http-equiv="refresh">
</noscript>
A 3-as kérdésre pedig az a válasz, hogy azt a switch-case szerkezetet le kellene futtatni az új tartalom betöltése után. Nem gondolnám, hogy ez olyan nagy változást jelentene a kódod szempontjából.
$.get(urn, function (data) {
var rscript = /<script(.|\s)*?\/script>/gi, // Script elemek nem maradnak
page = jQuery("<div />").append(data.replace(rscript, "")),
// Betöltődött a tartalom, megnézzük van-e cím, és frissítjük az oldal címét
containerBase = getBase(page) || urn.replace(baseRe, '');
document.title = page.find('head > title').text() ||
page.find('h1:first').text() || title;
makeRelative(container.html(page.find(id).html()));
runSwitchCase(urn);
});
…
function runSwitchCase(urn) {switch (urn) {
case 'xx':
// …
break;
}
}
runSwitchCase(window.location.pathname);