Всплывающее меню для одностраничного портала

Введение


Недавно решил изучить ASP.NET и одновременно создать что-нибудь полезное, посложнее «Hello, world!». Первое знакомство с ASP.NET у меня началось с IBuySpy. Довольно быстро разобравшись с этим Shared Source проектом, я начал его перекраивать под свои нужды. В итоге осталось довольно мало оригинального кода, кроме части ядра (ибо, зачем изобретать велосипед? :)). Кроме всего прочего, хотелось сделать больше функциональности, например, всплывающее меню. В сети много примеров всяких всплывающих менюшек, в том числе и исходников Open Source. Но они, как правило, сложны и не очень подходили под мои нужды. Поэтому я решил написать простое меню в виде пользовательского элемента и предложить Вам результаты своего творчества. В результате должно получиться это:



1. Реализация базы данных


Начнем с написания базы данных. Для этой статьи я немного переделал таблицу Tabs из IBuySpy:

CREATE TABLE [Tabs]
  (
              [
TabID] [int] IDENTITY (1, 1) NOT NULL,
              [
TabOrder] [int] NOT NULL,
              [
TabName] [nvarchar] (50) NOT NULL,
              [
ParentTab] [int] NOT NULL
 
) ON [PRIMARY]
 
GO

TabID – идентификатор закладки,

TabOrder – порядковый номер закладки,

TabName – имя закладки,

ParentTab – указывает на идентификатор родительской закладки или имеет -1, если это верхний уровень меню.

Также необходимо написать пару процедур. Одну для составления списка меню из закладок (Tabs):

CREATE PROCEDURE GetMenuItems
 
AS
 
SELECT   
      TabID
,
      
TabOrder,
      
TabName
  FROM
      Tabs
  WHERE
      ParentTab
= -1
  ORDER BY
      TabOrder
  GO

И еще одну для получения подменю для текущей закладки:

CREATE PROCEDURE GetSubMenuItems
 
(
      @
ParentTab int
 
)
  AS
 
SELECT   
      TabID
,
      
TabOrder,
      
TabName
  FROM
      Tabs
  WHERE
      ParentTab
= @ParentTab
  ORDER BY
      TabOrder
  GO

Надеюсь, тут все ясно. В принципе, можно обойтись только второй процедурой, передавая ей в качестве параметра -1 для заглавного меню. Но для большей простоты и наглядности оставим все как есть.

В качестве примера наполним таблицу следующими значениями:



На этом наша работа с SQL Server закончена, переходим к кодированию проекта.

2. Кодирование пользовательского элемента


Создадим для сего произведения новый проект. Он будет состоять из простой формы с таблицей для тестирования, класса для доступа к данным и собственно пользовательского элемента.

Для доступа к данным из базы создадим свой класс DataSource. Вот его код:

using System;
 
using System.Configuration;
 
using System.Data;
 
using System.Data.SqlClient;
 
namespace Daenur.TabList
 
{
   
/// <summary>
    /// Summary description for DataSource.
    /// </summary>
   
public class DataSource
   
{
      public
SqlDataReader GetMenuItems()
      {
       
// создаем соединение (connectionString берем из Web.config)
       
SqlConnection myConnection = new SqlConnection(ConfigurationSettings.AppSettings["connectionString"]);
       
SqlCommand myCommand = new SqlCommand("GetMenuItems", myConnection);
       
myCommand.CommandType = CommandType.StoredProcedure;// и получаем данные
       
myConnection.Open();
       
SqlDataReader reader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
        return
reader;
      }
      public
SqlDataReader GetSubMenuItems(int parentTab)
      {
       
// создаем соединение (connectionString берем из Web.config)
       
SqlConnection myConnection = new SqlConnection(ConfigurationSettings.AppSettings["connectionString"]);
       
SqlCommand myCommand = new SqlCommand("GetSubMenuItems", myConnection);
       
// добавляем параметр - № родительской закладки
       
SqlParameter parameterParentTab = new SqlParameter("@ParentTab", SqlDbType.Int, 4);
       
parameterParentTab.Value = parentTab;
       
myCommand.Parameters.Add(parameterParentTab);
       
myCommand.CommandType = CommandType.StoredProcedure;
       
// и получаем данные
       
myConnection.Open();
       
SqlDataReader reader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
        return
reader;
      }
    }
  }

Примечание: для тех, кто не знает, как использовать Web.config для хранения строки соединения, приведу пример:

<appSettings>
    <!--
строка соединения с базой -->
    <
add key="ConnectionString" value="server=localhost;database=TabList;uid=<Ваш_логин>;password=<Ваш_пароль>;" />
  </
appSettings>

Теперь создадим в проекте новый пользовательский элемент управления и назовем его TabList. В файле TabList.ascx напишем следующее:

<%@ Control Language="c#" AutoEventWireup="false" Codebehind="TabList.ascx.cs" Inherits="Daenur.TabList.TabList"
TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
  <
