Jak stworzyć dodatek do FireFoxa

background image

Technologie

Jeżeli tworzyłeś już wcześniej strony oparte na DHTML prawdopodobnie bardzo szybko
zrozumiesz ideę tworzenia rozszerzeń do FireFox. Wiedza którą posiadasz z dziedziny znanych już
wcześniej technologii wymaga jedynie przestawienia się na inny "framework", jakim jest
środowisko FireFox. Rozszerzenia FireFox opierają się na 4 poniższych technologiach i warstwach:

XPCOM

XUL

JavaScript

CSS

XPCOM (Cross Platform Component Object Model)

XPCOM to crossplatformowy model komponentów podobny do rozwiązania Microsoft COM.
Model ten może być używany i implementowany w wielu językach, w tym JavaScript - języku
wykorzystywanym przez dodatki FireFox. Ogólnie XPCOM oferuje zestaw podstawowych
komponentów i klas m.in. do obsługi plików, zarządzania pamięcią, zarządzania wątkami, ... W
przypadku XPCOM FireFox większość komponentów jest częścią silnika Gecko.
Innymi słowy XPCOM to warstwa umożliwiająca dostęp do operacji, które nie są możliwe
bezpośrednio z pozycji JavaScript. Przykładem jest chociażby dostęp do plików. Łącznikiem
pomiędzy JavaScript a XPCOM jest interfejs

XPConnect

.

XUL

XUL jest językiem opisu interfejsu użytkownika opartym na język XML. XUL posiada zbiór
podstawowych kontrolek i widgetów używanych w aplikacjach (przyciski, pulldowny, menu, ...).
Najprostszym sposobem, aby przekonać się czym dokładnie jest XUL będzie wpisanie w polu
adresu przeglądarki:

chrome://browser/content/browser.xul

background image

Proszę bardzo, mamy przeglądarkę w przeglądarce! Teraz wystarczy otworzyć Firebug i skorzystać
z funkcji inspect. Mam nadzieję, że jest to dość obrazowe wyjaśnienie roli jaką pełni XUL w
FireFox :-)

JavaScript

Javascript jest warstwą logiki rozszerzeń i łącznikiem z XPCOM i XUL (dzięki DOM).

CSS

Warstwa opisu prezentacji XUL.


Środowisko developerskie

Aby móc efektywnie pisać dodatki do FF musimy przygotować odpowiednie środowisko
developerskie. W pierwszej kolejności należy stworzyć nowy profil FireFoxa. W tym celu
tworzymy nowy skrót:

firefox.exe -no-remote -P dev

Po jego uruchomieniu powinno pojawić się okno zarządzania profilami przeglądarki. Założymy tam
nowy profil 'dev'.
Po uruchomieniu nowego profilu wpisujemy w oknie adesu about:config i zmieniamy
konfigurację następujących zmiennych, które będą przydatne przy testowaniu pisanych rozszerzeń:

nglayout.debug.disable_xul_cache - true

browser.dom.window.dump.enabled - true
javascript.options.showInConsole - true

javascript.options.strict - true

Jeżeli któraś z powyższych zmiennych nie istnieje, należy ją dodać naciskając prawy przycisk
myszy i wybierając Dodaj ustawienie typu -> Wartość logiczna (Boolean)
Warto również zainstalować bardzo pomocne dodatki:

Firebug

oraz

Chromebug

. Chromebug to

narzędzie napisane na bazie Firebug pracujące na warstwie chrome XUL, czyli warstwie niższej od
tej, na której pracuje Firebug. Po instalacji dodatku Chromebug należy uruchomić FireFox z
dodatkowym parametrem:

firefox.exe -no-remote -P dev -chromebug

Struktura plików i katalogów rozszerzeń FireFox

Po dodaniu i skonfigurowaniu nowego profilu w FireFox możemy już rozpocząć pracę nad
własnym dodatkiem. Nowo dodany profil będzie domyślnie dostępny w podobnej ścieżce:

C:\Documents and Settings\nazwa_uzytkownika\Dane
aplikacji\Mozilla\Firefox\Profiles\mhawpb4j.dev\

Tam też utworzymy nowy katalog, w którym znajdzie się nasz dodatek:

background image

extensions\moj_dodatek@dowolna_najlepiej_wlasna_domena.com

Przyjęło się, że nazwa katalogu (również ID dodatku) ma postać adresu e-mail. Jest to jednak rzecz
umowna, możemy go nazwać zupełnie dowolnie.
Wewnątrz nowo utworzonego katalogu należy stworzyć odpowiednią strukturę plików oraz
podkatalogów:

