Category Archives: WebDev

Bug w Internet Explorer 7 z oceną 10/10

Nie jest to jakimś szczególnym osiągnięciem znalezienie buga w IE, ale ten jest wyjątkowo ciekawy. Dotyczy rozwalającego się layoutu strony, a konkretnie formularzy. W zasadzie wszystkie pola są wyższe oraz szersze. Co ciekawe kod html i pozostałe elementy strony są identyczne w projekcie frontedu jak i wersji, która jest generowana przez mechanizmy formularzy i widoków. Gdzie więc szukać przyczyny? Pierwszym rozsądnym krokiem wydaje się wznieść ręce ku niebu i soczyście zakląć(chociaż z czasem już człowieka ręce od tego zaboleć mogą). Kopiuję wygenerowany kod html do projektu, który dostałem od “składacza” i wszystko jest ok, znak to, że kod jest jednak taki sam. Dla pewności ustawiam wszystkie dołączane pliki JS i CSS w takiej samej kolejności jak na projekcie – dalej mam to samo. I w tym momencie zauważyłem jedną, jedyną różnicę pomiędzy projektem a moim generowanym kodem: komentarz html zaraz po:

[source language=”html”]

[/source]

Komentarz ten to pozostałość po szablonie wygenerowanym przez NetBeansa, którego to nieuważny frontdeveloper nie usunął 😛 Po dodaniu komentarza początek dokumentu wygląda tak:

[source language=”html”]



[/source]

Odświeżam stronę w IE 7 i co widzę? Ano widzę piękny formularz, bez żadnej z oznak puszystości jakie wcześniej dało się zauważyć! Z niedowierzaniem usunąłem komentarz z projektu i faktycznie, tam również zmienił się wygląd wszystkich formularzy. Widziałem już wiele bugów w IE, ale ten jest wyjątkowy, szczególny, dlatego daję mu 10/10. Problem dotyczy jedynie IE 7.

Poniżej zamieszczam screenshot’y formularza bez oraz z komentarzem(kliknij aby powiększyć).

ie7_bug_orig

ie7_bug_comm

Pozdrowienia dla teamu developerów IE!

Number of Views :999

ZendFramework: atrybuty labeli w elementach radio(element multi)

Budując aplikacje z użyciem Zend Frameworka nie sposób nie wykorzystać potężnego narzędzia jakim jest Zend_Form i dekoratory. W ostatnim projekcie musiałem dodać atrybuty do labeli w elemencie Zend_Form_Element_Radio(dziedziczący po Zend_Form_Element_Multi). Docelowy kod miał wyglądać następująco:

[source language=”html”]



[/source]

Problem w tym, że za pomocą dekoratorów nie dodamy atrybutów do labeli okalających inputy. Rozwiązaniem problemu jest ustawienie odpowiednich atrybutów do elementu:

[source language=”php”]
$element = new Zend_Form_Element_Radio(‘e_name’);
$element->addMultiOptions(array(
‘1’ => ‘Tak’,
‘0’ => ‘Nie’,
))->setValue(‘1’)->setLabel(‘Czy naprawa gwarancyjna?’)
->setAttribs(array(‘class’ => ‘checkbox’, ‘label_class’ => ‘inline lh28’))
->setSeparator(”);
[/source]

każdy atrybut ustawiany metodą setAttribs() z prefiksem label_ lub label będzie dotyczył tych okalających inputy, pozostałe będą dodane do głównego labela elementu.

Number of Views :38022

Zwracanie danych różnego typu poprzez serwer RPC

