Strona początkowa

Tworzymy kalendarz

W dzisiejszym odcinku przygód Fantomasa... postaramy się stworzyć kalendarz. Moim zdaniem najłatwiejszą i najlepszą metodą, jest "nie wymyślać koła na nowo" i skorzystać po prostu z (odpowiedniego pluginu). My jednak chcemy się uczyć, a nie iść na łatwiznę.

Zobacz DEMO naszych kalendarzowych inputów

Ogólnie rzecz biorąc - trochę sobie popiszemy.

Założenia

Przed przystąpieniem do pracy ustalmy kilka punktów, które powinniśmy spełnić:

  1. Aby nie popaść w chaos, kalendarz powinien być napisany obiektowo
  2. kalendarz powinien móc być wykorzystany wielokrotnie na tej samej stronie
  3. wygląd kalendarza nas nie interesuje - zajmie się tym CSS
  4. cały kalendarz będzie tworzony dynamicznie w momencie wywołania, i usuwany gdy nie będzie potrzebny
  5. po wybraniu daty będzie ona gdzieś zwracana

To są nasze ogólne postanowienia. Jesteśmy jednak super, więc podnieśmy nieco poprzeczkę.
Stwórzmy nasz kalendarz jako metoda dla każdego pola tekstowego. Gdy dla takiego pola wywołamy stworzoną przez nas metodę, zamieni się ono w specjalne pole z ikonką kalendarza obok, która będzie wywoływać kalendarz do wybrania daty. Dodatkowo nasza metoda nieco zmieni wygląd i zachowanie takiego pola.

pole_kalendarza

Przygotowania środowiska

Naszą pracę zaczniemy od przygotowania środowiska do pracy. Będą nam potrzebne dwie dodatkowe metody - insertAfter wstawiająca element za elementem (dla ikony kalendarza), oraz empty - służąca do czyszczenia danego elementu z jego dzieci (obie metody poznaliśmy w rozdziale o hierarchii dokumentu). Nasz kalendarz będziemy mogli wypozycjonować absolutnie względem całego dokumentu. Moglibyśmy zawsze wykorzystać do tego dodatkowy span (z position:relative) leżący za naszym tekstowym polem i względem niego pozycjonować nasz kalendarz, jednak moglibyśmy mieć wtedy problemy z wszechstronnym wykorzystaniem naszego kalendarza, gdyby w layoucie stosowany był overflow:hidden. Oczywiście zawsze możemy to zmienić - poniższy przykład jest tylko wymysłem Fantomasa.

    //metoda wstawiająca element za elementem
    Node.prototype.insertAfter = function(newNode) {
		if(this.nextSibling) { //jezeli dany element ma za soba jakis obiekt
			return this.parentNode.insertBefore(newNode, this.nextSibling); //to wstawiamy przed tym obiektem nasz element
		} else {
			return this.parentNode.appendChild(newNode);
		}
	}
    
    //metoda czyszcząca element z jego dzieci
    Node.prototype.empty = function() {
		if (this.childNodes.length) {
			for (x=this.childNodes.length-1; x>=0; x--) {
				this.removeChild(this.childNodes[x]);
			}
		}
	}
    

Podstawowa klasa kalendarza

Zacznijmy od stworzenia podstawowej klasy kalendarza, którą to będziemy następnie rozwijać o nowe metody i właściwości:

    _Kalendarz = function(_obW,_klasa) {
        this.teraz = new Date();
        this.dzien = this.teraz.getDate();
        this.miesiac = this.teraz.getMonth();
        this.rok = this.teraz.getFullYear();
        this.obiekt_wywolujacy = _obW;
        this.text_input = _obW.previousSibling;
        this.o = this;
    }
    

Dzięki obiekt_wowołujący będziemy wiedzieli, co utworzyło nasz kalendarz (a będzie to ikona kalendarza). Właściwość o wskazuje na sam obiekt - wykorzystamy to w pod obiektach. W atrybucie "_klasa" przekażemy klasę, jaką otrzyma nasz kalendarz. Właściwość text_input będzie wskazywała na pole tekstowe, przy którym umieściliśmy naszą ikonkę kalendarza. Pamiętajmy, że nasz kalendarz tworzy ikonka, nie samo pole tekstowe.