install.rdf

chrome.manifest
content/

locale/
skin/

Pliki oraz ich strukturę opiszę poniżej. Warto zwrócić uwagę na trzy powyższe katalogi tzw.
pakietów:

content - Zawiera pliki XUL oraz JavaScript.

locale - Tutaj znajdują się pliki językowe interfejsu.

skin - Pliki związane z wizualizacją GUI, takie jak pliki CSS i obrazki.

Budujemy dodatek

W tutorialu tym spróbujemy zbudować dodatek, który będzie zapisywał w pliku tekstowym
wyszukiwane w Google wyrażenia oraz ich datę. Nazwiemy go GoogleQueryLog. Dodatek bardzo
prosty, ale może przy okazji komuś się przyda :-).

Do zrobienia mamy następujące elementy:

Warstwa logiki (JavaScript) odpowiedzialna za zbieranie informacji o wyszukiwanych
wyrażeniach. Będzie ona realizowała:

przechwycenie zdarzenia zmiany adresu w polu adresu przeglądarki

sprawdzenie czy adres URL odpowiada wyrażeniu regularnemu odpowiadającemu
URI wyszukiwania w google

jeżeli adres będzie pasował do wzorca zostanie z niego pobrane wyszukiwane
wyrażenie

wyrażenie zostanie zapisane w pliku tekstowym wraz z datą

Nowa pozycja w menu przeglądarki Google Query Log -> Zobacz logi

Okno wyświetlające logi z pliku tekstowego


W katalogu extension profilu 'dev' FireFox zakładamy katalog: googlequerylog@moje.testy.
Budujemy w nich opisaną powyżej strukturę katalogów i plików.

chrome.manifest

Plik chrome.manifest rejestruje pakiet dodatku chrome w FireFox. Zawartość pliku
chrome.manifest będzie następująca:
content googlequerylog content/
skin googlequerylog classic/1.0 skin/classic/
locale googlequerylog en-US locale/en-US/
locale googlequerylog pl-PL locale/pl-PL/

background image

overlay chrome://browser/content/browser.xul
chrome://googlequerylog/content/overlay.xul
Pierwsza linia rejestruje pakiet typu content. googlequerylog to nazwa pakietu, content/
to relatywna ścieżka do folderu gdzie będą znajdować się pliki źródłowe. W sposób analogiczny
definiowane są pozostałe pakiety: skin i locale. Nasz dodatek będzie dostępny w dwóch
wersjach językowych: angielskiej(US) i polskiej. Dla pozostałych wersji językowych FF
domyślnym językiem interfejsu dodatku będzie en-US.
Ostatnia linia określa tzn overlay. Overlay to technika pozwalająca na nałożenie na warstwę
dokumentu XUL kolejnej warstwy. Wyżej podałem przykład wpisania adresu
chrome://browser/content/browser.xul w polu adresu przeglądarki. browser.xul
to dokument XUL okna przeglądarki FireFox. Na tą warstwę nałożymy własny dokument XUL.
Potrzebne będzie nam to do tego, aby osadzić dodatkowo menu w przeglądarce opisane w
założeniach naszego przykładowego dodatku.


install.rdf

Jest to dokument XML zawierający dane dotyczące dodatku, które są wymagane do jego instalacji
w FireFox. Dokument ten będzie miał postać:

01.<?xml version="1.0"?>
02.<RDF xmlns="

http://www.w3.org/1999/02/22-rdf-syntax-ns

#"

xmlns:em="

http://www.mozilla.org/2004/em-rdf

#">

03.<Description about="urn:mozilla:install-manifest">

04.<!-- Unikalny identyfikator dodatku -->
05.<em:id>googlequerylog@moje.testy</em:id>
06.<!-- Informacje, że jest to dodatek do FF -->
07.<em:type>2</em:type>
08.<!-- Nazwa dodatku która będzie wyświetlana w menadżerze
dodatków FF -->
09.<em:name>Google Query Log</em:name>
10.<!-- Wersja dodatku -->
11.<em:version>0.0.1</em:version>
12.<!-- Opis który będzie widoczny w menadżerze dodatków FF -->
13.<em:description></em:description>
14.<!-- Autor -->
15.<em:creator>mturek</em:creator>
16.<!-- Strona dodatku -->
17.<em:homepageURL>

http://moje.testy/<

;/em:homepageURL>

18.<em:targetApplication>