Pisząc interaktywną aplikację internetową, gdzie część komunikacji użytkownika z naszą aplikacją chcemy przenieść na stronę AJAXową z wykorzystaniem serwera RPC, możemy natrafić na konieczność zwrócenia tablicy danych zamiast powiedzmy pojedynczej wartości. W swojej pracy posługuję się jQuery i na tej bibliotece oprę swój opis. Do komunikacji z serwerem RPC wykorzystuję jsonRPC(źródło http://www.json.org/). Podstawowe biblioteki wyglądają mniej więcej tak:

[sourcecode language=’javascript’]
(function($)
{
/* strip html */
var regexp = /<("[^"]*"|'[^']*'|[^'">])*>/gi;
$.stripHtml = function (str){
return str.replace(regexp,””);
}
$.fn.stripHtml = function() {
this.each(function() {
$(this).html($.stripHtml($(this).html()));
});
return $(this);
}
/* jsonRpc */
$.jsonRpc = $.jsonRpc || function(options) {
var ajaxOptions = {
type: ‘POST’,
contentType: ‘application/json’,
dataType: ‘json’,
processData: false
};
var data = {
version: options.version || ‘2.0’,
method: options.method || ‘system.listMethods’,
params: options.params || [],
id: options.id || Math.round(Math.random()*10000)
};
$.each(data, function(i){ delete options[i] });
function send() {
options.data = JSON.stringify(data);
$.ajax($.extend(ajaxOptions, options));
}
if (typeof JSON == ‘undefined’) {
$.getScript(‘http://www.json.org/json2.js’, function(){ send() });
} else {
send();
}
return $;
}
})(jQuery);
/* JSON */
if (!this.JSON) {
JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[u0000u00adu0600-u0604u070fu17b4u17b5u200c-u200fu2028-u202fu2060-u206fufeffufff0-uffff]/g, escapable = /[\"x00-x1fx7f-x9fu00adu0600-u0604u070fu17b4u17b5u200c-u200fu2028-u202fu2060-u206fufeffufff0-uffff]/g, gap, indent, meta = { // table of character substitutions 'b': '\b', 't': '\t', 'n': '\n', 'f': '\f', 'r': '\r', '"' : '\"', '\': '\\' }, rep; function quote(string) { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; if (value &amp;amp;amp;&amp;amp;amp; typeof value === 'object' &amp;amp;amp;&amp;amp;amp; typeof value.toJSON === 'function') { value = value.toJSON(key); } if (typeof rep === 'function') { value = rep.call(holder, key, value); } switch (typeof value) { case 'string': return quote(value); case 'number': return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': return String(value); case 'object': if (!value) { return 'null'; } gap += indent; partial = []; if (Object.prototype.toString.apply(value) === '[object Array]') { length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } v = partial.length === 0 ? '[]' : gap ? '[n' + gap + partial.join(',n' + gap) + 'n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } if (rep &amp;amp;amp;&amp;amp;amp; typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { k = rep[i]; if (typeof k === 'string') { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } v = partial.length === 0 ? '{}' : gap ? '{n' + gap + partial.join(',n' + gap) + 'n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { var i; gap = ''; indent = ''; if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } } else if (typeof space === 'string') { indent = space; } rep = replacer; if (replacer &amp;amp;amp;&amp;amp;amp; typeof replacer !== 'function' &amp;amp;amp;&amp;amp;amp; (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } return str('', {'': value}); }; } if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { var j; function walk(holder, key) { var k, v, value = holder[key]; if (value &amp;amp;amp;&amp;amp;amp; typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } if (/^[],:{}s]*$/. test(text.replace(/\(?:["\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). replace(/"[^"\nr]*"|true|false|null|-?d+(?:.d*)?(?:[eE][+-]?d+)?/g, ']'). replace(/(?:^|:|,)(?:s*[)+/g, ''))) { j = eval('(' + text + ')'); return typeof reviver === 'function' ? walk({'': j}, '') : j; } throw new SyntaxError('JSON.parse'); }; } })(); [/sourcecode] Kolenym krokiem jest ustalenie kontrolera dla zapytań ajaxowych. Powiedzmy niech to będzie kontroler ajax. Warto zaznaczyć, że startowanie mechanizmu MVC w ZendFramework w mojej aplikacji odbywa się jeszcze w bootstrapie, w takim razie musimy wyłączyć mechanizm widoków i layout dla naszego kontrolera, aby zwracane dane były dokładnie tym co nas interesuje, a nie dodatkowy były opakowane w jakiś szablon layoutu. [sourcecode language='php'] class AjaxController extends Zend_Controller_Action { private $_server = null; public function init() { parent::init(); Zend_Layout::resetMvcInstance(); Zend_Controller_Front::getInstance()->setParam(‘noViewRenderer’, true);
$this->_server = new Zend_Json_Server();
}
}
[/sourcecode]

