Blog webdeveloperski Patryk yarpo Jar

Ajax w oparciu o pływającą ramkę

Autor wiadomości Marzec 23, 2011

Ajax w szerszym znaczeniu oznacza komunikację z serwerem [wymianę danych z serwerem] bez konieczności przeładowania strony. Często mówiąc Ajax ma się na myśli także DHTML.

Nie o tym jednak będzie ten artykuł. Mam zamiar pokazać jak w łatwy sposób korzystać z dobrodziejstw Ajaksa bez jednego z filarów - obiektu XMLHttpRequest. Zastąpimy go pływającą ramką, a ramkę tę obudujemy w obiekt XMLHttpIframeRequest, który ma identyczny interfejs z natywnym ajaksowym obiektem.

Zasada działania

Tak naprawdę chodzi tylko o to, aby:

  1. Stworzyć pływającą ramkę, która będzie służyć jako medium komunikacyjne z serwerem.
  2. Stworzyć formularz, dzięki któremu będzie można wysyłać dane na serwer (będzie miał ustawiony target na tworzoną pływającą ramkę).
  3. Stworzyć obiekt symulujący XMLHttpRequest (będzie posiadał taki sam interfejs).

Uwaga: Kod pokazany poniżej nie działa prawidłowo w IE. Problemem jest obsługa zdarzenia onload. Jako, że raczej jest to artykuł o pewnej idei, pisany dla treningu, stwierdziłem, że mogę to zignorować. Pamiętaj jednak o tym, gdybyś chciał tego używać w swoich projektach.

Kod

Najnowsza wersja kodu dostępna jest na svn:

 

// Obiekt imitujacy XMLHttpRequest za pomoca Iframe
// autor: Patryk yarpo Jar, yarpo.pl
// data: 21-03-2011 r.

var XMLHttpIframeRequest = function()
{
	var oSelf = { readyState  : CONST('UNSET') };

	var oDiv = null
		bAsync = true,
		bXml = false,
		aHeaders = [];

	function CONST(k)
	{
		var c = {	UNSET : 0,  OPENED : 1, LOADING : 3, READY : 4,
				ID : 'yXMLHttpIframeRequest' };
		return c[k];
	}

	function fInit(method, url)
	{
		if (null === oDiv)
		{
			var id = CONST('ID');
			oDiv = document.createElement('div');
			oDiv.style.border = "none";
			oDiv.style.width = '0';
			oDiv.style.height = '0';
			oDiv.innerHTML = '';
			document.body.appendChild(oDiv);
			oDiv.iframe = document.getElementById(id);
			oDiv.form = document.createElement('form');
			oDiv.form.setAttribute('id', id + '_form');
			oDiv.form.target = id;
			oDiv.appendChild(oDiv.form);
		}
		oDiv.form.action = url;
		oDiv.form.method = method;
	}

	function fFillOutForm(data)
	{
		if ('string' !== (typeof data).toLowerCase())
		{
			return;
		}
		var params = data.split('&');
		oDiv.form.innerHTML = '';

		for(var i = 0; i < params.length; i++)
		{
			var keyVal = params[i].split('=');
			oDiv.form.innerHTML += '';
		}
	}

	function fOpen(method, url, async)
	{
		fInit(method, url);
		fSetSelfVals(CONST('OPENED'));
		bAsync = (false === async) ? false : true;
	}

	function fSend(data)
	{
		fFillOutForm(data);
		if (true === bAsync)
		{
			fSetSelfVals(CONST('LOADING'));
			oSelf.onreadystatechange.apply(oSelf);

                        // przeglądarki
			oDiv.iframe.onload = function()
			{
				fSetSelfVals(CONST('READY'),
					document.getElementById(CONST('ID')).contentWindow.document);
				oSelf.onreadystatechange.apply(oSelf);
			}
                        oDiv.form.submit();
		}
		else
		{
			alert('Połączenia synchroniczne nie są obsługiwane');
		}
	}

	function fSetSelfVals(readyState, response)
	{
		oSelf.readyState = readyState;
		if (true === bXml)
		{
			oSelf.responseXML = response;
		}
		else if (response)
		{
			oSelf.responseText = response.body.innerHTML;
		}
	}

	function fAbort()
	{
		fSetSelfVals(CONST('UNSET'));
		oSelf.onreadystatechange = null;
		bXml = false;
	}

	function fSetRequestHeader(k, v)
	{
		aHeaders.push({'header' : k, 'value' : v});
		bXml = ('content-type' === k.toLowerCase() && -1 !== v.indexOf('xml'));
	}

	function fGetRequestHeader()
	{
		if (0 == arguments.length)
		{
			var headCont = '';
			for(var i = 0; i < aHeaders.length; i++)
			{
				headCont += aHeaders[i].header + ': ' + aHeaders[i].value +'\n';
			}
			return headCont;
		}
		else if (1 == arguments.length)
		{
			for(var header in aHeaders)
			{
				if (header.header.toLowerCase() === arguments[0].toLowerCase())
				{
					return header.header + ': ' + header.value +'\n';
				}
			}
		}
	}

	return oSelf = {
		open : fOpen,
		send : fSend,
		abort : fAbort,
		setRequestHeader : fSetRequestHeader,
		getResponseHeader : fGetRequestHeader,
		getAllResponseHeaders : fGetRequestHeader,
		onreadystatechange : null,
		status : 200, // mock
		statusText : 'OK' // mock
	};
};