Nasz kalendarz będzie zawierał wszystko co kalendarz powinien zawierać - wypisane dni (tabela), guziki do przewijania oraz nazwę miesiąca. Rozwijamy więc naszą klasę:

    _Kalendarz = function(_obW,_klasa) {
        this.teraz = new Date();
        this.dzien = this.teraz.getDate();
        this.miesiac = this.teraz.getMonth();
        this.rok = this.teraz.getFullYear();
        this.obiekt_wywolujacy = _obW;
        this.text_input = _obW.previousSibling;
        this.nazwy_miesiecy = null; 
        this.div_place = null;
        this.div_tabela_z_kalendarzem = null;
        this.div_nazwa_miesiaca = null
        this.div_guziki = null;
        this.o = this;
    }
	

Właściwość div_tabela_z_kalendarzem będzie zawierała tabelę z kalendarzem. Właściwość div_nazwa_miesiaca będzie elementem div, w którym wypiszemy nazwę miesiąca. Właściwość div_place to div, w który wszystko upchniemy i będziemy go pozycjonować absolutnie. div_guziki będzie miejscem z guzikami.

Metoda init()

Pierwszą metodą będzie metoda inicjująca nasz kalendarz. To właśnie w niej zamienimy powyższe null na coś bardziej sensownego. Poza tym będziemy wiedzieli jakie metody musimy stworzyć :)

	this.init = function() {
        //tworzymy div z całą zawartością
        this.div_place = document.createElement('div');
        this.div_place.className = _klasa;
        this.div_place.style.position = "absolute"; //nie wazne jak ma klasa - musi być absolutnie :)
        this.div_place.style.top = parseInt(this.text_input.offsetTop + this.text_input.offsetHeight) + 'px';
        this.div_place.style.left = this.text_input.offsetLeft + 'px';				
        
        //tworzymy div z guzikami
     	this.div_guziki = document.createElement('div');
        this.div_guziki.className = "guziki-prev-next"
        this.div_place.appendChild(this.div_guziki);
        this.create_guziki();

        //tworzymy div z nazwą miesiąca
        this.div_nazwa_miesiaca = document.createElement('div');
        this.div_nazwa_miesiaca.className = 'nazwa-miesiaca';
        this.div_place.appendChild(this.nazwa_miesiaca);
        this.create_nazwa_miesiaca();

        //tworzymy div z tabelą kalendarza
        this.div_tabela_z_kalendarzem = document.createElement('div');
        this.div_tabela.className = 'kalendarz-table';				
        this.div_place.appendChild(this.div_tabela_z_kalendarzem);        
        this.create_tabela_kalendarza();

        //nasz div z zawartością wrzucamy na koniec body
        document.getElementsByTagName('body')[0].appendChild(this.div_place);
    }
    this.init(); //przy stworzeniu naszego obiektu od razu odpalamy funkcję inicjującą
    

W metodzie inicjującej stworzyliśmy poszczególne divy z naszego kalendarza, przypisaliśmy im odpowiednie klasy, a następnie dla każdego diva, wywołaliśmy odpowiednią metodę, która tworzy jego zawartość. Te metody napiszemy już za chwilę. Pozycję naszego div_place (zawierającego wszystkie elementy) ustaliliśmy za pomocą właściwości offset dla pola tekstowego. Dzięki temu nasz kalendarz będzie się znajdował tuż pod polem tekstowym.

Metoda create_guziki()