table id="menuTable" height="*" cellSpacing="0" cellPadding="0" width="*" border="1" runat="server">
  </
table>
  <
script language="javascript" type="text/javascript">
  var
currentSubMenu = null;
  var
currentSubMenuNum = 0;
  var
closeTimer = null;
  function
openSubMenu(num)
  {
    var
subMenu = document.getElementById("TabList_subMenu" + num);
    if (
subMenu != null)
    {
      
cancelCloseTime();
      
subMenu.style.display='';
      if ((
currentSubMenu != null) && (currentSubMenuNum != num))
      {
       
currentSubMenu.style.display='none';
      }
      
currentSubMenu = subMenu;
      
currentSubMenuNum = num;
    }
  }
  function
closeTime()
  {
   
closeTimer = window.setTimeout(closeMenu, 1000);
  }
  function
cancelCloseTime()
  {
    if (
closeTimer != null)
    {
      
window.clearTimeout(closeTimer);
      
closeTimer = null;
    }
  }
  function
closeMenu()
  {
    if (
currentSubMenu != null)
    {
      
currentSubMenu.style.display='none';
      
currentSubMenu = null;
      
currentSubMenuNum = 0;
    }
  }
 
document.onclick = closeMenu;
 
</script>

Пока сам элемент это пустая таблица, которая будет наполняться данными из базы и автоматически создавать структуру меню при загрузке страницы. Скрипт здесь нужен для показа или скрытия подменю. При наведении курсора на пункт меню его подменю появляется, а при клике на форме или просто после покидания пункта меню курсором – исчезает.

Файл с кодом (TabList.cs) выглядит так:

namespace Daenur.TabList
 
