ASP.NET: Repeater cz. 2

 

Po powiązaniu naszej kolekcji z repeaterem za pomocą polecenia DataBind() nasze dane wyświetlane są w wierszach. Wygląda to tak:

Repeater - databind

Zaimplementujmy na początek możliwość dodawania nowego wiersza. Wiersz ten nie może wyświetlać żadnego tekstu, ale jednocześnie repeater wyświetlać musi elementy znajdujące się w kolekcji. Jak uważni czytelnicy zapewne zauważą jest tu pewna sprzeczność. Klasa SimpleItem przechowuje bowiem identyfikator jak typ int, który nie jest nullowalny. Z tego powodu nie można wymagać prostego dodania pustego pola tekstowego i jednoczesnego zapisu do kolekcji (chyba, że typ zostałby zmieniony na nullowalny int, czyli int?). Na szczęście da się wybrnąć z tej sytuacji dzięki istniejącej logice biznesowej, a mianowicie, przyjęciu, że identyfikator nie może mieć wartości 0 (nie jest to naiwne założenie). 

Należy przede wszystkim umieścić na stronie przycisk umożliwiający dodawanie nowego wiersza. Postępowanie przy tym jest następujące:

  1. Odczyt wszystkich wierszy repeatera i zapis ich do nowej kolekcji
  2. Dodanie nowego “pustego” elementu
  3. Powiązanie nowej kolekcji z repeaterem

Punkt pierwszy wynika z tego, że po powiązaniu kolekcji z repeaterem, jeśli nastąpi jakiś postback, to nie da się już uzyskać dostępu do kolekcji (chyba, że zostanie wykorzystany jakiś mechanizm zarządzania stanem aplikacji), natomiast dane cały czas “wędrują” razem z repeaterem, dzięki czemu można je wydobyć. Spójrzmy zatem na kod:

/// <summary>
/// When add row button clicked
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnAddRepeaterRow_Click(object sender, EventArgs e)
{
	//Create new collection
	Collection<SimpleItem> myCollection = new Collection<SimpleItem>();

	//Get collection from repeater
	foreach (RepeaterItem item in repDynamicRows.Items)
	{
		string id = ((TextBox)item.FindControl("txtId")).Text;
		string name = ((TextBox)item.FindControl("txtName")).Text;

		if (string.IsNullOrEmpty(id))
		{
			//Write 0 in collection because it will result in empty textbox when bound
			myCollection.Add(new SimpleItem(0, ""));
		}
		else
		{
			myCollection.Add(new SimpleItem(Convert.ToInt32(id, CultureInfo.InvariantCulture), name));
		}
	}

	//Add additional row
	myCollection.Add(new SimpleItem(0, ""));

	//Bind new data source to repeater
	repDynamicRows.DataSource = myCollection;
	repDynamicRows.DataBind();
}

Do wierszy repeatera możemy się dostać poprzez jego właściwość Items. Z każdego wiersza natomiast można wydobyć zawartość każdej kontrolki, wywołując na obiekcie typu RepeaterItem metodę FindControl() podając odpowiedni identyfikator danej kontrolki. Konieczne jest również rzutowanie na określony typ kontrolki.

W powyższym przykładzie, możliwe jest dodawanie kolejnego wiersza, nawet jeśli poprzedni nie został uzupełniony. Dodanie wiersza do kolekcji wiąże się z zamianą tekstu znajdującego się w polu tekstowym w kolumnie Id na liczbę, czyli wymaga konwersji, która może być zakończona sukcesem jedynie jeśli próbujemy konwertować liczbę. Z tego powodu w pętli znalazł się warunek if…else…, który powoduje to, że jeśli użytkownik nie wpisał nic w polach tekstowych, to zostanie do kolekcji dodany obiekt o id = 0 i pustej nazwie. Na zakończenie, z racji tego, że w końcu naszym życzeniem było dodanie nowego wiersza, to w taki sam sposób dodawany jest nowy element do kolekcji, po czym jest ona bindowana do repeatera. Takie działanie spowoduje, że w momencie wyświetlenia strony użytkownikowi w repeaterze zostanie wyświetlony dodatkowy wiersz:

repeater - row added 

Jak widać na powyższym obrazku, pomimo tego, że dodaliśmy do kolekcji element o identyfikatorze 0, to nie został on wyświetlony. Jak to się stało? Otóż zastosowana została tutaj pewna mała sztuczka. Mianowicie wykorzystane zostało zdarzenie repeatera ItemDataBound, zachodzące podczas wiązania każdego wiersza do repeatera:

/// <summary>
/// When row is bound to repeater insert blank when id is equal to 0
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void repDynamicRows_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
	TextBox txtId = e.Item.FindControl("txtId") as TextBox;
	if (txtId != null)
	{
		if (txtId.Text == "0")
		{
			txtId.Text = "";
		}
	}
}

Powyższy kod sprawia to, że w momencie, kiedy z kolekcji zostanie odczytany element, który spowoduje wyświetlenie w polu tekstowym w kolumnie Id tekstu “0” to zostanie on zamieniony na pusty tekst, dzięki czemu użytkownik nie zobaczy żadnych danych w tym polu, czyli zamierzony efekt został osiągnięty.

