Всплывающее меню для одностраничного портала (исходники)Источник: 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. Извиняюсь за возможные недочеты. В этом примере показана только суть. Остальное Вы сможете легко реализовать сами. Например, можно прикрутить сюда какую-нибудь графику или просто добавить новые свойства вроде цвета фона. Успехов в изучении новых технологий! |