W metodzie init ustalam również iż nie będę wykorzystywał viewRenderera oraz tworzę nową instancję Zend_Json_Server. Kolejnym krokiem jest napisanie akcji w naszym kontrolerze. Będą to proste metody gdzie ustawimy klasy obsługujące poszczególne akcje

[sourcecode language=’php’]
public function answerAction()
{
$this->_server->setClass(‘Model_Ajax_Forest’);
}
public function postDispatch()
{
$this->_server->handle();
}
[/sourcecode]

dla akcji answer obsługę metod wywołania przerzucam na klasę Model_Ajax_Forest. W metodzie postDispatch zaczynam obsługę wywołania, chociaż równie dobrze mógłbym to zrobić w akcji answer, aczkolwiek takie rozwiązanie daje mi większą elastyczność.

Dla jasności klasa Model_Ajax_Forest to zwykła klasa z tym, że musi być udokumentowana, czyli każda metoda musi mieć komentarz do samej siebie jakie parametry przyjmuje jakich typów są to parametry oraz jakie typy wartości zwraca etc. przykładowa klasa może wyglądać tak:

[sourcecode language=’php’]
_zmienna = microtime();
}
/**
* Metoda zwraca sume i roznice dwoch parametrow.
*
* @param int $param1
* @param int $param2
* @return array
*/
public function pro($param1, $param2)
{
$sum = array($param1 + $param2, $param1 – $param2);
return $sum;
}
}
[/sourcecode]

powyższy przykład to prezentacja metody która może zwracać tablicę, ale tak naprawdę w naszym przypadku to rozwiązanie nie zadziała. Dlaczego? Odpowiedź jest zaskakująco prosta taki zapis:

[sourcecode language=’php’]
$zmienna = array(10, 20);
return $zmienna;
[/sourcecode]

to nie to samo co

[sourcecode language=’php’]
return array(10, 20);
[/sourcecode]

Otrzymany typ danych w metodzie do obsługi ajaxowego żądania jest determinowany przez typ znajdujący się za słowem kluczowym return w metodzie odpowiedzialnej za obsługę tego żądania. Mając powiedzmy taką metodę przypiętą do jakiegoś przycisku

[sourcecode language=’javascript’]

function handle(param1, param2)
{
jQuery.jsonRpc({
url: “/ajax/answer”,
method: “pro”,
params: [param1, param2],
success: function(data){
if(data.error) {
alert(data.error.message);
return;
}
else
{
if(data.result == false)
{
alert(“ERROR: Błąd systemu, proszę o kontakt z administracją”);
return;
}
alert(data.result[0];
alert(data.result[1];
}
},
error: function(data, status)
{
alert(“ERROR: “+data.responseText);
},
beforeSend: function()
{
}
});
}

[/sourcecode]

Otrzymamy błąd ponieważ oczekujemy otrzymania tablicy a w metodzie klasy Model_Ajax_Forest zwracamy zmienną tablicową a nie tworzoną dynamicznie tablicę. Pytanie które się nasuwa to dlaczego? Nie starając się wodzić kogokolwiek za nos przyznam szczerze, że nie wiem. Jak do tej pory nie byłem zmuszony dojść dlaczego tak się dzieje, również przez brak czasu tak naprawdę, ale jest to jedna z tych rzeczy, które oznaczam kategorią “ćwieków”. Cały czas w podświadomości siedzi to dlaczego i w wolnych chwilach staram się dojść do tego. Bardzo chętnię przyjmę komentarze wyjaśniejące cały procedens.

Jak idzie bardzo łatwo zauważyć, cały ciężar obsługi wywołania przenosimy na ZendFramework, który świetnie sobie z tym radzie, do nas tak naprawdę należy jedynie obsługa obróbki danych.

Number of Views :1607