{
   
using System;
   
using System.Data;
   
using System.Data.SqlClient;
   
using System.Web;
   
using System.Web.UI;
   
using System.Web.UI.HtmlControls;
   
/// <summary>
    /// Summary description for TabList.
    /// </summary>
    // Направление меню (горизонтальное или вертикальное)
   
public enum repeatDirection{Horizontal = 0, Vertical};
    public abstract class
TabList : System.Web.UI.UserControl
   
{
      protected
System.Web.UI.HtmlControls.HtmlTable menuTable;
      private
DataSource data = new DataSource();// источник данных
      
public repeatDirection RepeatDirection; // направление меню
      
private void Page_Load(object sender, System.EventArgs e)
      {
       
// Put user code to initialize the page here
       
DataBind();
      }
      public
override void DataBind()
      {
      
// получаем все закладки
       
SqlDataReader menuItems = data.GetMenuItems();// если меню горизонтальное
       
if (RepeatDirection == repeatDirection.Horizontal)
        {
         
// то формируем строку в нашей таблице
         
HtmlTableRow tr = new HtmlTableRow();
         
tr.ID = "menu";
         
tr.Attributes.Add("runat", "server");
          while (
menuItems.Read())
          {
            
HtmlTableCell td = new HtmlTableCell();
            
td.ID = "menuItem" + menuItems["TabOrder"];
            
// создаем код закладки - ссылки
            // (здесь: onmouseout и onmouseover - события, обработчики которых находятся в скрипте)
            
string HtmlText = @"<nobr> &nbsp; <a onmouseout=""closeTime()"" onmouseover=""openSubMenu(" +
   
menuItems["TabOrder"] + @")"" href=""" + menuItems["TabOrder"] + @"""> Tab" +
   
menuItems["TabOrder"] + " </a> &nbsp; </nobr>";
            
td.Controls.Add(new LiteralControl(HtmlText));
            
// для каждой закладки создаем свою таблицу - подменю
            
td.Controls.Add(SubMenu((int) menuItems["TabOrder"]));
            
tr.Controls.Add(td);
          }
         
menuTable.Controls.Add(tr);
        }
        else
// RepeatDirection.Vertical
       
{
         
// иначе формируем столбец
          // (все остальное аналогично)
         
while (menuItems.Read())
          {
            
HtmlTableRow tr = new HtmlTableRow();
            
tr.ID = "menu";
            
tr.Attributes.Add("runat", "server");
            
HtmlTableCell td = new HtmlTableCell();
            
td.ID = "menuItem" + menuItems["TabOrder"];
            
string HtmlText = @"&nbsp; <a onmouseout=""closeTime()"" onmouseover=""openSubMenu(" +
   
menuItems["TabOrder"] + @")"" href=""" + menuItems["TabOrder"] + @"""> Tab" +
   
menuItems["TabOrder"] + " </a> &nbsp;";
            
td.Controls.Add(new LiteralControl(HtmlText));
            
td.Controls.Add(SubMenu((int) menuItems["TabOrder"]));
            
tr.Controls.Add(td);
            
menuTable.Controls.Add(tr);
          }
        }
      }
      
/// <summary>
      ///   Функция, создающая подменю для нужной закладки
      /// </summary>
      
private HtmlTable SubMenu(int tabOrder)
      {
       
// получаем все закладки для подменю
       
SqlDataReader subMenuItems = data.GetSubMenuItems(tabOrder);
       
// создаем таблицу подменю
       
HtmlTable tbl = new HtmlTable();
       
tbl.ID = "subMenu" + tabOrder.ToString();
       
tbl.Style.Add("display", "none");// скрыта по умолчанию
       
tbl.Style.Add("position", "absolute");// появляется поверх всего
  //    tbl.Style.Add("filter", "alpha (opacity=75)"); // прозрачность
       
tbl.Attributes.Add("cellSpacing", "0");
       
tbl.Attributes.Add("cellPadding", "0");
       
tbl.Attributes.Add("width", "160px");
       
tbl.Attributes.Add("border", "1");
       
tbl.Attributes.Add("bgcolor", "white");
       
// назначаем обработчики событий
       
tbl.Attributes.Add("onmouseover", "cancelCloseTime()");// при наведении курсора
       
tbl.Attributes.Add("onmouseout", "closeTime()");// при выходе за пределы подменю
        // формируем столбец, состоящий из ссылок
       
while (subMenuItems.Read())
        {
         
HtmlTableRow tr = new HtmlTableRow();
         
HtmlTableCell td = new HtmlTableCell();
         
td.InnerHtml = @"&nbsp; <a href=""" + tabOrder + "-" + subMenuItems["TabOrder"] + @"""> SubTab"
   
tabOrder + "-" + subMenuItems["TabOrder"] + "</a> &nbsp;";
         
tr.Cells.Add(td);
         
tbl.Controls.Add(tr);
        }
        return
tbl;// возвращаем готовое подменю
      
}
      
#region Web Form Designer generated code
      
override protected void OnInit(EventArgs e)
      {
                  
//
                // CODEGEN: This call is required by the ASP.NET Web Form Designer.
                //
       
InitializeComponent();
       
base.OnInit(e);
      }
      
/// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      
private void InitializeComponent()
      {
       
this.Load += new System.EventHandler(this.Page_Load);
      }
      
#endregion
   
}
  }

Для тестирования нашего меню необходима HTML страница, содержащая наш элемент. Вот HTML код основной формы:

<%@ Register TagPrefix="TabListControl" TagName="TabList" Src="TabList.ascx" %>
  <%@
Page language="c#" Codebehind="Default.aspx.cs" AutoEventWireup="false" Inherits="Daenur.TabList.DefaultForm" %>
  <!
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
  <
HTML>
   <
HEAD>
    <
title>DefaultForm</title>
   </
HEAD>
   <
body MS_POSITIONING="GridLayout">
    <
TABLE height="523" cellSpacing="0" cellPadding="0" width="174" border="0" ms_2d_layout="TRUE">
     <
TR vAlign="top">
      <
TD width="174" height="523">
       <
form id="DefaultForm" name="DefaultForm" action="Default.aspx" method="post">
        <
TABLE height="157" cellSpacing="0" cellPadding="0" width="511" border="0" ms_2d_layout="TRUE">
         <
TR vAlign="top">
          <
TD width="10" height="15"></TD>
          <
TD width="501"></TD>
         </
TR>
         <
TR vAlign="top">
          <
TD height="142"></TD>
          <
TD>
           <
table height="141" cellSpacing="0" cellPadding="0" width="500">
            <
tr>
             <
td width="20"></td>
             <
td width="*">
               <
TABLISTCONTROL:TABLIST id="TabList" runat="server" RepeatDirection="Horizontal"></TABLISTCONTROL:TABLIST>
             </
td>
             <
td width="20"></td>
            </
tr>
            <
tr>
             <
td width="20"></td>
             <
td width="*"></td>
             <
td width="20">
             </
td>
            </
tr>
           </
table>
          </
TD>
         </
TR>
        </
TABLE>
       </
form>
      </
TD>
     </
TR>
    </
TABLE>
   </
body>
  </
HTML>

Здесь все просто. Мы создали на форме таблицу и в одну из ее ячеек поместили наш элемент TabList.

Таким образом, получаем само меню в виде таблицы со строкой или столбцом элементов в зависимости от заданного направления и скрытые таблицы – подменю. Видимыми их делает выполнение скрипта при наведении курсора.

Кстати, чтобы поменять направление меню на горизонтальное, достаточно исправить строку в файле Default.aspx:

RepeatDirection="Horizontal" на RepeatDirection="Vertical"

В результате:



Заключение


Как видите, все довольно просто (а Вы как хотели :)). Я показал, как можно расширить функциональность одностраничного портала, похожего на IBuySpy. Извиняюсь за возможные недочеты. В этом примере показана только суть. Остальное Вы сможете легко реализовать сами. Например, можно прикрутить сюда какую-нибудь графику или просто добавить новые свойства вроде цвета фона. Успехов в изучении новых технологий!

Автор: Золотухин Роман