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

Источник: hardline
Золотухин Роман

Недавно решил изучить 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>   <a onmouseout=""closeTime()"" onmouseover=""openSubMenu(" + 
    menuItems["TabOrder"] + @")"" href=""" + menuItems["TabOrder"] + @"""> Tab" + 
    menuItems["TabOrder"] + " </a>   </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 = @"  <a onmouseout=""closeTime()"" onmouseover=""openSubMenu(" + 
    menuItems["TabOrder"] + @")"" href=""" + menuItems["TabOrder"] + @"""> Tab" + 
    menuItems["TabOrder"] + " </a>  "; 
            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 = @"  <a href=""" + tabOrder + "-" + subMenuItems["TabOrder"] + @"""> SubTab" +  
    tabOrder + "-" + subMenuItems["TabOrder"] + "</a>  "; 
          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. Извиняюсь за возможные недочеты. В этом примере показана только суть. Остальное Вы сможете легко реализовать сами. Например, можно прикрутить сюда какую-нибудь графику или просто добавить новые свойства вроде цвета фона. Успехов в изучении новых технологий!

 

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=21592