Przystępujemy do pisania poszczególnych metod. Pierwsza z nich to create_guziki():

    this.create_guziki = function() {
	    var ob = this.o;
        var buttonPrev = document.createElement('input');
            buttonPrev.value = '<';
            buttonPrev.type = "button";
            buttonPrev.className = 'input-prev';
            buttonPrev.onclick = function() {
                ob.miesiac--;												
                if (ob.miesiac<0) ob.miesiac = 11;
                ob.create_tabela_kalendarza();
                ob.create_nazwa_miesiaca();
            }
        this.div_guziki.appendChild(buttonPrev); 
                    
        var buttonNext = document.createElement('input');
            buttonNext.className = 'input-next';
            buttonNext.value = '>';
            buttonNext.type = "button";
            buttonNext.onclick = function() {
                ob.miesiac++;												
                if (ob.miesiac>11) ob.miesiac = 0;
                ob.create_tabela_kalendarza();
                ob.create_nazwa_miesiaca();
            }
        this.div_guziki.appendChild(buttonNext);  
    }
    

Stworzyliśmy 2 przyciski. Dodatkowo napisaliśmy im obsługę zdarzenia onclick, z wykorzystaniem techniki przekazania obiektu rodzica. W zasadzie nie ma tu nic trudnego. Obsługujemy zwykłą zmianę miesięcy, a po każdej takiej zmianie, tworzymy na nowo tabelę z kalendarzem oraz wypisujemy nazwę miesiąca. Pamiętajmy, że miesiące w javascript zawierają się w zakresie 0 - 11.

Metoda create_nazwa_miesiaca()

Kolejną metodą, którą utworzymy będzie metoda create_nazwa_miesiaca() służąca do wypisania nazwy aktualnie wyświetlanego miesiąca:

    this.create_nazwa_miesiaca = function() {
        var nazwy_miesiecy = ['styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień'];
        
        //tworzymy nazwę miesiąca
		var miesiac = document.createTextNode(nazwy_miesiecy[this.miesiac])
        
        this.div_nazwa_miesiaca.empty(); //czyscimy zawartosc diva
        this.div_nazwa_miesiaca.appendChild(miesiac);
    }
    

Div z nazwą miesiąca wyczyściliśmy z jego dzieci, a następnie wstawiliśmy do niego teskt - nazwę miesiąca pobraną z tabeli.

Metoda create_tabela_kalendarza()

Wszystko było do tej pory proste. Wchodzimy więc na level Hard.

Kolejną metodą, którą się zajmiemy jest metoda create_tabela_kalendarza(), która będzie tworzyła tabalę z kalendarzem.

Aby wyświetlić dni miesiąca, musimy mieć kilka danych. Miesiąc może się zacząć od poniedziałku, wtorku itp. Musimy to wyliczyć. Podobnie z liczbą dni. Jak wiemy, miesiąc może mieć 30 lub 31 dni. Dodatkowo aby nie było za łatwo, luty ma ich 28 lub 29 - zależnie od tego czy rok jest przestępny, czy nie. To także musimy wyliczyć.

Do obliczenia liczby dni wykorzystamy dodatkową metodę pobierzDni()

    this.pobierzDni = function()  {
        var dni;
        var miesiac = this.miesiac+1;
        if (miesiac==1 || miesiac==3 || miesiac==5 || miesiac==7 || miesiac==8 || miesiac==10 || miesiac==12)  {
            dni=31;
        } else if (miesiac==4 || miesiac==6 || miesiac==9 || miesiac==11) {
            dni=30;
        } else if (miesiac==2) {
            if (this.przestepnyRok()) {
                dni=29;
            } else { 
                dni=28; 
            }
        }
        return dni;
    }
    

Powyższą metodę możemy napisać oczywiście o wiele krócej:

    this.pobierzDni = function()  {
        var miesiac = this.miesiac+1;
        var liczba_dni = [0,31,28,31,30,31,30,31,31,30,31,30,31];
			liczba_dni[2] = this.przestepnyRok() ? 29 : 28;
		return liczba_dni[miesiac];
    }
	

Działanie tej metody polega na sprawdzeniu numeru miesiąca i zwróceniu odpowiedniej liczby dni. W przypadku lutego (linijka 9), musimy wyliczyć, czy mamy do czynienia z rokiem przestępnym. Stwórzmy więc jeszcze jedną metodę, według informacji zawartych na Wikipedii:

    this.przestepnyRok = function() {
        if (((this.rok % 4)==0) && ((this.rok % 100) != 0) || ((this.rok % 400)==0)) {
            return true;
        } else { 
            return false; 
        }
    }
    