Usuwanie wiersza z repeatera

Jeśli chodzi o usuwanie wybranego wiersza z repeatera, to jak łatwo można wywnioskować służy do tego znak X znajdujący się w trzeciej kolumnie. Jego zadaniem jest, po kliknięciu, usunięcie wiersza, w którym się on znajduje. Postępowanie tutaj jest podobne jak w przypadku dodawania nowego wiersza, z tą różnicą, że przy tworzeniu nowej kolekcji musimy jeden wiersz pominąć (ten, w którym znajduje się znak X) oraz oczywiście nie jest dodawany nowy wiersz. W kodzie wygląda to następująco:

/// <summary>
/// Event after delete link clicked in repeater
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
protected void repDynamicRows_ItemCommand(object source, RepeaterCommandEventArgs e)
{
	if (e.CommandName == "Delete")
	{
		Collection<SimpleItem> myCollection = new Collection<SimpleItem>();

		//Add all elements from repeater to collection except the deleted one
		foreach (RepeaterItem item in repDynamicRows.Items)
		{
			LinkButton lkbRemove = (LinkButton)item.FindControl("lkbDelete");

			if (lkbRemove != e.CommandSource)
			{
				string id = ((TextBox)item.FindControl("txtId")).Text;
				string name = ((TextBox)item.FindControl("txtName")).Text;
				if (string.IsNullOrEmpty(id))
				{
					//Write 0 in collection because it will result in empty textbox when bound
					myCollection.Add(new SimpleItem(0, ""));
				}
				else
				{
					myCollection.Add(new SimpleItem(Convert.ToInt32(id, CultureInfo.InvariantCulture), name));
				}
			}
		}

		//Bind collection to repeater
		repDynamicRows.DataSource = myCollection;
		repDynamicRows.DataBind();
	}
}

Tym razem wykorzystane zostało zdarzenie ItemCommand, które występuje w momencie kliknięcia w jakiś element posiadający charakter przycisku, ale znajdujący się w repeaterze. W przedstawionym przykładzie jest to LinkButton wyświetlający X. Jak widać, wszelkie czynności są wykonywane dopiero, gdy sprawdzone zostanie, że komenda to “Delete”:

if(e.CommandName == "Delete")
{
	...
}

Kolejna sztuczka opiera się na odnalezieniu źródła, które spowodowało wykonanie zdarzenia:

...
	LinkButton lkbRemove = (LinkButton)item.FindControl("lkbDelete");

	if (lkbRemove != e.CommandSource)
	{
		...
	}

Dla każdego wiersza odnajdywany jest w nim LinkButton i jeśli nie jest on tym, który spowodował wykonanie zdarzenia to wiersz ten jest dodawany do kolekcji. Łatwo więc zauważyć, że wiersz z którego zdarzenie zostało wykonane nie zostanie dodany do kolekcji, czyli efekt zamierzony został osiągnięty.

Na zakończenie słowo o zapisie kolekcji z repeatera. W tym celu obsłużone musi być zdarzenie kliknięcia w jakiś przycisk znajdujący się poza repeaterem, którego zadaniem jest zapis danych. Należy odczytać wszystkie dane z repeatera i przypisać je do utworzonej kolekcji, co zostało już pokazane.

PS. Kod implementujący opisany mechanizm nie znalazł się w projekcie konkursowym, zatem nie znalazł się on w źródłach umieszczonych na codeplexie, stąd proszę o nie szukanie go tam 🙂

Reklamy

4 Responses to ASP.NET: Repeater cz. 2

  1. Krzysztof says:

    Tutorial jest fajny, chociaz, dziala na stronach ktore nie maja master page. Sprobowalem zaimplementowac na ten wzor na stronce ktora ma nad soba master page i niestety walcze caly dzien z tym problemem, a mianowicie przy zdarzeniu OnClick Buttona w foreach repeater nie ma zadnego Item. Stowrzylem osobna web app bez master page i trzyma controlki, a tutaj nie i nie wiem jak sie dostac do nich. Robie find na rozne sposoby.

    Jakis pomysl?

    • Wszystkie stronki jakie robię mają master.page więc odrobinę się zdziwiłem o co chodzi, ale że lubię rozwiązywać takie problemy w najbliższym czasie zajrzę do tego. Dawno do tego tematu nie wracałem więc ew. potem poproszę o kod na maila to rozgryziemy jakoś czemu jest nie tak jak powinno 🙂

  2. przemek says:

    czemu nie GridView?

    • Odpowiedź jest dość prosta – repeater dlatego, że akurat wtedy potrzebowałem się go nauczyć, dokładnie stworzyć opisaną przeze mnie funkcjonalność – takie było wymaganie projektowe. Ogólnie przewaga repeatera jest taka, że renderuje on prosty kod html, co w znaczny sposób ułatwia jego ostylowanie przez osoby nie znające .NET

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d blogerów lubi to: