Overview Structured Content (Tool) Overview Content that follows a specific, repeatable format that is stored and available throughout the project (i.e. Blog, News, Events, etc.) Structured content consists of: A Class that inherits from BusinessObject<T> to provide the data structure. A user-control that inherits from ToolGridList<T> to provide the list interface for managing entries. A user-control that inherits from ToolControl<T> to provide the interface for managing data entry. Once created the Control must be registered: System Administrator -> Data Sections -> Tools, and then provide permissions to the applicable roles. Usage Data can be accessed via the AdvantageModuleEngine that is available in the following components AdvantagePageTemplate AdvantageModule AdvantageModuleRewrite There are many ways to access the data. Please review the Methods of AdvantageModuleEngine. Data Entity (BusinessObject) BusinessObject<T> : BusinessObjectBase Methods & Properties Overview A BusinessObject is a container that holds the data entity you are creating. There is no need to interact with the database. All CRUD and versioning happens automatically when you inherit from BusinessObject<self> Any public property with a GET/SET will automatically be saved. Excluding a public property is possible if you decorate the property with [XMLIgnore]. Override methods (Mandatory) SetSummaryDataRow -> Used to create the data expression that will be used to fill the list tool for managing the data add/edit screen. Override methods (optional) SetSearchableProperties -> used to allow data to be indexed for performance benefits during searching and display. Usage Creating fields are done using properties. Specifying List fields for the tool control is done inside the SetSummaryDataRow method using AddSummaryDataRow(title, data) Example BlogArticle BusinessObjectnamespace AdvantageClient { /// <summary> /// Summary description for BlogArticle /// </summary> public class BlogArticle : BusinessObject<BlogArticle> { //Blogs are limited to the current domain only! public override bool RestrictToCurrentDomain { get { return true; } } //Title of article public string Title { get; set; } //Image of article private AdvantageImage _image = null; public AdvantageImage Image { get { if (_image == null) { this._image = new AdvantageImage(); this._image.AddImageElement(new AdvantageImageElement() { ImageName = "Mobile", }); this._image.AddImageElement(new AdvantageImageElement() { ImageName = "Desktop", BreakPointName = "Small"}); this._image.DefaultElementName = "Mobile"; } return _image; } set { _image = value; } } //Date of article private DateTime? _postedDate = null; public DateTime PostedDate { get { if (_postedDate==null) _postedDate=DateTime.Now; return (DateTime) _postedDate; } set { _postedDate = value; } } //Summary of article public string Summary { get; set; } //Content of article public string Content { get; set; } //Author of article public string Author { get; set; } //Is it a featured article public bool IsFeatured { get; set; } //Tags of article public List<string> Tags { get; set; } //Categories of article public List<Guid> Categories { get; set; } //Defines the columns and data to be displayed in the lister. // (Title,Date) protected override void SetSummaryDataRow() { if (string.IsNullOrEmpty(Title)) Title = ""; if (Title.Length > 50) { AddSummaryDataRow("Title", Title.PadRight(50).Substring(0, 50) + "..."); } else { AddSummaryDataRow("Title", Title); } AddSummaryDataRow("Date", PostedDate); } //Defines indexed lookup values that can be used when retrieving lists //when using AdvantageModuleEngine. protected override void SetSearchableProperties() { if (Tags != null) foreach (string tag in Tags) AddSearchableProperty("Tag", tag); if (Categories != null && Categories.Count > 0) { //create an entry for each category so it can be retrieved by the AdvantageModuleEngine foreach (Guid category in Categories) AddSearchableProperty("Category", category); } AddSearchableProperty("PostedDate", PostedDate); } //Optional PostPublish is called after the object is published. public override bool PostPublish(ActionArgs e, out eCMSEngineEventStatus status, out string message) { //This example stores the list of blog articles in cache on the front-end expression. //Once an article is published, the cache should be invalidated. AdvantageCMS.Core.Utils.Cache.AdvantageCacheManager cache = new AdvantageCacheManager(eAdvantageCacheContainerType.WebServer, e.AdminDomainId, e.AdminLanguageId, e.CurrentSql); cache.Invalidate(new string[] { "Blog" }); //This example also logs each article view in a separate object. //Create an initial log object if it does not exist. using (AdvantageModuleEngine me = new AdvantageModuleEngine(e.CurrentSql, e.AdminDomainId, e.AdminLanguageId)) { List<BlogLog> bls = me.GetPublishedObjectsBykey<BlogLog>("ArticleID", eComparison.Equals, this.MasterID); if ((bls == null) || (bls.Count == 0)) { BlogLog bl; bl = new BlogLog(); bl.ArticleID = this.MasterID; bl.Views = 0; ToolHelperResult success = me.BusinessHelper.PublishObject<BlogLog>(e, bl); } } return base.PostPublish(e, out status, out message); } } } Supporting classes/objects for example BlogLog BusinessObjectnamespace AdvantageClient { public class BlogLog : BusinessObject<BlogLog> { public Guid ArticleID { get; set; } public int Views { get; set; } protected override void SetSummaryDataRow() { AddSummaryDataRow("Display", ArticleID); } protected override void SetSearchableProperties() { AddSearchableProperty("ArticleID", ArticleID); } public override bool Validate(eCMSActions action, ActionArgs e, out eCMSEngineEventStatus status, out string message) { status = eCMSEngineEventStatus.Success; message = string.Empty; if (action == eCMSActions.Publish && ArticleID == Guid.Empty) { message += "Article Id cannot be blank.<br/>"; status = eCMSEngineEventStatus.Warning; } return status == eCMSEngineEventStatus.Success; } } } Tool List Control ToolGridList<T>:ToolListBase (usercontrol) Properties & Methods Overview A user-control that allows a developer to create a list of all the structured content of BusinessObject<T> to be managed in the admin interface. Usage Adding the control and registering it with the associated BusinessObject allows for management of the entry records. Define the GridList Control to bind. Defined the fields to display based on the SetSummaryDataRow method defined in the BusinessObject<T> Override the GetDataSource() method to customize display entries. Example BlogArticleList.ascx<%@ Control Language="C#" AutoEventWireup="true" CodeFile="BlogArticleList.ascx.cs" Inherits="AdvantageClient.ClientAdmin_BlogArticleList" %> <!-- This is the control that will be used to display the list of blog articles --> <advantage:AdvantageToolList runat="server" ID="StandardAdvantageToolListControl1" /> BlogArticleList.ascx.csnamespace AdvantageClient { public partial class ClientAdmin_BlogArticleList : ToolGridList<BlogArticle> { protected override void OnInit(EventArgs e) { grdToolList = StandardAdvantageToolListControl1.Grid; base.OnInit(e); } public override void DefineGridColumns() { DefineColumn("Title", "Title", eDataType.String, true, true, true); DefineColumn("Date", "Date", eDataType.Date, true, true, true); DefineColumn("My Custom Field", "MyField", eDataType.String, true, true, true); } //Simple example showing how you can intercept the display and add a custom field public override DataTable GetDataSource() { var dt = base.GetDataSource(); dt.Columns.Add("MyField", typeof(string)); foreach (DataRow row in dt.Rows) { row["MyField"] = row["Title"]+"-"+row["Date"].ToString(); } return dt; } } Tool Control ToolControl<T>:ToolListBase (usercontrol) Properties & Methods Overview A user-control that binds the GFUI interface to the BusinessObject<T>. Developers use the following methods: protected override void LoadDataFromObject(ActionArgs e) -> Allows for loading data from the BusinessObject<T> to the GUI protected override void SaveDataToObject() -> Allows for saving from the GUI to the BusinessObject<T> Example Blog article with categories and tags BlogArticle.ascx<%@ Control Language="C#" AutoEventWireup="true" CodeFile="BlogArticle.ascx.cs" Inherits="AdvantageClient.ClientAdmin_BlogArticle" %> <%@ Register TagPrefix="Advantage" Namespace="AdvantageCMS.Web.UI" Assembly="AdvantageCMS.Web.UI" %> <asp:PlaceHolder runat="server" ID="phTop"> <style> input[type="text"].four-row { height: 200px !important; } </style> </asp:PlaceHolder> <!-- Tab strip UI --> <telerik:RadTabStrip runat="server" ID="tabConfig" SelectedIndex="0" AutoPostBack="false" MultiPageID="multiPage1"> <Tabs> <telerik:RadTab Text="Properties" Selected="true" PageViewID="pvProperties"></telerik:RadTab> <telerik:RadTab Text="Taxonomy" PageViewID="pvTaxonomy"></telerik:RadTab> <telerik:RadTab Text="Content" PageViewID="pvContent"></telerik:RadTab> </Tabs> </telerik:RadTabStrip> <telerik:RadMultiPage ID="multiPage1" runat="server" SelectedIndex="0"> <!--Article Properties--> <telerik:RadPageView runat="server" ID="pvProperties"> <fieldset> <legend>Article Information</legend> <div class="form-row"> <label>Title</label> <telerik:RadTextBox runat="server" ID="txtTitle"></telerik:RadTextBox> </div> <div class="form-row"> <label>SEO Name</label> <telerik:RadTextBox runat="server" ID="txtSEO"></telerik:RadTextBox> </div> <div class="form-row"> <label>Date</label> <telerik:RadDatePicker ID="dtDate" runat="server"></telerik:RadDatePicker> </div> <div class="form-row"> <label>Summary</label> <telerik:RadTextBox runat="server" ID="txtSummary" Rows="4" CssClass="four-row" TextMode="Multiline" MaxLength="500"></telerik:RadTextBox> </div> <div class="form-row"> <label>Published By</label> <telerik:RadTextBox runat="server" ID="txtPublishedBy"></telerik:RadTextBox> </div> <div class="form-row"> <label>Is Featured</label> <asp:CheckBox runat="server" ID="chkFeaturedPage" /> </div> <div class="form-row"> <label>Article Image</label> <advantage:AdvantageSelectorImage runat="server" ID="imgArticle" /> </div> </fieldset> </telerik:RadPageView> <!--Taxonomy (Categories, Tags)--> <telerik:RadPageView runat="server" ID="pvTaxonomy"> <fieldset> <legend>Taxonomy</legend> <div class="form-row"> <label>Categories</label> <asp:CheckBoxList runat="server" RepeatColumns="4" ID="chkLstCategories" /> </div> <div class="form-row"> <label>Tags</label> <telerik:RadAutoCompleteBox RenderMode="Lightweight" runat="server" ID="txtTagList" AllowCustomEntry="True" Filter="Contains" InputType="Token" EnableClientFiltering="True" AutoPostBack="false" Width="72%"> <TokensSettings AllowTokenEditing="False" /> </telerik:RadAutoCompleteBox> </div> </fieldset> </telerik:RadPageView> <!--Content--> <telerik:RadPageView runat="server" ID="pvContent"> <advantage:AdvantageEditor ID="editorContent" runat="server" Title="Content"></advantage:AdvantageEditor> </telerik:RadPageView> </telerik:RadMultiPage> <asp:PlaceHolder runat="server" ID="phBottom"></asp:PlaceHolder> BlogArticle.ascx.csusing System; using System.Collections.Generic; using System.Linq; using AdvantageCMS.Core.Admin.BaseClasses; using AdvantageCMS.Core.Admin.Event; using AdvantageCMS.Core.Common; using AdvantageCMS.Core.Common.BaseClasses; using AdvantageCMS.Core.Common.Engine; using AdvantageCMS.Core.Common.Extension; using Telerik.Web.UI; namespace AdvantageClient { public partial class ClientAdmin_BlogArticle : ToolControl<BlogArticle> { protected override void OnInit(EventArgs e) { base.OnInit(e); //Register the script to top of page RegisterTopScript(phTop); //Register the script to bottom of page RegisterBottomScript(phBottom); //Load the categories to checklist control if (chkLstCategories.Items.Count == 0) { chkLstCategories.DataSource = ModuleEngine.GetAllPublishedObjects<BlogCategory>().OrderBy(cat => cat.Name); chkLstCategories.DataTextField = "Name"; chkLstCategories.DataValueField = "MasterID"; chkLstCategories.DataBind(); } } //Save data to object (called when save/publish button is clicked) protected override void SaveDataToObject() { if (string.IsNullOrEmpty(txtSEO.Text)) txtSEO.Text = txtTitle.Text; MyObject.SEOName = txtSEO.Text.UrlFriendly(); MyObject.Title = txtTitle.Text; MyObject.Image = imgArticle.GetAdvantageImage(); MyObject.PostedDate = (dtDate?.SelectedDate ?? DateTime.Now); MyObject.Content = editorContent.Content; MyObject.Summary = txtSummary.Text; MyObject.Author = txtPublishedBy.Text; MyObject.IsFeatured = chkFeaturedPage.Checked; List<Guid> categories = new List<Guid>(); foreach (var item in chkLstCategories.GetCheckedItems()) categories.Add(item); MyObject.Categories = categories; List<string> tags = new List<string>(); foreach (AutoCompleteBoxEntry entry in txtTagList.Entries) tags.Add(entry.Text); MyObject.Tags = tags; MyObject.Content = editorContent.Content; } //Load data from object (called when add/edit button is clicked) protected override void LoadDataFromObject(ActionArgs e) { txtTitle.Text = MyObject.Title; editorContent.Content = MyObject.Content; txtSummary.Text = MyObject.Summary; txtPublishedBy.Text = MyObject.Author; txtSEO.Text = MyObject.SEOName; imgArticle.SetAdvantageImage(MyObject.Image); LoadTags(MyObject.Tags); if (MyObject.Categories != null) chkLstCategories.SetCheckedItems(MyObject.Categories); chkFeaturedPage.Checked = MyObject.IsFeatured; dtDate.SelectedDate = MyObject.PostedDate; tabConfig.SelectedIndex = 0; } //Get all tags and load to autocompletebox private void LoadTags(List<string> tagList) { txtTagList.Entries.Clear(); List<string> tags = ModuleEngine.GetPublishedKeyValueObjects<BlogArticle, string>("Tag"); if (tagList != null) { foreach (string s in tagList) { tags.Add(s); AutoCompleteBoxEntry e = new AutoCompleteBoxEntry(s, s); txtTagList.Entries.Add(e); } } txtTagList.DataSource = tags.Distinct(); txtTagList.DataBind(); } } }