Mając przygotowane powyższe metody, przystąpmy do pisania metody create_tabela_kalendarza()

       
    this.create_tabela_kalendarza = function() {	
		var ob = this.o;
		var pierwszyDzien = new Date(this.rok, this.miesiac+1, 1);
		var pozycjaStartowa = pierwszyDzien.getDay();							
		var dni = this.pobierzDni();        
			dni += pozycjaStartowa;					
        
    	    //daleszy kod metody rysującej tabelę
    }
	

Pod zmienną ob podstawiamy obiekt, gdyż w tworzonej tabeli będą przyciski ze zdarzeniami click. Dni wyliczamy dodając do nich pozycję startową. Dalsza część tej metody polega na wykonaniu kilku pętli. Pierwsza z nich to utworzenie nazw dni. Nie specjalnego. Druga tworzy puste komórki tabeli - od 0 do pozycji pierwszego dnia w miesiącu (pozycjaStartowa). Trzecia - ostatnia pętla tworzy komórki z dniami. Każda taka komórka ma przypisaną klasę "dzień", oraz przy kliknięciu zwraca do pola tekstowego datę. Po utworzeniu zawartości tabeli, wstawiamy ją do oczyszczonego div_tabela_z_kalendarzem. Przy każdej pętli sprawdzamy, czy nie doszliśmy do końca tygodnia za pomocą równania (x%7==0). Jeżeli tak się dzieje, wówczas do dołączamy do tabeli aktualny wiersz, i zaczynamy tworzyć nowy.

    this.create_tabela_kalendarza = function() {	
 		var ob = this.o;
		var pierwszyDzienMiesiaca = new Date(this.rok, this.miesiac, 1);

		var pozycjaStartowa = pierwszyDzienMiesiaca.getDay();	                
            
		var dni = this.pobierzDni();
			dni += pozycjaStartowa;					
			
		var dni_tygodnia = ['Su','Mo','Tu','We','Th','Fr','Sa'];
        var tabela = document.createElement('table');
			tabela.className = 'kalendarz-table';

        this.div_tabela_z_kalendarzem.empty();

		//pierwsza pętla - nagłówek tabeli z nazwami dni
        var tr = document.createElement('tr');
        for (i = 0; i < dni_tygodnia.length; i++) {					
            var th = document.createElement('th');
                th.appendChild(document.createTextNode(dni_tygodnia[i]));
                tr.appendChild(th);
        }
        tabela.appendChild(tr);
        
        //druga pętla - puste komórki do momentu pierwszego dnia
		var tr = document.createElement('tr');

		for (j = 0; j < pozycjaStartowa; j++) {					
			if ( j%7 == 0) {					
				tabela.appendChild(tr);
				var tr = document.createElement('tr');								
			}						

			var td = document.createElement('td');
				td.appendChild(document.createTextNode(' '));
			tr.appendChild(td);
		}
        
        //trzecia pętla - wypisujemy dni
		for (i = pozycjaStartowa; i < dni; i++) {					
			if ( i%7 == 0 ) {
				tabela.appendChild(tr);
				var tr = document.createElement('tr');								
			}
			var nr_dnia = parseInt(i-pozycjaStartowa+1);
			if (nr_dnia < 10) nr_dnia = "0" + nr_dnia;
			var td = document.createElement('td');						
				td.appendChild(document.createTextNode(nr_dnia));
				td.className = 'dzien';
				td.onclick = function() {
					var miesiac = ((ob.miesiac+1) < 10)? "0"+(ob.miesiac+1) : ob.miesiac+1;	
					ob.text_input.value = ob.rok + '-' + miesiac + '-' + this.firstChild.nodeValue;
				};
			tr.appendChild(td);
			
		}
		tabela.appendChild(tr);
		
		this.div_tabela_z_kalendarzem.appendChild(tabela);
	}
    