19.<Description>

20.<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
21.<!-- Minimalna wersja FF którą obsługuje dodatek -->
22.<em:minVersion>3.0</em:minVersion>
23.<!-- Maksymalna wersja FF którą obsługuje dodatek -->
24.<em:maxVersion>3.6.*</em:maxVersion>

25.</Description>

26.</em:targetApplication>

27.</Description>

28.</RDF>

background image

content/overlay.xul

Plik overlay.xul umieścimy w katalogu content, czyli wskazanym w pliku
chrome.manifest:

01.<?xml version="1.0"?>
02.<!DOCTYPE window SYSTEM
"

chrome://googlequerylog/locale/qooglequerylog.dtd

">

03.<overlay id="googlequerylogOverlay"
xmlns="

http://www.mozilla.org/keymaster/gatekeeper/there.is.only.x

ul

">

04.<script type="application/javascript"
src="

chrome://googlequerylog/content/service.js

"/>

05.<menubar id="main-menubar">

06.<menu id="googlequerylog-menu" label="Google Query Log"
insertafter="helpMenu">

07.<menupopup id="sitehoover-menupopup">

08.<menuitem id="sitehoover-menu-showlog"
label="&overlay.menuShowLogs;"
oncommand="Service.dialogLogOpen()" />

09.</menupopup>

10.</menu>

11.</menubar>

12.</overlay>
Teraz pora uruchomić developerski profil FireFoxa.

Powinniśmy w menu zobaczyć nową pozycję:

Wytłumaczę w jaki sposób na podstawie powyższego pliku XUL została dodana nowa pozycja
menu. Używając Chromebug skorzystaj z funkcji inspect i najedź myszą na górną belkę menu. W
DOM zauważysz, że w głównym dokumenci XUL przeglądarki
chrome://browser/content/browser.xul jest obiekt o ID main-menubar. Ten sam ID
jest użyty w naszym dokumencie XUL. FireFox uruchamiając nasz dodatek połączył oba
dokumenty wiedząc do którego obiektu ma się "przykleić". W kolejnym tagu naszego XUL (menu)
jest atrybut insertafter który ma wartość helpMenu. Jest ID elementu menu "Pomoc" ...

background image

Prawda, że proste? :-)
W naszym dokumencie XUL dołączony jest plik
chrome://googlequerylog/content/service.js, czyli nasza lokalna biblioteka
skryptu JS, która za chwilę stworzymy. Będzie ona zawierała metody potrzebne do realizacji
naszego zadania.


content/service.js

Pora zabrać się za logikę naszej aplikacji, czyli skrypt JavaScript obsługujący nasz dodatek. Plik
umieścimy również w katalogu content (analogiczna ścieżka, jak ta określona w stworzonym
dokumencie XUL).

