BlogEngine(4)---Widget小部件
?? 前面的兩篇文章中,我們分別介紹了BE的插件和主題機(jī)制,這一篇我們來看看BE三大特性中的最后一個(gè):Widget。
所謂的widget,在BE中可以理解為一塊特定的顯示區(qū)域,在這個(gè)區(qū)域中可以用來顯示文章分類信息,博主個(gè)人信息,訪客信息等等一系列你可以想到的東西。在BE中,一個(gè)widget就是一個(gè)用戶控件,統(tǒng)一放在widget目錄中。當(dāng)用戶想添加自己的widget時(shí)只需要在widget下添加以這個(gè)widget命名的文件夾以及對(duì)應(yīng)的widget控件,相當(dāng)?shù)姆奖恪O旅嬖蹅兙蛠硗ㄟ^一個(gè)簡單的例子來“重現(xiàn)”widget的實(shí)現(xiàn)方法。當(dāng)然,在這個(gè)例子中我只是實(shí)現(xiàn)了“顯示”而已,額外的“編輯”,“排序”在弄懂了下面的實(shí)現(xiàn)后應(yīng)該不難。
"重現(xiàn)"widget
?? 首先看一下項(xiàng)目圖,我仍然使用的上次實(shí)現(xiàn)主題更換的那個(gè)項(xiàng)目。只不過添加了一個(gè)widgets文件夾,并在其中放置了Search和TextBox兩個(gè)widget,具體的widget.ascx中的內(nèi)容我們后面再看。
?? 重點(diǎn)看下面三個(gè)用戶控件。
?????? WidgetBase.ascx:這個(gè)用戶控件時(shí)所有widget的基類,所有的widget都要繼承這個(gè)用戶控件。它定義了所有widget的一些通用的屬性,比如名字,是否可編輯,是否顯示標(biāo)題等等。
???? WidgetContainer.ascx:這個(gè)用戶控件可以看成是對(duì)widget的一層包裝,所有的widget最后并不是直接顯示到頁面中的,而是要經(jīng)過這個(gè)控件的包裝確定統(tǒng)一的顯示外觀后再顯示到頁面中。這樣做的好處顯而易見,用戶在前臺(tái)能夠看到一個(gè)具有統(tǒng)一界面與操作體驗(yàn)的widget。
??? ? WidgetZone.ascx:所有的用戶控件最后不可能散落的顯示在頁面的各個(gè)地方,肯定有一個(gè)專門的地方(容器)在盛放這些控件。而這個(gè)WidgetZone就是用來盛放這些控件的了。
?
?? 大體介紹了幾個(gè)必要的控件的作用。我們從具體的一個(gè)widget入手(比如說Search),來看看到底widget是怎樣被加入到頁面中并顯示出來的。下面是search的widget.ascx中的代碼:
?
?
public partial class Widget : WidgetBase {public override bool IsEditable { get { return true; } }public override string Name { get { return "搜索"; } }public override void LoadWidget() { //var settings = this.GetSettings(); //if (!settings.ContainsKey("content")) //{ // return; //}string content = "<input type='text' id='key' /><input type='submit' value='search' id='btnSubmit'/>";LiteralControl text = new LiteralControl { Text = content }; this.Controls.Add(text); } }根據(jù)前面講到的,這個(gè)widget必須繼承WidgetBase,以便讓每個(gè)widget都有統(tǒng)一的屬性。在這個(gè)widget中沒有自己的方法,都是通過override來重寫了父類中的方法或者屬性。那就來看看WidgetBase中的內(nèi)容:
public abstract partial class WidgetBase : System.Web.UI.UserControl { /// <summary> /// Gets a value indicating whether the header is visible. This only takes effect if the widgets isn't editable. /// </summary> /// <value><c>true</c> if the header is visible; otherwise, <c>false</c>.</value> public virtual bool DisplayHeader { get { return true; } }/// <summary> /// Gets a value indicating whether or not the widget can be edited. /// <remarks> /// The only way a widget can be editable is by adding a edit.ascx file to the widget folder. /// </remarks> /// </summary> public abstract bool IsEditable { get; }/// <summary> /// Gets the name. It must be exactly the same as the folder that contains the widget. /// </summary> public abstract string Name { get; }/// <summary> /// Gets or sets a value indicating whether [show title]. /// </summary> /// <value><c>true</c> if [show title]; otherwise, <c>false</c>.</value> public bool ShowTitle { get; set; }/// <summary> /// Gets or sets the title of the widget. It is mandatory for all widgets to set the Title. /// </summary> /// <value>The title of the widget.</value> public string Title { get; set; }/// <summary> /// Gets or sets the widget ID. /// </summary> /// <value>The widget ID.</value> public Guid WidgetId { get; set; }/// <summary> /// Gets or sets the name of the containing WidgetZone /// </summary> public string Zone { get; set; }/// <summary> /// GetSettings會(huì)根據(jù)WidgetID從儲(chǔ)存介質(zhì)中獲得相應(yīng)的配置信息(也就是內(nèi)容信息)并存儲(chǔ)在Cache中 /// </summary> public StringDictionary GetSettings() { //MOCK var cacheId = string.Format("be_widget_{0}", this.WidgetId); if (this.Cache[cacheId] == null) { StringDictionary s = new StringDictionary(); s.Add("content", "<a href='#' >text href</a>"); // var ws = new WidgetSettings(this.WidgetId.ToString()); this.Cache[cacheId] = s; }return (StringDictionary)this.Cache[cacheId]; }/// <summary> /// 這個(gè)方法在用戶自定義的widget中被重寫 /// </summary> public abstract void LoadWidget();protected override void Render(HtmlTextWriter writer) { if (string.IsNullOrEmpty(this.Name)) { throw new NullReferenceException("Name must be set on a widget"); }base.Render(writer); } }前面的那一大堆用英文注釋的屬性是我直接從BE中拿過來的,就是定義了一些widget的共有的特性。值得注意的有兩個(gè)方法:1.GetSetting,這個(gè)方法用于從存儲(chǔ)介質(zhì)(數(shù)據(jù)庫,xml)中獲得這個(gè)widget的一些配置信息,相當(dāng)于給每個(gè)widget提供了一個(gè)自由存儲(chǔ)的功能。2.loadWidget,這個(gè)方法是一個(gè)抽象方法,在子類中實(shí)現(xiàn)。好像看到這里我們并沒有看到這個(gè)方法是怎樣被調(diào)用的,先不著急。我們接著往下看 :)
現(xiàn)在widget都一切準(zhǔn)備就緒了,就差其他人來將它加到特定的 顯示區(qū)域顯示了。這個(gè)任務(wù)交給widgetZone來完成。下面看看widgetZone的實(shí)現(xiàn):
public abstract partial class WidgetZone : System.Web.UI.UserControl { //用于存放所有的WidgetZone及其對(duì)應(yīng)的子widget信息,無論WidgetZone有幾個(gè),這個(gè)只有一個(gè) private static readonly Dictionary<string, XmlDocument> XmlDocumentByZone = new Dictionary<string, XmlDocument>();private string zoneName = "be_WIDGET_ZONE";/// <summary> /// 區(qū)域的名字(標(biāo)志) /// </summary> public string ZoneName { get { return zoneName; }set { zoneName = value; } }/// <summary> /// 這個(gè)zone包含的子widgetxml信息 /// </summary> private XmlDocument XmlDocument { get { return XmlDocumentByZone.ContainsKey(ZoneName) ? XmlDocumentByZone[ZoneName] : null; } }protected override void OnInit(EventArgs e) { //從存儲(chǔ)介質(zhì)中獲得這個(gè)widgetZone所包含的widget信息 if (XmlDocument == null) { //Mock data string mockXml = "<widgets>" + "<widget id=\"d9ada63d-3462-4c72-908e-9d35f0acce40\" title=\"TextBox\" showTitle=\"True\">TextBox</widget> " + "<widget id=\"19baa5f6-49d4-4828-8f7f-018535c35f94\" title=\"Administration\" showTitle=\"True\">Administration</widget> " + "<widget id=\"d81c5ae3-e57e-4374-a539-5cdee45e639f\" title=\"Search\" showTitle=\"True\">Search</widget> " + "<widget id=\"77142800-6dff-4016-99ca-69b5c5ebac93\" title=\"Tag cloud\" showTitle=\"True\">Tag cloud</widget>" + "<widget id=\"4ce68ae7-c0c8-4bf8-b50f-a67b582b0d2e\" title=\"RecentPosts\" showTitle=\"True\">RecentPosts</widget>" + "</widgets>"; XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(mockXml); XmlDocumentByZone[ZoneName] = xmlDocument; } base.OnInit(e); }protected override void OnLoad(EventArgs e) { //將取出的每個(gè)widget控件寫入 var zone = this.XmlDocument.SelectNodes("//widget"); if (zone == null) { return; }This is for compatibility with older themes that do not have a WidgetContainer control. //var widgetContainerExists = WidgetContainer.DoesThemeWidgetContainerExist(); //var widgetContainerVirtualPath = WidgetContainer.GetThemeWidgetContainerVirtualPath();foreach (XmlNode widget in zone) { var fileName = string.Format("{0}widgets/{1}/widget.ascx", Utils.RelativeWebRoot, widget.InnerText); try { //加載特定的控件,控件類型為WidgetBase(因?yàn)槊總€(gè)控件都繼承了WidgetBase) var control = (WidgetBase)Page.LoadControl(fileName); if (widget.Attributes != null) { //從讀取的xml屬性中將值復(fù)制給control屬性 control.WidgetId = new Guid(widget.Attributes["id"].InnerText); control.Title = widget.Attributes["title"].InnerText; control.ShowTitle = control.IsEditable ? bool.Parse(widget.Attributes["showTitle"].InnerText) : control.DisplayHeader; }control.ID = control.WidgetId.ToString().Replace("-", string.Empty); control.Zone = zoneName;control.LoadWidget();//將此控件包裝到widgetContainer里面,這樣每個(gè)control都有一個(gè)統(tǒng)一的外觀(修改,刪除按鈕在這里統(tǒng)一) var widgetContainer = WidgetContainer.GetWidgetContainer(control); //將包裝好的widget加入這個(gè)zone中 Controls.Add(widgetContainer); } catch (Exception ex) { //找不到則不加載 } }base.OnLoad(e); }protected override void Render(System.Web.UI.HtmlTextWriter writer) { writer.Write("<div id=\"widgetzone_{0}\" class=\"widgetzone\">", this.zoneName);base.Render(writer);writer.Write("</div>");//如果沒有權(quán)限修改widget,則不輸出管理按鈕 //if (!Security.IsAuthorizedTo(Rights.ManageWidgets)) //{ // return; //}//var selectorId = string.Format("widgetselector_{0}", this.zoneName); //writer.Write("<select id=\"{0}\" class=\"widgetselector\">", selectorId); //var di = new DirectoryInfo(this.Page.Server.MapPath(string.Format("{0}widgets", Utils.RelativeWebRoot))); //foreach (var dir in di.GetDirectories().Where(dir => File.Exists(Path.Combine(dir.FullName, "widget.ascx")))) //{ // writer.Write("<option value=\"{0}\">{1}</option>", dir.Name, dir.Name); //}//writer.Write("</select>??"); //writer.Write( // "<input type=\"button\" value=\"添加部件\" οnclick=\"BlogEngine.widgetAdmin.addWidget(BlogEngine.$('{0}').value, '{1}')\" />", //by Spoony // selectorId, // this.zoneName); //writer.Write("<div class=\"clear\" id=\"clear\">?</div>"); } }一些屬性我們就不啰嗦了,懂點(diǎn)看一下OnInit和OnLoad方法。在Oninit方法中會(huì)從存儲(chǔ)介質(zhì)中加載xml格式的需要加載的widget信息,里面記錄了這個(gè)widgetZone需要加載哪些widget,注意到XmlDocumentByZone這個(gè)屬性是個(gè)靜態(tài)的方法,也就是說如果有多個(gè)widgetZone,那么這個(gè)鍵值對(duì)里面將會(huì)有多個(gè)值。接著看onload方法,先將前面讀取到的xml中的所有widget標(biāo)簽解析出來,這樣就能得到具體的widget的信息。然后通過Page.LoadControl來加載widgets文件夾下面對(duì)應(yīng)的widget,然后根據(jù)讀取的xml信息,給這個(gè)從loadControl中加載的widget設(shè)置一些基本的信息(因?yàn)槔^承了WidgetBase,所以這里的設(shè)值就可以統(tǒng)一了)。設(shè)置完之后調(diào)用control.LoadWidget(); 來執(zhí)行用戶在loadwidget方法中的代碼。最后在通過widgetContainer將此widget包裝一下加入這個(gè)zone,具體怎么包裝的我們繼續(xù)來看widgetContainer就知道了。
public partial class WidgetContainer : System.Web.UI.UserControl { /// <summary> /// 要包裝的widget /// </summary> public WidgetBase Widget { get; set; }/// <summary> /// 獲得操作按鈕的html代碼 /// </summary> protected string AdminLinks { get { //根據(jù)用戶是否登錄,判斷是否顯示操作按鈕(刪除,修改等) //if (Security.IsAuthorizedTo(Rights.ManageWidgets)) //{ if (this.Widget != null) { var sb = new StringBuilder();var widgetId = this.Widget.WidgetId;sb.AppendFormat("<a class=\"delete\" href=\"#\" οnclick=\"BlogEngine.widgetAdmin.removeWidget('{0}');return false\" title=\"{1} widget\"><span class=\"widgetImg imgDelete\">?</span></a>", widgetId, "delete"); sb.AppendFormat("<a class=\"edit\" href=\"#\" οnclick=\"BlogEngine.widgetAdmin.editWidget('{0}', '{1}');return false\" title=\"{2} widget\"><span class=\"widgetImg imgEdit\">?</span>", this.Widget.Name, widgetId, "edit"); sb.AppendFormat("<a class=\"move\" href=\"#\" οnclick=\"BlogEngine.widgetAdmin.initiateMoveWidget('{0}');return false\" title=\"{1} widget\"><span class=\"widgetImg imgMove\">?</span></a>", widgetId, "move");return sb.ToString(); } //}return String.Empty; } }/// <summary> /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event. /// </summary> /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> protected override void OnLoad(EventArgs e) { base.OnLoad(e); ProcessLoad(); }private bool _processedLoad; /// <summary> /// Manually run the Initialization process. /// </summary> public void ProcessLoad() { if (_processedLoad) { return; }// phWidgetBody is the control that the Widget control // gets added to. var widgetBody = this.FindControl("phWidgetBody");if (widgetBody != null) { widgetBody.Controls.Add(this.Widget); } else { var warn = new LiteralControl { Text = "無法在當(dāng)前主題模板的部件容器中找到 ID 為 \"phWidgetBody\" 的控件."//by Spoony }; this.Controls.Add(warn); }_processedLoad = true; }/// <summary> /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. /// </summary> /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> protected override void OnPreRender(EventArgs e) { base.OnPreRender(e);// Hide the container if the Widget is null or also not visible. this.Visible = (this.Widget != null) && this.Widget.Visible; }/// <summary> /// 從主題中得到widgetContainer的位置 /// </summary> /// <returns></returns> public static string GetThemeWidgetContainerVirtualPath() { return string.Format("~/themes/{0}/WidgetContainer.ascx", "stardard" /*為了演示方便,這里直接讀取默認(rèn)的主題*/); }/// <summary> /// 得到特定主題下的widgetContainer的物理位置 /// </summary> /// <returns></returns> public static string GetThemeWidgetContainerFilePath() { return HostingEnvironment.MapPath(GetThemeWidgetContainerVirtualPath()); }/// <summary> /// 是否存在widgetContainer文件 /// </summary> /// <returns></returns> public static bool DoesThemeWidgetContainerExist() { // This is for compatibility with older themes that do not have a WidgetContainer control. return File.Exists(GetThemeWidgetContainerFilePath()); }/// <summary> /// 加載widgetContainer,用于包裝widget,如果當(dāng)前主題文件沒有提供widgtContainer.ascx,則使用默認(rèn)的容器 /// </summary> /// <param name="widgetControl"></param> /// <param name="widgetContainerExists"></param> /// <param name="widgetContainerVirtualPath"></param> /// <returns></returns> private static WidgetContainer GetWidgetContainer( WidgetBase widgetControl, bool widgetContainerExists, string widgetContainerVirtualPath) { //如果主題提供了用于包裝的widgetContainer,則讀取。否則返回某人的WidgetContainer WidgetContainer widgetContainer = widgetContainerExists ? (WidgetContainer)widgetControl.Page.LoadControl(widgetContainerVirtualPath) : new DefaultWidgetContainer();widgetContainer.ID = "widgetContainer" + widgetControl.ID; widgetContainer.Widget = widgetControl;return widgetContainer; }/// <summary> /// 加載widgetContainer,用于包裝widget,如果當(dāng)前主題文件沒有提供widgtContainer.ascx,則使用默認(rèn)的容器 /// </summary> public static WidgetContainer GetWidgetContainer( WidgetBase widgetControl) { return GetWidgetContainer(widgetControl, DoesThemeWidgetContainerExist(), GetThemeWidgetContainerVirtualPath()); } }重點(diǎn)來看GetWidgetContainer這個(gè)方法。他有三個(gè)參數(shù),第一個(gè)就是我們要包裝的widget對(duì)象,第二標(biāo)明了主題中是否提供了包裝樣式,如果沒有那么就使用默認(rèn)的包裝樣式,第三個(gè)參數(shù)是主體的虛擬路徑,用來從主題文件中加載包裝樣式文件。接著,程序通過判斷widgetContainerExists 來判斷到底應(yīng)該使用哪種包裝樣式,然后將傳進(jìn)來的widget對(duì)象賦值給這個(gè)包裝對(duì)象的widget屬性,供render的時(shí)候使用。具體的render方法并不在這個(gè)widgetContainer中。而是在默認(rèn)提供的包裝樣式控件和主題提供的樣式中,我們看一下某人提供的包裝容器:
internal sealed class DefaultWidgetContainer : WidgetContainer { /// <summary> /// The widgetBody instance needed by all WidgetContainers. /// </summary> private readonly System.Web.UI.WebControls.PlaceHolder widgetBody = new System.Web.UI.WebControls.PlaceHolder { ID = "phWidgetBody" };/// <summary> /// Initializes a new instance of the <see cref="DefaultWidgetContainer"/> class. /// </summary> internal DefaultWidgetContainer() { this.Controls.Add(this.widgetBody); }/// <summary> /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. /// </summary> /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> protected override void Render(HtmlTextWriter writer) { if (this.Widget == null) { throw new NullReferenceException("WidgetContainer requires its Widget property be set to a valid WidgetBase derived control"); }var widgetName = this.Widget.Name; var widgetId = this.Widget.WidgetId;if (string.IsNullOrEmpty(this.Widget.Name)) { throw new NullReferenceException("Name must be set on a widget"); }var sb = new StringBuilder();sb.AppendFormat("<div class=\"widget {0}\" id=\"widget{1}\">", widgetName.Replace(" ", string.Empty).ToLowerInvariant(), widgetId); sb.Append(this.AdminLinks); if (this.Widget.ShowTitle) { sb.AppendFormat("<h4>{0}</h4>", this.Widget.Title); } else { sb.Append("<br />"); }sb.Append("<div class=\"content\">");writer.Write(sb.ToString()); base.Render(writer); writer.Write("</div>"); writer.Write("</div>"); } }在默認(rèn)的提供的包裝容器中,首先聲明了一個(gè)placeholder用來放置widget,不然在WidgetContainer下的processLoad方法中會(huì)報(bào)錯(cuò)。主要還是看render方法,在這里就是具體怎樣顯示這個(gè)widget的外表了,比如標(biāo)題應(yīng)該顯示在哪里,內(nèi)容顯示在哪里等等布局。這樣就給所有的widget提供統(tǒng)一的樣式了。
好了,到這里widget的實(shí)現(xiàn)方式已經(jīng)說完了,不知道你是否已經(jīng)明白其中的流程?這是最后的效果圖:
源碼下載
http://www.vdisk.cn/down/index/7644535A9490
轉(zhuǎn)載于:https://www.cnblogs.com/qianlifeng/archive/2011/05/03/2034899.html
總結(jié)
以上是生活随笔為你收集整理的BlogEngine(4)---Widget小部件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: idea base64encoder没有
- 下一篇: rabbitmq direct 多个消费