Wykonaliśmy kolejno trzy pętle. Pierwsze dwie to chleb powszechni dla super bohaterów. Przypatrzmy się trzeciej pętli. Na jej początku ustawiamy wartość dnia. Dla poprawienia estetyki wstawiamy zero wiodące do numeru dnia. Podobnie postępujemy zresztą przy miesiącu. Każdej komórce w tej pętli ustawiamy klasę "dzien", oraz przypisujemy jej zdarzenie click. Zdarzenie to wstawia zero wiodące dla miesiąca, oraz wylicza rok. Javascript nie za bardzo sobie radzi z latami po 2000 roku zwracając je jako 100+n, dlatego musimy do tej liczby dodać 1900. Ostatnia czynność z tego zdarzenia to ustawienie naszemu polu tekstowemu odpowiedniej wartości.
Nasza metoda kończy się wstawieniem do diva div_tabela_z_kalendarzem naszej nowo utworzonej tabeli. Włala. Praktycznie (nie zupełnie) skończyliśmy pisanie naszego obiekt ukalendarza.

Pozostają nam 2 rzeczy do zrobienia. Pierwsza z nich to napisanie stosownej metody dla inputów, która będzie odpalała nasz kalendarz.

Metoda dla inputów

Metoda powinna utworzyć prz ydanym inpucie ikonkę kalendarza, po kliknięciu której stworzy się nasz obiekt kalendarz.

	Node.prototype.kalendarz = function() {
		if (this.nodeName.toUpperCase()=='INPUT' && this.type.toUpperCase()=='TEXT') {
			this.value = "rrrr-mm-dd";
			this.kalendarzowy = true;            
			var input = document.createElement('input');
				input.type = 'button';
				input.className = 'ikona-kalendarza';
				input.jestKalendarz = false;
				input.kalendarz = null;
				input.onclick = function() {
					if (!this.jestKalendarz) {
						this.kalendarz = new _Kalendarz(this,'kalendarz')
						this.jestKalendarz = true;
						return
					} else {
						this.kalendarz.deleteKalendarz();
						this.jestKalendarz = false;
					}	return
					
				}
			this.insertAfter(input);
		}
	}
    

Sprawdzamy czy nasz input jest polem tekstowym (dla innych nie było by sensu zwracać wartości). Jeżeli jest, działamy dalej. Ustawiamy mu informacyjną wartość, oraz dodajemy mu właściwość kalendarzowy. Dzięki niej będziemy mogli w przyszłości łatwo wykryć, czy dany input jest już kalendarzowy. następnie tworzymy nowy input - naszą ikonkę kalendarza. Ustawiamy jej klasę "ikona-kalendarza", oraz przypisujemy obsługę zdarzenia click. Dodajemy jej też dodatkową właściwość, w której będziemy trzymać stan istnienia kalendarza. Prosta sprawa - jeżeli dla danego inputa kalendarz nie istnieje, to go tworzymy. Jeżeli jednak istnieje, to go usuwamy. Zaraz, zaraz - przecież nasz kalendarz, jeszcze się nie potrafi usuwać. No to go tego nauczmy pisząc dla niego małą metodę:

    this.deleteKalendarz = function() {
		this.div_place.parentNode.removeChild(this.div_place);
		delete this.o;
	}
    

Od tej chwili zmiana zwykłego inputa na input z kalendarzem będzie dziecinnie prosta:

    var input = document.getElementById('jakies_pole_tekstowe')
	    input.kalendarz();
    

Wszystko razem