001.try {

002.netscape.security.PrivilegeManager.enablePrivilege("Universa
lXPConnect");

003.} catch (e) {

004.alert("Dostęp do pliku niedozwolony (FF).");

005.}
006.
007.window.addEventListener('load', function () {

008.gBrowser.addEventListener('DOMContentLoaded', function ()
{

009.Service.parseUrl(gBrowser.currentURI.spec);

010.}, false)

011.}, false);

012.
013.
014.var Service = {

015.extensionId: 'googlequerylog@moje.testy',
016.logFilename: 'qooglequerylog.log',
017.query: '',
018.logFile: null,
019.
020.dialogLogOpen: function(){

021.window.openDialog('

chrome://googlequerylog/content/showlo

gs.xul

','','chrome,centerscreen,modal');

022.},
023.
024.dialogLogInit: function(){

025.var logContent = Service.getQueryLog();
026.document.getElementById('googlequerylog-textbox').value =
logContent;

027.},
028.
029.parseUrl: function(_url){

030.reg = new RegExp(/^http:\/\/www.google.(pl|
com)\/.*(&|\?)q=(.*?)&/);
031.reg_dest = reg.exec(_url);
032.if (reg_dest != null){

background image

033.var query = decodeURIComponent(reg_dest[1].replace(/\
+/g, '%20'));
034.if (query != this.query){

035.this.saveQueryLog(query);

036.}

037.}
038.this.query = query;

039.},
040.
041.fileInit: function(){

042.var extension =
Components.classes["@mozilla.org/extensions/manager;1"]

043..getService(Components.interfac
es.nsIExtensionManager)
044..getInstallLocation(this.extens
ionId)
045..getItemLocation(this.extension
Id);

046.var filePath = extension.path + "\\" + this.logFilename;;
047.this.logFile =
Components.classes["@mozilla.org/file/local;1"]

048..createInstance(Components.interfac
es.nsILocalFile);

049.this.logFile.initWithPath(filePath);
050.if(this.logFile.exists() == false){

051.this.logFile.create( Components.interfaces.nsIFile.NOR
MAL_FILE_TYPE, 420);

052.}

053.},
054.
055.saveQueryLog: function(_query){

056.var now = new Date();
057.var content = this.getDate() + '|' + _query + '\r\n';
058.this.fileInit();
059.var outputStream =
Components.classes["@mozilla.org/network/file-output-
stream;1"]

060..createInstance(Components.
interfaces.nsIFileOutputStream)
;

061.var convertOutputStream =
Components.classes["@mozilla.org/intl/converter-output-
stream;1"]

062..createInstance(Component
s.interfaces.nsIConverterOutp
utStream);

063.outputStream.init(this.logFile, 0x02 | 0x08 | 0x10, 0666,
0);
064.
065.// konwertujemy do UTF-8 i zapisujemy plik
066.convertOutputStream.init(outputStream, "UTF-8", 0, 0);
067.convertOutputStream.writeString(content);

background image

068.convertOutputStream.close();

069.},
070.
071.getQueryLog: function(){

072.this.fileInit();
073.var inputStream =
Components.classes["@mozilla.org/network/file-input-
stream;1"]

074..createInstance(Components.in
terfaces.nsIFileInputStream);

075.var convertInputStream =
Components.classes["@mozilla.org/intl/converter-input-
stream;1"]

076..createInstance(Componen
ts.interfaces.nsIConverterIn
putStream);

077.inputStream.init(this.logFile, 0x01, 00004, null);
078.convertInputStream.init(inputStream, 'UTF-8', 1024,
0xFFFD);
079.convertInputStream.QueryInterface(Components.interfaces.n
sIUnicharLineInputStream);
080.var outputArray = new Array();
081.if (convertInputStream instanceof
Components.interfaces.nsIUnicharLineInputStream) {

082.var line = {};
083.var cont;
084.do {

085.cont = convertInputStream.readLine(line);
086.outputArray.push(line.value.split("|"));

087.} while (cont);

088.}
089.convertInputStream.close();
090.outputArray.reverse();
091.var output = '';
092.for (var i = 0; i < outputArray.length; i++ ){

093.if (outputArray[i][1]){

094.output += outputArray[i][0] + ' - ' +
outputArray[i][1] + "\r\n";

095.}

096.}
097.return output;

098.},
099.
100.getDate: function(){

101.var d = new Date();
102.function pad(n){

103.return n<10 ? '0'+n : n

104.}
105.return d.getFullYear()+'-'

106.+ pad(d.getMonth()+1)+'-'
107.+ pad(d.getDate())+' '

background image

108.+ pad(d.getHours())+':'
109.+ pad(d.getMinutes())+':'
110.+ pad(d.getSeconds());

111.}

112.}

Na początku pliku dodajemy obsługę zdarzenia 'load' okna przeglądarki, które będzie
wywoływało funkcję Service.parseUrl(). Argumentem tej funkcji jest adres otwieranej
strony. Przekazywany adres URL będzie porównywany ze wzorcem odpowiadającym adresowi
query wyszukiwania w Google i w przypadku dopasowania wzorca wyszukiwane wyrażenie
zostanie zapisane w pliku. Aby uzyskać dostęp do lokalnego systemu plików użyte zostały
komponenty XPCOM. Za zapis logów odpowiada funkcja Service.saveQueryLog().

content/showlogs.xul

W dokumencie overlay.xul w elemencie menuitem dodana jest obsługa zdarzenia oncommand.
Jest to zdarzenie, które zostanie wywołane po wybraniu myszką pozycji menu "Pokaż logi" - czyli
wywołanie metody Service.dialogLogOpen(). Metoda ta otwiera w nowym oknie
dokument showlogs.xul:

01.<?xml version="1.0"?>
02.<?xml-stylesheet href="

chrome://global/skin/

"?>

03.<?xml-stylesheet href="

chrome://googlequerylog/skin/style.css

"?>

04.<!DOCTYPE window SYSTEM
"

chrome://googlequerylog/locale/qooglequerylog.dtd

">

05.<window id="googlequerylog-logs-dialog"
xmlns="

http://www.mozilla.org/keymaster/gatekeeper/there.is.only.x

ul

"

06.title="&logswindow.title;"
07.onload="Service.dialogLogInit();">

08.<script type="application/javascript"
src="

chrome://googlequerylog/content/service.js

"/>

09.<box height="400" width="400">

10.<textbox id="googlequerylog-textbox" flex="1"
multiline="true" readonly="true" />

11.</box>
12.<button label="&logswindow.closButton;"
oncommand="window.close();" />

13.</window>
Zwróć uwagę na zdarzenie onload na elemencie window. Fukcja
Service.dialogLogInit() uruchamia sekwencje otwarcia pliku logów z dysku oraz
wyświetlenie ich w nowym oknie.

background image

style.css

W chrome.manifest zarejestrowaliśmy pakiet skin. Jest to pakiet, w którym definiujemy
warstwę prezentacji, która reprezentowana jest arkuszem CSS. W naszym dodatku równie dobrze
mogłoby jej nie być, jednak dodałem ją, aby zaprezentować w jaki sposób jest to realizowane w
dodatkach do FireFox. Z arkusza css korzysta showlogs.xul, gdzie jest on dołączany w prologu
dokumentu:

<?xml-stylesheet href="chrome://googlequerylog/skin/style.css"?>

Plik style.css umieszczony jest w lokalizacji skin/classic/style.css
1.#googlequerylog-textbox {

2.-moz-appearance: none;
3.background:#FFF8D6;

4.}
Styl ten zmienia kolor tła elementu texbox. Mozilla wprowadza wiele dodatkowych styli, które
możemy użyć w ramach rozszerzeń. Opis wszystkich znajduje się

tutaj

. W przykładzie powyżej

znajduje się jeden z nich: -moz-appearance:none, który wyłącza natywne style textboxa. Bez
użycia tego stylu nie bylibyśmy w stanie zmienić tła tego elementu.
Warto zwrócić uwagę również na dodany arkusz chrome://global/skin/. Jest to arkusz
globalny w którym zdefiniowane są style globalne przeglądarki (np. wizualizacja otwieranego okna,
przycisków, ...).

background image

Wersje językowe

Powróćmy ponownie do pliku chrome.manifest. Zdefiniowaliśmy w nim wpisy odpowiadające
deklaracji wersji językowych dodatku:

locale googlequerylog en-US locale/en-US/

locale googlequerylog pl-PL locale/pl-PL/

W dodatku mamy trzy elementy, które należałoby przetłumaczyć na inne języki. Jest to pozycja w
menu "Pokaż logi" oraz nazwa okna z logami i przycisk "Zamknij". Są one umieszczone w
dokumentach XUL. Dokumenty XUL mają składnie XML, więc skorzystamy z encji, które będą
zwierały tłumaczenia.
Encję dołączamy deklaracją DOCTYPE

<!DOCTYPE window SYSTEM "chrome://googlequerylog/locale/qooglequerylog.dtd">

Zgonie z chrome.manifest definicje encji umieścimy w lokalizacjach /locale/pl-
PL/qooglequerylog.dtd oraz analogicznie dla wersji en-US:

<!ENTITY overlay.menuShowLogs "Pokaż logi">
<!ENTITY logswindow.title "Logi">

<!ENTITY logswindow.closButton "Zamknij">

W dokumencie XUL będziemy się do nich odwoływać za pomocą encji, np:
&overlay.menuShowLogs;
Są one umieszczone w plikach overlay.xul i showlogs.xul.
Można zauważyć, że w deklaracji ścieżki DOCTYPE nie są uwzględniane katalogi językowe, np.
pl-PL. Silnik FireFox automatycznie je uzupełnia w zależności od wersji językowej przeglądarki.
Dobrze, ale w jaki sposób skorzystać z etykiet językowych w JavaScript? Akurat w naszym
przykładzie nie było takiej potrzeby, jednak warto przy okazji o tym wspomnieć.
Przykładowo stworzymy plik locale/pl-PL/qooglequerylog.properties
googlequerylog.label1=etykieta 1
googlequerylog.label2=etykieta 2
Poniżej znajduje się przykładowy kod JavaScript pokazujący w jaki sposób możemy skorzystać z
tak zdefiniowanych etykiet:

1.var _bundle =
gmyextensionBundle.createBundle("

chrome://googlequerylog/locale/go

oglequerylog.properties

");

2.alert(_bundle.GetStringFromName('googlequerylog.label1'));

Testy

Chciałbym wspomnieć o dość ważnej kwestii związanej z testowaniem pisanych dodatków. Wyżej
przedstawiłem konfigurację stworzonego nowego profilu FireFox do celów deweloperskich. W
konfiguracji tej zostało m.in. wyłączone cacheowanie ładowanych dokumentów XUL. Jeżeli
dodatek opiera się na niezależnym dokumencie XUL (jak w naszym przypadku okno z logami) jego
testowanie nie wymaga każdorazowego restartu FireFox. Jeżeli jednak testujemy warstwę overlay,
która jest nałożona na główne okno przeglądarki
(chrome://browser/content/browser.xul) konieczne będzie cykliczne zamykanie i
ponowne otwieranie FF.

background image

Paczka dystrybucyjna - XPI

Ostatnim elementem jest przygotowanie dystrybucji dodatku w formie plików XPI. XPI jest niczym
innym jak archiwum ZIP. Paczka XPI zawiera lekko zmodyfikowaną strukturę katalogów i plików
w stosunku do wersji deweloperskiej:

chrome.manifest

install.rdf
chrome/googlequerylog.jar

.jar to również plik ZIP. Do przygotowania paczki archiwów polecam paker

7-Zip

.

Katalogi content, locale, skin zarchiwizujemy do formatu ZIP i zmienimy nazwę
stworzonego archiwum na googlequerylog.jar. Umieścimy go w katalogu chrome
(zgodnie z powyższa strukturą paczki XPI).
Następnie zmodyfikujemy plik chrome.manifest, tak aby miał postać:

content googlequerylog jar:chrome/googlequerylog.jar!/content/

skin googlequerylog classic/1.0 jar:chrome/googlequerylog.jar!/skin/classic/
locale googlequerylog en-US jar:chrome/googlequerylog.jar!/locale/en-US/

locale googlequerylog pl-PL jar:chrome/googlequerylog.jar!/locale/pl-PL/
overlay chrome://browser/content/browser.xul

chrome://googlequerylog/content/overlay.xul

Widzimy, że dodaliśmy informacje, że poruszamy się względem archiwum .jar.
Teraz całą strukturę plików i katalogów zarchiwizujemy do ZIP i zmienimy nazwę archiwum na
googlequerylog.xpi.
To wszystko. Na koniec wystarczy przeciągnąć stworzony plik XPI do okna FireFox aby go
zainstalować.
Powyższy dodatek można pobrać

stąd

*

Na koniec

Tutorial ten jest jedynie wprowadzeniem do tematu rozszerzeń Mozilli. Możliwości jakie daje nam
FireFox do tworzenia dodatków są naprawdę duże, co zresztą widać po bogatej

bibliotece

dostępnych rozszerzeń

. W wielu przypadkach są to bardzo użyteczne dodatki, bez których często

nie można sobie już wyobrazić korzystania z przeglądarki. Jak widać, aby napisać własny dodatek
wystarczy jedynie dobry pomysł, bo samo jego stworzenie, mam nadzieję, nie wydaje się już takie
trudne. Powodzenia!

MANUNU


Document Outline


Wyszukiwarka

Podobne podstrony:
jak stworzyc bramke do wysyłania maili, PHP Skrypty
Jak stworzyć formularz do przesyłania informacji na podany email, PHP Skrypty
Jak stworzyc skrut do programu
Jak stworzyć formularz, który zapisze?ne do pliku tekstowego,?y potem jego zawartość dołączyć
Jak stworzyć koszyk zamówień do sklepu internetowego z wykorzystaniem cookies, PHP Skrypty
jak stworzyc system www do edycji plikow tekstowych i stron w wybranyum katalogu, PHP Skrypty
Poradnik jak Poprawnie wgrac Ursus Addon 2013 Dodatek do Farming Simulator 2013 by Edzio021
Jak stworzyć opis produktu skierowany do kobiet eKomercyjnie(1)
Jak zainstalować dodatek Multiplayer do Gta San Andreas
Jak stworzyc skuteczna strone W Nieznany
Jak stworzyć najniezwyklejszy i niezapomniany Marketing swojego życia
Jak stworzyć prostą wyszukiwarkę dla własnych stron WWW, PHP Skrypty
Jak stworzyć zaawansowany test wyboru lub quiz, PHP Skrypty
Jak Polska szła do Unii Europejskiej
Jak nisko już upadła cywilizacja Jak daleko jeszcze do kompletnego dna NewWorldOrder com pl
Jak stworzyc system identyfikacji wizualnej firmy

więcej podobnych podstron