Wykorzystanie

Dla danych w formacie plain-text:

function test()
{
	var oClient = new XMLHttpIframeRequest();
	oClient.open('POST', 'test.php');
	oClient.onreadystatechange = function()
	{
		if (4 == this.readyState)
		{
			alert(this.responseText);
		}
	}
	oClient.send('a=1&b=2&c=4');
}

demo online

Dla danych w formacie XML:

function test()
{
	var oClient = new XMLHttpIframeRequest();
	oClient.open('POST', 'test.xml');
	oClient.setRequestHeader("Content-Type", "text/xml");
	oClient.onreadystatechange = function()
	{
		if (4 == this.readyState)
		{
			alert(this.responseXML.getElementsByTagName('message')[0].firstChild.nodeValue);
		}
	}
	oClient.send(null);
}

demo online

zawartość pliku 'test.xml':

<?xml version='1.0' encoding='ISO-8859-1'?>
<note>
   <from>Jani</from>
   <to>Tove</to>
   <message>Witaj stary druhu!</message>
</note>

W przypadku wykorzystania XML koniecznym było stosowanie metody GET. W przeciwnym wypadku otrzymywałem (na tym serwerze - lokalnie było ok) błąd 405.

Braki

Niestety pływająca ramka jedynie imituje działanie obiektu XMLHttpRequest, stąd nie wszystko działa jak byśmy tego chcieli.

Różnice:

  • Zawsze `status' jest równy 200 (nie wiem, czy istnieje możliwość przechwycenia informacji o kodzie błędu w pływającej ramce, np. 404, czy 500). Podobnie `statusText' zawsze ma wartość "OK". Dlatego nie warto ich sprawdzać. Wartości te są ustawione na sztywno
  • rozwiązanie to nie działa w pełni w IE. Aby to naprawić należałoby zmienić fragment:
    oDiv.iframe.onload = function() ... 

    W tym fragmencie wykłada się IE. Nie przechwytuje ty zdarzenia. Nie działały także addEventLIstener, ani attacheEvent. Jeśli ktoś to rozwiążę, proszę o informację w komentarzu. Z tego, co ostatnio się dowiedziałem, należy użyć zdarzenia `onreadystatechange' i `status' == "completed".

  • ustawianie nagłówków sprawdza jedynie, czy oczekiwaną wartością jest xml. Cała reszta owszem - jest zapisywane, ale nie ma wpływu na działanie obiektu. Można co najwyżej pobrać wartość ustawionych nagłówków
  • jedyne wspierane metody to 'GET' i 'POST'.
  • nie ma obsługi wywołań synchronicznych. Choć to powinno dać się zrobić bez większego problemu. Może kiedyś dorobię
  • `abort' tak naprawdę czyści jedynie obsługę zdarzenia `onload', a nie anuluje żądania. Może się zdarzyć, że po `abort' przypiszemy nowy callback `onload', i zostanie on wywołany nim tego będziemy oczekiwać :/

Gdyby ktoś rozwiązał którąś z bolączek, byłbym wdzięczny za informację.

Warto przeczytać:

Komentarze (3) Trackbacks (0)
  1. Szkoda że nie działa w cross domain… 😛

  2. Jak nie działa w cross domain?

    Przecież w pływającą ramkę MOŻNA wczytać zasób z innej domeny. Powyższy kod nie działa w IE. Dlaczego? Nie działa atrybut `onload’. Rozwiązanie problemu jest identyczne jak w poradzie:

    http://www.yarpo.pl/2011/05/07/json-with-padding-czyli-zdalny-ajax/#onreadystatechange

    Jeśli nadal nie działa jak oczekiwałbyś, to proszę daj znać. Myślę, że powinno działać 🙂

  3. O, to ciekawe 🙂

    Dzięki za uzupełnienie


Leave a comment

 

Brak trackbacków.