Czas kończyć ten odcinek Przygód Super Fantomasa. Zbierzmy wszystko razem do kupy:

    
    //srodowisko
        Node.prototype.insertAfter = function(newNode) {
            if(this.nextSibling) { //jezeli dany element ma za soba jakis obiekt
                return this.parentNode.insertBefore(newNode, this.nextSibling); //to wstawiamy przed tym obiektem nasz element
            } else {
                return this.parentNode.appendChild(newNode);
            }
        }
        
        Node.prototype.empty = function() {
            if (this.childNodes.length) {
                for (x=this.childNodes.length-1; x>=0; x--) {
                    this.removeChild(this.childNodes[x]);
                }
            }
        }    

	//super klasa obietów kalendarzy
        _Kalendarz = function(_obW,_klasa) {
            this.teraz = new Date();
            this.dzien = this.teraz.getDate();
            this.miesiac = this.teraz.getMonth();
            this.rok = this.teraz.getFullYear();
            this.obiekt_wywolujacy = _obW;
            this.text_input = _obW.previousSibling;
            this.nazwy_miesiecy = null; 
            this.div_place = null;
            this.div_tabela_z_kalendarzem = null;
            this.div_nazwa_miesiaca = null
            this.div_guziki = null;
            this.o = this;
    
            //metoda tworzaca guziczki        
            this.create_guziki = function() {
                var ob = this.o;
                var buttonPrev = document.createElement('input');
                    buttonPrev.value = '<';
                    buttonPrev.type = "button";
                    buttonPrev.className = 'input-prev';
                    buttonPrev.onclick = function() {
                        ob.miesiac--;												
                        if (ob.miesiac<0) ob.miesiac = 11;
                        ob.create_tabela_kalendarza();
                        ob.create_nazwa_miesiaca();
                    }
                this.div_guziki.appendChild(buttonPrev); 
                            
                var buttonNext = document.createElement('input');
                    buttonNext.className = 'input-next';
                    buttonNext.value = '>';
                    buttonNext.type = "button";
                    buttonNext.onclick = function() {
                        ob.miesiac++;												
                        if (ob.miesiac>11) ob.miesiac = 0;
                        ob.create_tabela_kalendarza();
                        ob.create_nazwa_miesiaca();
                    }
                this.div_guziki.appendChild(buttonNext);  
            }
    
            this.create_nazwa_miesiaca = function() {
                var nazwy_miesiecy = ['styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień'];
                
                //tworzymy nazwę miesiąca
                var miesiac = document.createTextNode(nazwy_miesiecy[this.miesiac])
                
                this.div_nazwa_miesiaca.empty(); //czyscimy zawartosc diva
                this.div_nazwa_miesiaca.appendChild(miesiac);
            }
    
            //metoda wyliczajaca liczbe dni
            this.pobierzDni = function()  {
                var dni;
                var miesiac = this.miesiac+1;
                if (miesiac==1 || miesiac==3 || miesiac==5 || miesiac==7 || miesiac==8 || miesiac==10 || miesiac==12)  {
                    dni=31;
                } else if (miesiac==4 || miesiac==6 || miesiac==9 || miesiac==11) {
                    dni=30;
                } else if (miesiac==2) {
                    if (this.przestepnyRok()) {
                        dni=29;
                    } else { 
                        dni=28; 
                    }
                }
                return dni;
            }
    
            //metoda wyliczajaca rok przestepny        
            this.przestepnyRok = function() {
                if (((this.rok % 4)==0) && ((this.rok % 100) != 0) || ((this.rok % 400)==0)) {
                    return true;
                } else { 
                    return false; 
                }
            }
    
			//metoda tworząca kalendarz
			this.create_tabela_kalendarza = function() {	
				var ob = this.o;
				var pierwszyDzienMiesiaca = new Date(this.rok, this.miesiac, 1);

				var pozycjaStartowa = pierwszyDzienMiesiaca.getDay();	                
					
				var dni = this.pobierzDni();
					dni += pozycjaStartowa;					
					
				var dni_tygodnia = ['Su','Mo','Tu','We','Th','Fr','Sa'];
				var tabela = document.createElement('table');
					tabela.className = 'kalendarz-table';

				this.div_tabela_z_kalendarzem.empty();

				//pierwsza pętla - nagłówek tabeli z nazwami dni
				var tr = document.createElement('tr');
				for (i = 0; i < dni_tygodnia.length; i++) {					
					var th = document.createElement('th');
						th.appendChild(document.createTextNode(dni_tygodnia[i]));
						tr.appendChild(th);
				}
				tabela.appendChild(tr);
				
				//druga pętla - puste komórki do momentu pierwszego dnia
				var tr = document.createElement('tr');

				for (j = 0; j < pozycjaStartowa; j++) {					
					if ( j%7 == 0) {					
						tabela.appendChild(tr);
						var tr = document.createElement('tr');								
					}						

					var td = document.createElement('td');
						td.appendChild(document.createTextNode(' '));
					tr.appendChild(td);
				}
				
				//trzecia pętla - wypisujemy dni
				for (i = pozycjaStartowa; i < dni; i++) {					
					if ( i%7 == 0 ) {
						tabela.appendChild(tr);
						var tr = document.createElement('tr');								
					}
					var nr_dnia = parseInt(i-pozycjaStartowa+1);
					if (nr_dnia < 10) nr_dnia = "0" + nr_dnia;
					var td = document.createElement('td');						
						td.appendChild(document.createTextNode(nr_dnia));
						td.className = 'dzien';
						td.onclick = function() {
							var miesiac = ((ob.miesiac+1) < 10)? "0"+(ob.miesiac+1) : ob.miesiac+1;	
							ob.text_input.value = ob.rok + '-' + miesiac + '-' + this.firstChild.nodeValue;
						};
					tr.appendChild(td);
					
				}
				tabela.appendChild(tr);
				
				this.div_tabela_z_kalendarzem.appendChild(tabela);
			}
            
            //metoda usuwa kalendarz
            this.deleteKalendarz = function() {
                this.div_place.parentNode.removeChild(this.div_place);
                delete this.o;
            }
                
            //metoda inicjujaca obiekt
            this.init = function() {
                //tworzymy div z całą zawartością
                this.div_place = document.createElement('div');
                this.div_place.className = _klasa;
                this.div_place.style.position = "absolute"; //nie wazne jak ma klasa - musi być absolutnie :)
                this.div_place.style.top = parseInt(this.text_input.offsetTop + this.text_input.offsetHeight) + 'px';
                this.div_place.style.left = this.text_input.offsetLeft + 'px';			
                
                //tworzymy div z guzikami
                this.div_guziki = document.createElement('div');
                this.div_guziki.className = "guziki-prev-next"
                this.div_place.appendChild(this.div_guziki);
                this.create_guziki();
        
                //tworzymy div z nazwą miesiąca
                this.div_nazwa_miesiaca = document.createElement('div');
                this.div_nazwa_miesiaca.className = 'nazwa-miesiaca';
                this.div_place.appendChild(this.nazwa_miesiaca);
                this.create_nazwa_miesiaca();
        
                //tworzymy div z tabelą kalendarza
                this.div_tabela_z_kalendarzem = document.createElement('div');
                this.div_tabela_z_kalendarzem.className = 'kalendarz-table';				
                this.div_place.appendChild(this.div_tabela_z_kalendarzem);        
                this.create_tabela_kalendarza();
        
                //nasz div z zawartością wrzucamy na koniec body
                document.getElementsByTagName('body')[0].appendChild(this.div_place);
            }
            this.init(); //przy stworzeniu naszego obiektu od razu odpalamy funkcję inicjującą
        }
    
	//metoda dla inputwo - zamienia inputy na kalendarzowe
        Node.prototype.kalendarz = function(klasa_kalenarza) {
            if (this.nodeName.toUpperCase()=='INPUT' && this.type.toUpperCase()=='TEXT') {
   				this.value = "rrrr-mm-dd";
				this.kalendarzowy = true;                
                var input = document.createElement('input');
                    input.type = 'button';
                    input.className = 'ikona-kalendarza';
                    input.jestKalendarz = false;
                    input.kalendarz = null;                
                    input.onclick = function() {
                        if (!this.jestKalendarz) {
                            this.kalendarz = new _Kalendarz(this,klasa_kalenarza);
                            this.jestKalendarz = true;
                            return
                        } else {
                            this.kalendarz.deleteKalendarz();
                            this.jestKalendarz = false;
                        }	return;
                        
                    }
                this.insertAfter(input);
            }
        }    
    

