Tabs
먼저 첨부 파일을 다운 로드 받아 실행 해 보기를 권한다.
이번에 살펴볼 컨트롤은 Tabs컨트롤이다.
ASP.NET 사이트설명은
여기를 클릭 하기 바란다.
탭이 어떤 건지는 따로 설명할 필요가 없을 듯하다.
ASP.NET 1.0 버전의 Tab 컨트롤도 이 사이트에서
다운 받아 볼수 있다..(지금은 안쓰겠지만..ㅡ.ㅡ;)
이 컨트롤도 약간의 버그가 있다.
게다가 2% 부족한 면도 있다.
이 부분을 수정하느라 엄청나게 애를 먹었다..ㅡ.ㅠ;
이후에 설명을 하겠지만 탭이 바뀔때 일어나는 서버 이벤트가 일어나지 않는다.
구글링을 하다가 유사하게 해결하는 꽁수를 발견하기는 했지만. 정석은 아닐듯하다.
또 하나 2%가 부족한 면은...
탭에 사용할수 있는 것이 텍스트 뿐이라는 것이다.
우리나라 사이트처럼 화려한(?) 이미지의 도배가 되는 사이트에서는 텍스트로 탭의 헤더를 장식하는 일은 거의 없다고 봐도 될것이다.
그래서 이 부분에 이미지를 넣을 수 있도록 수정하였다.(엄청 애먹었다..ㅡ.ㅡ; 남의 코드를 수정한다는건 역시...)
이 컨트롤은 마치 Accordion 컨트롤 처럼 컨테이너컨트롤이 각각의 아이템을 가지고 있는 템플릿형식의 컨트롤이다.
차근차근 설명을 해 보도록 하겠다.
먼저 TabContainer의 속성을 살펴 보도록 하자.
<cc1:TabContainer ID="TabContainer1" runat="server"
OnClientActiveTabChanged="OnClientActiveTabChanged"
OnActiveTabChanged="OnActiveTabChanged"
ScrollBars="Auto"
ActiveTabIndex="0"
Height="400"
CssClass="ajax__tab_xp">
OnClientActiveTabChanged = 탭을 변경시킬때 일어나는 클라이언트 이벤트이다.
OnActiveTabChanged = 탭을 변경시킬때 일어나는 서버 이벤트이다.
ScrollBars = 미리 할당된 영역을 초과 하였을때 스크롤바를 보여주는 형식이다.
AcitveTabIndex = 처음 렌더링 될때 선택되어질 탭의 인덱스이다.
Height = 탭의 Content 영역중에 Height 이다.
CssClass = 스타일로서..기본값이 ajax__tab_xp 로 설정되어 있다.
이 Css 파일은 아래와 같이 선언되어 있다.
/* xp theme */
.ajax__tab_xp .ajax__tab_header {font-family:verdana,tahoma,helvetica;font-size:11px;background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-line.gif")%>) repeat-x bottom;}
.ajax__tab_xp .ajax__tab_outer {padding-right:4px;background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-right.gif")%>) no-repeat right;height:21px;}
.ajax__tab_xp .ajax__tab_inner {padding-left:3px;background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-left.gif")%>) no-repeat;}
.ajax__tab_xp .ajax__tab_tab {height:13px;padding:4px;margin:0;background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab.gif")%>) repeat-x;}
.ajax__tab_xp .ajax__tab_hover .ajax__tab_outer {background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-hover-right.gif")%>) no-repeat right;}
.ajax__tab_xp .ajax__tab_hover .ajax__tab_inner {background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-hover-left.gif")%>) no-repeat;}
.ajax__tab_xp .ajax__tab_hover .ajax__tab_tab {background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-hover.gif")%>) repeat-x;}
.ajax__tab_xp .ajax__tab_active .ajax__tab_outer {background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-active-right.gif")%>) no-repeat right;}
.ajax__tab_xp .ajax__tab_active .ajax__tab_inner {background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-active-left.gif")%>) no-repeat;}
.ajax__tab_xp .ajax__tab_active .ajax__tab_tab {background:url(<%=WebResource("AjaxControlToolkit.Tabs.tab-active.gif")%>) repeat-x;}
.ajax__tab_xp .ajax__tab_body {font-family:verdana,tahoma,helvetica;font-size:10pt;border:1px solid #999999;border-top:0;padding:8px;background-color:#ffffff;}
이 부분을 재정의 해서 사용자 임의대로 꾸미는 것도 가능하다.
대신 이름은 똑같아야 할 것이다..ㅡ.ㅡ;
이제 TabPanel 의 속성을 보도록 하자.
<cc1:TabPanel ID="TabPanel1" runat="server"
ScrollBars="Auto"
Height="35"
OverImageUrl="/images/sub_02_04_01.gif"
OutImageUrl="/images/sub_02_04_02.gif"
Width="190">
<cc1:TabPanel ID="TabPanel2" runat="server"
HeaderText="Tab2"
OnClientClick="OnClientClick">
ScrollBars = 미리 할당된 영역을 초과 하였을때 스크롤바를 보여주는 형식이다.
OnClientClick = 탭이 선택되어 졌을때 일어나는 클라이언트 이벤트이다.
HeaderText = 탭 헤더에 보여지는 텍스트이다.
OverImageUrl = 마우스가 over가 되었거나 selected 되었을때 보여지는 이미지이다.(추가된 속성이다.)
OutImageUrl = 마우스가 out가 되었거나 unselected 되었을때 보여지는 이미지이다.(추가된 속성이다.)
Height = 이미지의 Height 이다.(추가된 속성이다.)
Width = 이미지의 Width 이다.(추가된 속성이다.)
Height와 Width는 원래 있는 속성이나 사용되지 않는 속성이었다.
그러나 TabHeader에 Image 로 렌더되는 부분을 추가 하면서 이 부분에 이용하기로 하였다.
착오 없기 바란다.
TabPanel은 자식 노드로 HeaderTemplate과 ContentTemplate을 가지고 있다.
HeaderTemplate는 내부적으로 HeaderText가 렌더되는 span 태그에 innerHTML로 들어 가게 된다.
그리고 ContentTemplate은 Content영역에 렌더 될 내용이다.
추가된 부분의 코드에 대한 부연 설명을 해 보겠다.
먼저
Tab.js 파일의 수정 부분이다.
6 //방실 추가 overImageUrl, outImageUrl, width, height
241 this._overImageUrl = null;
242 this._outImageUrl = null;
243 this._width = 30;
244 this._height = 20;
위 처럼 4개의 속성을 새로 만들었다.
323 get_overImageUrl : function() {
324 return this._overImageUrl;
325 },
326 set_overImageUrl : function(value) {
327 if (this._overImageUrl != value) {
328 if (this.get_isInitialized()) {
329 throw Error.invalidOperation("headerTab cannot be changed after initialization.");
330 }
331 this._overImageUrl = value;
332 this.raisePropertyChanged("overImageUrl");
333 }
334 },
335
336 get_outImageUrl : function() {
337 return this._outImageUrl;
338 },
339 set_outImageUrl : function(value) {
340 if (this._outImageUrl != value) {
341 if (this.get_isInitialized()) {
342 throw Error.invalidOperation("headerTab cannot be changed after initialization.");
343 }
344 this._outImageUrl = value;
345 this.raisePropertyChanged("outImageUrl");
346 }
347 },
348
349 get_width : function() {
350 return this._width;
351 },
352 set_width : function(value) {
353 if (this._width != value) {
354 if (this.get_isInitialized()) {
355 throw Error.invalidOperation("headerTab cannot be changed after initialization.");
356 }
357 this._width = value;
358 this.raisePropertyChanged("width");
359 }
360 },
361
362 get_height : function() {
363 return this._height;
364 },
365 set_height : function(value) {
366 if (this._height != value) {
367 if (this.get_isInitialized()) {
368 throw Error.invalidOperation("headerTab cannot be changed after initialization.");
369 }
370 this._height = value;
371 this.raisePropertyChanged("height");
372 }
373 },
새로 만든 4개의 속성을 위 처럼 정의 하였다.(뭐 거의 Copy & Paste 였다..ㅡ.ㅡ;)
Initialize 메서드에서 초기화 작업시에 이미지라면 미리 정의된 스타일 대신에 이미지를 보여주게끔 변경하였다.
489 if(this._outImageUrl != null)
490 {
491 this._header.style.width = this._width;
492 this._header.style.height = this._height;
493 this._header.style.backgroundImage = "url("+this._outImageUrl+")";
494 this._header.style.cursor = "pointer";
495 }
496 else
497 {
498 Sys.UI.DomElement.addCssClass(this._headerOuterWrapper, "ajax__tab_outer");
499 Sys.UI.DomElement.addCssClass(this._headerInnerWrapper, "ajax__tab_inner");
500 }
Tab의 Header는 위에서 보여지는 _header라는 span 태그로 렌더된다.
위 코드를 보면 알겠지만 이미지의 경우에는 _header의 backgroundImage 로 설정되게끔 되었다.
Image 태그가 아니므로 Height와 Width가 맞지 않으면 Header 이미지는 옳게 렌더되지 않는다.
_activate 메서드 또한 아래 처럼 추가 하였다.
543 if(this._overImageUrl != null)
544 {
545 this._header.style.backgroundImage = "url("+this._overImageUrl+")";
546 }
547 else
548 {
549 Sys.UI.DomElement.addCssClass(this._tab, "ajax__tab_active");
550 }
_deactivate 메서드도 아래 처럼 추가 하였다.
560 if(this._outImageUrl != null)
561 {
562 this._header.style.backgroundImage = "url("+this._outImageUrl+")";
563 }
564 else
565 {
566 Sys.UI.DomElement.removeCssClass(this._tab, "ajax__tab_active");
567 }
이제 onmouseover와 onmouseout 만 추가 하면 js 파일은 완성된다.
586 _header_onmouseover : function(e)
587 {
588 if(this._overImageUrl != null)
589 {
590 this._header.style.backgroundImage = "url("+this._overImageUrl+")";
591 }
592 else
593 {
594 Sys.UI.DomElement.addCssClass(this._tab, "ajax__tab_hover");
595 }
596 },
597 _header_onmouseout : function(e)
598 {
599 if(this._outImageUrl != null)
600 {
601 if(!this._active)
602 {
603 this._header.style.backgroundImage = "url("+this._outImageUrl+")";
604 }
605 }
606 else
607 {
608 Sys.UI.DomElement.removeCssClass(this._tab, "ajax__tab_hover");
609 }
610 },
이제 수정된 TabPanel 코드에 대해서 살펴 보자.
162 [DefaultValue("")]
163 [Category("Appearance")]
164 [ExtenderControlProperty]
165 [ClientPropertyName("overImageUrl")]
166 public string OverImageUrl
167 {
168 get { return (string)(ViewState["OverImageUrl"] ?? string.Empty); }
169 set { ViewState["OverImageUrl"] = value; }
170 }
171
172 [DefaultValue("")]
173 [Category("Appearance")]
174 [ExtenderControlProperty]
175 [ClientPropertyName("outImageUrl")]
176 public string OutImageUrl
177 {
178 get { return (string)(ViewState["OutImageUrl"] ?? string.Empty); }
179 set { ViewState["OutImageUrl"] = value; }
180 }
181
182 [DefaultValue("")]
183 [Category("Appearance")]
184 [ExtenderControlProperty]
185 [ClientPropertyName("width")]
186 public override Unit Width
187 {
188 get
189 {
190 return base.Width;
191 }
192 set
193 {
194 base.Width = value;
195 }
196 }
197
198 [DefaultValue("")]
199 [Category("Appearance")]
200 [ExtenderControlProperty]
201 [ClientPropertyName("height")]
202 public override Unit Height
203 {
204 get
205 {
206 return base.Height;
207 }
208 set
209 {
210 base.Height = value;
211 }
212 }
단지 Property 를 추가해 준것 뿐인데..
주의 할 것은 다음과 같다.
먼저 Height속성과 Width 속성은 파생된 속성이다. 단지 사용하지 않을 뿐이었으나 사용하기 위하여 먼저 재정의를 거친다.
override 라는 키워드가 그것이다.
또 주의할 사항은 ExtenderControlProperty 라는 어트리뷰트이다.
이 어트리뷰트는 렌더될때 이 속성을 속성인지 이벤트인지 등등의 판별하는 키워드의 역할을 한다.
Sys.Application.add_init(function() {
$create(AjaxControlToolkit.TabPanel, {"headerTab":$get("__tab_TabContainer1_TabPanel1"),"height":"35px","outImageUrl":"/images/sub_02_04_02.gif","overImageUrl":"/images/sub_02_04_01.gif","scrollBars":4,"width":"190px"}, null, {"owner":"TabContainer1"}, $get("TabContainer1_TabPanel1"));
});
Sys.Application.add_init(function() {
$create(AjaxControlToolkit.TabPanel, {"headerTab":$get("__tab_TabContainer1_TabPanel2"),"height":"","width":""}, {"click":OnClientClick}, {"owner":"TabContainer1"}, $get("TabContainer1_TabPanel2"));
});
위 예시는 이 예제의 렌더링 된 html 소스이다.
맨 아랫부분을 보게 되면 위 코드를 확인 할 수 있다.
$create라는 전역 메서드로 개체를 생성하는데 있어 2번째 매개변수가 바로 property의 영역이다. 3번째는 event의 영역이 되겠다.
위에서 설명한 어트리뷰트를 어떻게 설정하는지에 따라 렌더되는 위치가 달라지며 js파일에서 읽혀지는 것도 달라진다.
나중에 이 부분에 대해서 더 살펴볼 날도 있을지는...모르겠다..^^;
어쨌든...
위처럼 TabPanel.cs 클래스와 Tabs.js 파일을 수정하게 되면 이제 Image로 Header를 사용할 수 있게 된다.
이제 ActiveTabChanged 라는 서버이벤트에 대해서 알아 보자.
이 부분은 꽁수다.(내가 발견한 건 물론..아니고..ㅡ.ㅡ;)
아마 버그라고 추정이 되나..설마 정말 이런식으로 사용하라고 이 이벤트를 만들지는 않았으리라..
위의 TabContainer 의 소개에서 예시로 보여 주었던 OnClientActiveTabChanged 이벤트를 아래와 같이 정의 한다.
function OnClientActiveTabChanged(sender, e)
{
alert('OnClientActiveTabChanged');
__doPostBack('<%=TabContainer1.ClientID %>');
}
코드를 보면 알겠지만..
사용자 임의로 postback을 일으킨다.
그 대상은 TabContainer가 된다.
TabPanel의 ContentTemplate내부에 아래 코드를 삽입하도록 한다.
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="TabContainer1" EventName="ActiveTabChanged" />
</Triggers>
<ContentTemplate>
ID :<asp:Label runat="server" ID="lblSelectedTab" />
</ContentTemplate>
</asp:UpdatePanel>
이 코드는 Trigger를 통해 TabContainer의 ActiveTabChanged 이벤트를 호출하는 내용이다.
이렇게 되면
__doPostBack(
'<%=TabContainer1.ClientID %>'); 를 통해 위의 트리거가 호출되게 된다.
그렇게 되면 아래와 같은 서버 코드가 실행된다.
protected void OnActiveTabChanged(object sender, EventArgs e)
{
this.lblSelectedTab.Text = this.TabContainer1.Tabs[this.TabContainer1.ActiveTabIndex].ID;
if (this.TabContainer1.ActiveTabIndex == 1)
{
this.BindData();
}
}
이렇게 되면 서버 이벤트를 얻을 수는 있다.
그러나 이 코드가 꽁수라고 말하는 것은 이 코드가 상당히 매끄럽지 못하다는 느낌 때문이다.
예를 들어 TabChangedEventArgs 라는 클래스라도 만들어서 매개변수로 넘겨주어야..그나마 깔끔해진다.
마치 Nobot 이나 Accodion 컨트롤 처럼 말이다.
이 글의 처음에 잠깐 언급했던 asp.net 1.x 버전의 Tab 컨트롤도 이런 의미의 EventArgs 가지고 있다.
어쨋든 이 정도면 이 컨트롤을 별 무리 없이 사용할 수 있으리라 판단이 된다.