Przykładowe stylowanie dla naszego dzieła:

		.ikona-kalendarza {width:30px; height:20px; border:0; background:url(calendar.gif) no-repeat; width:21px; height:21px; position:relative; top:2px; cursor:pointer}
		.kalendarz {width:150px; background:#fff; height:170px; padding:5px; border:1px solid #ddd; -moz-border-radius:4px; -webkit-border-radius:4px; }
		.kalendarz .input-prev, .kalendarz .input-next {width:30px; height:20px; border:1px solid #ddd; background:#fff; font:11px Arial; color:#333; -moz-border-radius:2px}
		.kalendarz .input-prev {float:left;}
		.kalendarz .input-next {float:right}
		.kalendarz .nazwa-miesiaca {font:11px Arial; color:#666; margin:0 40px; text-align:center;}
		.kalendarz .kalendarz-table {margin-top:30px}
		.kalendarz .kalendarz-table table {font:12px Arial; color:#666; position:absolute; top:0; left:0; margin-top:20px; width:100%;}
		.kalendarz .kalendarz-table table th {font:bold 11px Arial; color:#333; padding-top:3px;}
		.kalendarz .kalendarz-table table td.dzien {border:1px solid #ddd; padding:2px; font:12px Arial}
		.kalendarz .kalendarz-table table td.dzien:hover {background:#D1EBFD; cursor:pointer}
    

Plus przykładowa metamorfoza:

    document.getElementById('input_zamien').onclick = function () {
        if (!document.getElementById('tekstowe1').kalendarzowy) {
            document.getElementById('tekstowe1').kalendarz('kalendarz');
        }
        if (!document.getElementById('tekstowe2').kalendarzowy) {
            document.getElementById('tekstowe2').kalendarz('kalendarz');
        }
    }    
    

Demo

Nasze kalendarzowe inputy wyglądają następująco:

Kilka słów zakończenia

Co jeszcze można dodać? Możemy np dodać dodatkowy atrybut dla naszej klasy _Kalendarz, który będzie mówił, czy kalendarz ma być usuwany po wybraniu daty. Wystarczy wtedy sprawdzić wartość tego atrybutu, i jeżeli jest prawdziwy, wówczas w zdarzeniu click dla komórek wywoływać dodatkowo metodę deleteKalendarz. Dodatkowym usprawnieniem może być pobieranie przy zamianie inputa jego value i ustawienie jako dodatkowej właściwości. Następnie umożliwienie odmiany zamienionego pola na zwykłe pole wraz z przywróceniem poprzedniej wartości. Myślę, że jest to na tyle proste (i podobne do tego co zrobiliśmy), że zostawię to jako praca domowa.

Na pewno też zauważyłeś pewne niekonsekwencje przy trzymaniu się techniki przekazywania obiektu do podobiektów. W niektórych metodach to robię, w niektórych nie. Ładnym zwyczajem było by przy każdej metodzie stosować tą technikę, i nie mieszać this z this.o (nasz przykład).
Z tego co sobie przypominam, starsze przeglądarki nie za dobrze radziły sobie z obsługą offsetTop, offsetLeft i offsetHeight. Dla nich przydało by się napisać dodatkową funkcję, która robiła by pętlę po nadrzędnych obiektach i dodawała wszystkie wartości uzyskując w ten sposób pozycję od początku strony. Przykładowa pętla mogła by mieć postać:

    while (obiekt.parentNode) {
	    top = obiekt.parentNode.offsetTop;
	    left += obiekt.parentNode.offsetLeft;
	    obiekt = obiekt.parentNode
    }
    

Powyższy kod przetestowałem na FF, Operze i Chromie - wszystkie przeglądarki nie narzekały. Na IE6 nie testowałem, bo go już nie wspieram. Dla innych przeglądarek będziesz też musiał przerobić metody Node, które stworzyliśmy w środowisku. Zawsze możesz napisać je w postaci zwykłych funkcji (IE niestety nie rozumie czym jest Node).