From ce4828d67897bc4418698aa49637502b2cad0217 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 11 Oct 2012 02:30:48 +0500 Subject: [PATCH] Fixes HasContent if there is actually no content (new install). Fixes issue with TreeDefinitionCollection not initializing when accessing singleton... not sure how i overlooked that before. This fixes issues with MNTP initialization and probably some other tree stuff. Removes warning log in umbracomodule for not a document as this just takes up space in the log. Fixes issue with logger that was logging 'Info' for our internal Debug calls. --- src/Umbraco.Core/DisposableTimer.cs | 2 +- .../LegacyTransientObjectsResolver.cs | 2 +- .../DefaultPublishedContentStore.cs | 5 +- src/Umbraco.Web/UmbracoModule.cs | 9 +- .../umbraco/Trees/TreeDefinitionCollection.cs | 1 + .../MultiNodeTreePicker/MNTP_DataEditor.cs | 1338 ++++++++--------- 6 files changed, 675 insertions(+), 682 deletions(-) diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index 5269244609..f60c3a5634 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -87,7 +87,7 @@ namespace Umbraco.Core /// public static DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage) { - LogHelper.Info(loggerType, () => startMessage); + LogHelper.Debug(loggerType, () => startMessage); return new DisposableTimer(x => LogHelper.Debug(loggerType, () => completeMessage + " (took " + x + "ms)")); } diff --git a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs index 582ec50b9e..27f2307bd2 100644 --- a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.ObjectResolution /// TODO: However, it would make much more sense to do this and would speed up the application plus this would make the GetById method much easier. /// protected LegacyTransientObjectsResolver(IEnumerable refreshers) - : base(ObjectLifetimeScope.Transient) // false = new objects every time + : base(ObjectLifetimeScope.Transient) // new objects every time { foreach (var l in refreshers) { diff --git a/src/Umbraco.Web/DefaultPublishedContentStore.cs b/src/Umbraco.Web/DefaultPublishedContentStore.cs index c1d79283a7..1b8dfddc7c 100644 --- a/src/Umbraco.Web/DefaultPublishedContentStore.cs +++ b/src/Umbraco.Web/DefaultPublishedContentStore.cs @@ -161,7 +161,10 @@ namespace Umbraco.Web public bool HasContent(UmbracoContext umbracoContext) { - var node = GetXml(umbracoContext).SelectSingleNode(XPathStrings.RootDocuments); + var xml = GetXml(umbracoContext); + if (xml == null) + return false; + var node = xml.SelectSingleNode(XPathStrings.RootDocuments); return node != null; } diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 5f84c387f6..8368563990 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -271,10 +271,11 @@ namespace Umbraco.Web if (maybeDoc && GlobalSettings.IsReservedPathOrUrl(lpath)) maybeDoc = false; - if (!maybeDoc) - { - LogHelper.Warn("Not a document"); - } + //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) + //if (!maybeDoc) + //{ + // LogHelper.Warn("Not a document"); + //} return maybeDoc; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs index e8aca83c1a..5d816ce07f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs @@ -33,6 +33,7 @@ namespace umbraco.cms.presentation.Trees { get { + instance.EnsureTreesRegistered(); return instance; } } diff --git a/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs b/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs index a36b93e5f9..6e0a04320b 100644 --- a/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs +++ b/src/umbraco.editorControls/MultiNodeTreePicker/MNTP_DataEditor.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Xml.Linq; using ClientDependency.Core; +using Umbraco.Core; using umbraco.cms.presentation.Trees; using umbraco.controls.Images; using umbraco.controls.Tree; @@ -17,679 +19,665 @@ using umbraco.IO; namespace umbraco.editorControls.MultiNodeTreePicker { - /// - /// The user interface to display to the content editor - /// - [ClientDependency(ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.tooltip.min.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "controls/Images/ImageViewer.js", "UmbracoRoot")] - public class MNTP_DataEditor : Control, INamingContainer - { - #region Static Constructor - - /// - /// This adds our filtered tree definition to the TreeDefinitionCollection at runtime - /// instead of having to declare it in the database - /// - static MNTP_DataEditor() - { - if (TreeDefinitionCollection.Instance - .Where(x => x.TreeType == typeof(FilteredContentTree)) - .Count() == 0) - { - lock (m_Locker) - { - //double check lock.... - - if (TreeDefinitionCollection.Instance - .Where(x => x.TreeType == typeof(FilteredContentTree)) - .Count() == 0) - { - //need to add our tree definitions to the collection. - - //find the content tree to duplicate - var contentTree = TreeDefinitionCollection.Instance.Where(x => x.Tree.Alias.ToUpper() == "CONTENT").Single(); - var filteredContentTree = new TreeDefinition(typeof(FilteredContentTree), - new umbraco.BusinessLogic.ApplicationTree(true, false, 0, - contentTree.Tree.ApplicationAlias, - "FilteredContentTree", - contentTree.Tree.Title, - contentTree.Tree.IconClosed, - contentTree.Tree.IconOpened, - "umbraco.editorControls", - "MultiNodeTreePicker.FilteredContentTree", - contentTree.Tree.Action), - contentTree.App); - - //find the media tree to duplicate - var mediaTree = TreeDefinitionCollection.Instance.Where(x => x.Tree.Alias.ToUpper() == "MEDIA").Single(); - var filteredMediaTree = new TreeDefinition(typeof(FilteredMediaTree), - new umbraco.BusinessLogic.ApplicationTree(true, false, 0, - mediaTree.Tree.ApplicationAlias, - "FilteredMediaTree", - contentTree.Tree.Title, - contentTree.Tree.IconClosed, - contentTree.Tree.IconOpened, - "umbraco.editorControls", - "MultiNodeTreePicker.FilteredMediaTree", - contentTree.Tree.Action), - contentTree.App); - - //add it to the collection at runtime - TreeDefinitionCollection.Instance.Add(filteredContentTree); - TreeDefinitionCollection.Instance.Add(filteredMediaTree); - } - } - } - } - - #endregion - - /// - /// Initializes a new instance of the class. - /// - public MNTP_DataEditor() - { - this.MediaTypesWithThumbnails = new string[] { "image" }; - ShowThumbnailsForMedia = true; - TreeToRender = "content"; - MaxNodeCount = -1; - MinNodeCount = 0; - StartNodeId = uQuery.RootNodeId; - ShowToolTips = true; - ControlHeight = 200; - } - - #region Static members - /// - /// Used for locking code blocks - /// - private static readonly object m_Locker = new object(); - #endregion - - #region Protected members - - /// - /// - /// - protected CustomValidator MinItemsValidator; - - /// - /// - /// - protected CustomTreeControl TreePickerControl; - - /// - /// - /// - protected Repeater SelectedValues; - - /// - /// - /// - protected HiddenField PickedValue; - - /// - /// - /// - protected HtmlGenericControl RightColumn; - #endregion - - #region public Properties - - /// - /// gets/sets the value based on an array of IDs selected - /// - public string[] SelectedIds - { - get - { - List val = new List(); - var splitVals = PickedValue.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - //this will make sure only the node count specified is saved - //into umbraco, even if people try to hack the front end for whatever reason. - if (MaxNodeCount >= 0) - { - for (var i = 0; i < splitVals.Length; i++) - { - if (i < MaxNodeCount) - { - val.Add(splitVals[i]); - } - else break; - } - } - else - { - val = splitVals.ToList(); - } - return val.ToArray(); - } - set - { - XmlValue = ConvertToXDocument(value); - } - } - - /// - /// get/set the value for the selected nodes in xml format - /// - public XDocument XmlValue - { - get - { - return ConvertToXDocument(SelectedIds); - } - set - { - if (value == null) - { - SelectedValues.DataSource = null; - PickedValue.Value = ""; - } - else - { - //set the data source for the repeater and hidden field - var nodes = value.Descendants("nodeId"); - SelectedValues.DataSource = nodes; - PickedValue.Value = string.Join(",", nodes.Select(x => x.Value).ToArray()); - } - } - } - - /// - /// The property name being edited with the current data editor. This is used for the min items validation statement. - /// - public string PropertyName { get; set; } - - /// - /// The tree type alias to render - /// - public string TreeToRender { get; set; } - - /// - /// An xpath filter to match nodes that will be disabled from being clicked - /// - public string XPathFilter { get; set; } - - /// - /// The minimum amount of nodes that can be selected - /// - public int MinNodeCount { get; set; } - - /// - /// The maximum amount of nodes that can be selected - /// - public int MaxNodeCount { get; set; } - - /// - /// The start node id - /// - public int StartNodeId { get; set; } - - /// - /// The start node selection type - /// - public NodeSelectionType StartNodeSelectionType { get; set; } - - /// - /// The xpath expression type to select the start node when the StartNodeSelectionType is XPath - /// - public XPathExpressionType StartNodeXPathExpressionType { get; set; } - - /// - /// The XPath expression to use to determine the start node when the StartNodeSelectionType is XPath - /// - public string StartNodeXPathExpression { get; set; } - - /// - /// Gets or sets a value indicating whether [show tool tips]. - /// - /// true if [show tool tips]; otherwise, false. - /// Shows/Hides the tooltip info bubble. - public bool ShowToolTips { get; set; } - - /// - /// The XPathFilterType to match - /// - public XPathFilterType XPathFilterMatchType { get; set; } - - /// - /// Gets or sets a value indicating whether [show thumbnails for media]. - /// - /// - /// true if [show thumbnails for media]; otherwise, false. - /// - /// Whether or not to show thumbnails for media - public bool ShowThumbnailsForMedia { get; set; } - - /// - /// A list of media type names that can have thumbnails (i.e. 'image') - /// - public string[] MediaTypesWithThumbnails { get; set; } - - /// - /// This is set by the data type and allows us to save a cookie value - /// for persistence for the data type. - /// - public int DataTypeDefinitionId { get; set; } - - /// - /// The height of the tree control box in pixels - /// - public int ControlHeight { get; set; } - - #endregion - - /// - /// Initialize the control, make sure children are created - /// - /// An object that contains the event data. - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - EnsureChildControls(); - } - - /// - /// Add the resources (sytles/scripts) - /// - /// - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - //add the js/css required - this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerStyles.css", umbraco.cms.businesslogic.datatype.ClientDependencyType.Css); - this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerScripts.js", umbraco.cms.businesslogic.datatype.ClientDependencyType.Javascript); - - //update the tree type (we need to do this each time because i don't think view state works with these controls) - switch (TreeToRender) - { - case "media": - TreePickerControl.TreeType = "FilteredMediaTree"; - TreePickerControl.App = "media"; - break; - case "content": - default: - TreePickerControl.TreeType = "FilteredContentTree"; - TreePickerControl.App = "content"; - break; - } - - if (Page.IsPostBack) - { - //since it is a post back, bind the data source to the view state values - XmlValue = ConvertToXDocument(SelectedIds); - } - - //bind the repeater if theres a data source, or if there's no datasource but this is a postback (i.e. nodes deleted) - if (SelectedValues.DataSource != null || Page.IsPostBack) - { - SelectedValues.DataBind(); - } - - } - - /// - /// Creates the child controls for this control - /// - protected override void CreateChildControls() - { - base.CreateChildControls(); - - EnsureChildControls(); - - //create the tree control - TreePickerControl = new CustomTreeControl - { - ID = "TreePicker", - IsDialog = true, - ShowContextMenu = false, - DialogMode = TreeDialogModes.id, - Height = Unit.Pixel(ControlHeight), - StartNodeID = StartNodeId - }; - - //create the hidden field - PickedValue = new HiddenField { ID = "PickedValue" }; - - //create the right column - RightColumn = new HtmlGenericControl("div") { ID = "RightColumn" }; - RightColumn.Attributes.Add("class", "right propertypane"); - - //create the repeater - SelectedValues = new Repeater - { - //EnableViewState = false, - ID = "SelectedValues", - ItemTemplate = new SelectedItemsTemplate() - }; - - SelectedValues.ItemDataBound += SelectedValues_ItemDataBound; - - //add the repeater to the right column - RightColumn.Controls.Add(SelectedValues); - - MinItemsValidator = new CustomValidator() - { - ID = "MinItemsValidator", - ErrorMessage = - string.Format(MNTPResources.Val_MinItemsInvalid, MinNodeCount) - }; - MinItemsValidator.ServerValidate += new ServerValidateEventHandler(MinItemsValidator_ServerValidate); - - //add the controls - this.Controls.Add(MinItemsValidator); - this.Controls.Add(TreePickerControl); - this.Controls.Add(PickedValue); - this.Controls.Add(RightColumn); - } - - - - /// - /// Ensure the repeater is data bound - /// - public override void DataBind() - { - base.DataBind(); - SelectedValues.DataBind(); - } - - void MinItemsValidator_ServerValidate(object source, ServerValidateEventArgs args) - { - args.IsValid = true; - if (MinNodeCount > 0 && SelectedIds.Length < MinNodeCount) - { - args.IsValid = false; - } - } - - /// - /// Event handler for the selected node repeater. - /// This will fill in all of the text values, icons, etc.. for nodes based on their ID. - /// - /// - /// - void SelectedValues_ItemDataBound(object sender, RepeaterItemEventArgs e) - { - var liSelectNode = (HtmlGenericControl)e.Item.FindControl("SelectedNodeListItem"); - var lnkSelectNode = (HtmlAnchor)e.Item.FindControl("SelectedNodeLink"); - var litSelectNodeName = (Literal)e.Item.FindControl("SelectedNodeText"); - var infoButton = (HtmlAnchor)e.Item.FindControl("InfoButton"); - - //hide the info button if tooltips are hidden - if (!ShowToolTips) - { - infoButton.Style.Add(HtmlTextWriterStyle.Display, "none"); - } - - var thisNode = (XElement)e.Item.DataItem; - int thisNodeId; - if (int.TryParse(thisNode.Value, out thisNodeId)) - { - umbraco.cms.businesslogic.Content loadedNode; - - try - { - loadedNode = new umbraco.cms.businesslogic.Content(thisNodeId); - - //add the node id - liSelectNode.Attributes["rel"] = thisNodeId.ToString(); - //add the path to be referenced - liSelectNode.Attributes["umb:nodedata"] = loadedNode.Path; - lnkSelectNode.HRef = "javascript:void(0);"; - litSelectNodeName.Text = loadedNode.Text; - - if (loadedNode.IsTrashed) - { - //need to flag this to be removed which will be done after all items are data bound - liSelectNode.Attributes["rel"] = "trashed"; - } - else - { - //we need to set the icon - if (loadedNode.ContentTypeIcon.StartsWith(".spr")) - lnkSelectNode.Attributes["class"] += " " + loadedNode.ContentTypeIcon.TrimStart('.'); - else - { - //it's a real icon, so make it a background image - lnkSelectNode.Style.Add(HtmlTextWriterStyle.BackgroundImage, - string.Format("url('{0}')", IconPath + loadedNode.ContentTypeIcon)); - //set the nospr class since it's not a sprite - lnkSelectNode.Attributes["class"] += " noSpr"; - } - - //show the media preview if media and allowed - if (TreeToRender == "media" && ShowThumbnailsForMedia) - { - var imgPreview = (ImageViewer)e.Item.FindControl("ImgPreview"); - //show the thubmnail controls - imgPreview.Visible = true; - - //add the item class - var item = (HtmlGenericControl)e.Item.FindControl("Item"); - item.Attributes["class"] += " thumb-item"; - - //item.Style.Add(HtmlTextWriterStyle.Height, "50px"); - ////make the content sit beside the item - //var inner = (HtmlGenericControl)e.Item.FindControl("InnerItem"); - //inner.Style.Add(HtmlTextWriterStyle.Width, "224px"); - - //check if it's a thumbnail type element, we need to check both schemas - if (MediaTypesWithThumbnails.Select(x => x.ToUpper()) - .Contains(loadedNode.ContentType.Alias.ToUpper())) - { - imgPreview.MediaId = thisNodeId; - imgPreview.DataBind(); - } - } - } - - } - catch (ArgumentException) - { - //the node no longer exists, so we display a msg - litSelectNodeName.Text = "NODE NO LONGER EXISTS"; - } - } - } - - /// - /// set the nodekey to the id of this datatype - /// - /// - /// this is how get the xpath out of the cookie to know how the tree knows how to filter things. - /// generally the nodekey is used for a string id, but we'll use it for something different. - /// - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - TreePickerControl.NodeKey = this.DataTypeDefinitionId.ToString(); - - SavePersistentValuesForTree(XPathFilter); - } - - /// - /// Override render to control the exact output of what is rendered this includes instantiating the jquery plugin - /// - /// The object that receives the server control content. - /// - /// Generally i don't like to do this but there's a few div's, etc... to render so this makes more sense. - /// - protected override void Render(HtmlTextWriter writer) - { - //
- //
- //
Select items
- //
- //
- // - //
- //
- //
- //
- - RenderTooltip(writer); - - writer.AddAttribute("class", (!MinItemsValidator.IsValid ? "error " : "") + "multiNodePicker clearfix"); - writer.AddAttribute("id", this.ClientID); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - - writer.AddAttribute("class", "header propertypane"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.Write("Select Items"); - writer.RenderEndTag(); - writer.RenderEndTag(); - - writer.AddAttribute("class", "left propertypane"); - writer.AddStyleAttribute(HtmlTextWriterStyle.Height, ((ControlHeight + 10).ToString() + "px")); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - //add the tree control here - TreePickerControl.RenderControl(writer); - writer.RenderEndTag(); - - RightColumn.RenderControl(writer); - - //render the hidden field - PickedValue.RenderControl(writer); - - writer.RenderEndTag(); //end multiNodePicker div - - var tooltipAjaxUrl = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + @"/controls/Tree/CustomTreeService.asmx/GetNodeInfo"; - - //add jquery window load event to create the js tree picker - var jsMethod = string.Format("jQuery('#{0}').MultiNodeTreePicker('{1}', {2}, '{3}', {4}, {5}, '{6}', '{7}');", - TreePickerControl.ClientID, - this.ClientID, - MaxNodeCount, - tooltipAjaxUrl, - ShowToolTips.ToString().ToLower(), - (TreeToRender == "media" && ShowThumbnailsForMedia).ToString().ToLower(), - IOHelper.ResolveUrl(SystemDirectories.Umbraco), - TreeToRender); - var js = "jQuery(window).load(function() { " + jsMethod + " });"; - - writer.WriteLine(""); - - } - - /// - /// converts a list of Ids to the XDocument structure - /// - /// The value. - /// - private XDocument ConvertToXDocument(IEnumerable val) - { - if (val.Count() > 0) - { - return new XDocument(new XElement("MultiNodePicker", - new XAttribute("type", TreeToRender), - val.Select(x => new XElement("nodeId", x.ToString())))); - } - else - { - //return null to support recursive values - return null; - - //return an empty node set - //return new XDocument(new XElement("MultiNodePicker")); - } - } - - /// - /// this will render the tooltip object on the page so long as another - /// one hasn't already been registered. There should only be one tooltip. - /// - private void RenderTooltip(HtmlTextWriter writer) - { - if (this.Page.Items.Contains("MNTPTooltip")) - { - return; - } - - //render the tooltip holder - //
- //
- //
- //
- //this.Page.Controls.AddAt(0, new LiteralControl("
")); - writer.AddAttribute("id", "MNTPTooltip"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.AddAttribute("class", "throbber"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderEndTag(); //end throbber - writer.AddAttribute("class", "tooltipInfo"); - writer.RenderBeginTag(HtmlTextWriterTag.Div); - writer.RenderEndTag(); //end tooltipInfo - writer.RenderEndTag(); //end tooltipo - - //ensure we add this to our page items so it's not duplicated - this.Page.Items.Add("MNTPTooltip", true); - } - - /// - /// This will update the multi-node tree picker data which is used to store - /// the xpath data and xpath match type for this control id. - /// - /// The xpath. - /// - /// This will save the data into a cookie and also into the request cookie. It must save - /// it to both locations in case the request cookie has been changed and the request cookie - /// is different than the response cookie. - /// - private void SavePersistentValuesForTree(string xpath) - { - - //create the output cookie with all of the values of the request cookie - - var newCookie = HttpContext.Current.Response.Cookies[MNTP_DataType.PersistenceCookieName] ?? new HttpCookie(MNTP_DataType.PersistenceCookieName); - - //store the xpath for this data type definition - newCookie.MntpAddXPathFilter(this.DataTypeDefinitionId, xpath); - //store the match type - newCookie.MntpAddXPathFilterType(this.DataTypeDefinitionId, XPathFilterMatchType); - //store the start node id - newCookie.MntpAddStartNodeId(this.DataTypeDefinitionId, StartNodeId); - //store the start node selection type - newCookie.MntpAddStartNodeSelectionType(this.DataTypeDefinitionId, StartNodeSelectionType); - //store the start node xpath expression type - newCookie.MntpAddStartNodeXPathExpressionType(this.DataTypeDefinitionId, StartNodeXPathExpressionType); - //store the start node xpath expression - newCookie.MntpAddStartNodeXPathExpression(this.DataTypeDefinitionId, StartNodeXPathExpression); - //store the current editing node if found - if (!string.IsNullOrEmpty(HttpContext.Current.Request["id"])) - { - var id = 0; - if (int.TryParse(HttpContext.Current.Request["id"], out id)) - { - newCookie.MntpAddCurrentEditingNode(this.DataTypeDefinitionId, id); - } - } - - HttpContext.Current.Response.Cookies.Add(newCookie); - - //add it to the request cookies too, thus overriding any old data - if (HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName] != null && HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName].Values.Count > 0) - { - //remove the incoming one and replace with new one - HttpContext.Current.Request.Cookies.Remove(MNTP_DataType.PersistenceCookieName); - } - HttpContext.Current.Request.Cookies.Add(newCookie); - - } - - /// - /// A reference path to where the icons are actually stored as compared to where the tree themes folder is - /// - private static readonly string IconPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/images/umbraco/"; - } + /// + /// The user interface to display to the content editor + /// + [ClientDependency(ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "ui/jquery.tooltip.min.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "controls/Images/ImageViewer.js", "UmbracoRoot")] + public class MNTP_DataEditor : Control, INamingContainer + { + #region Static Constructor + + /// + /// This adds our filtered tree definition to the TreeDefinitionCollection at runtime + /// instead of having to declare it in the database + /// + static MNTP_DataEditor() + { + //NOTE: Before we had locking here but static ctors are always threadsafe + + if (TreeDefinitionCollection.Instance.Any(x => x.TreeType == typeof (FilteredContentTree))) + return; + + + //need to add our tree definitions to the collection. + + //find the content tree to duplicate + var contentTree = TreeDefinitionCollection.Instance.Single(x => x.Tree.Alias.ToUpper() == "CONTENT"); + var filteredContentTree = new TreeDefinition(typeof(FilteredContentTree), + new umbraco.BusinessLogic.ApplicationTree(true, false, 0, + contentTree.Tree.ApplicationAlias, + "FilteredContentTree", + contentTree.Tree.Title, + contentTree.Tree.IconClosed, + contentTree.Tree.IconOpened, + "umbraco.editorControls", + "MultiNodeTreePicker.FilteredContentTree", + contentTree.Tree.Action), + contentTree.App); + + //find the media tree to duplicate + var mediaTree = TreeDefinitionCollection.Instance.Single(x => x.Tree.Alias.ToUpper() == "MEDIA"); + var filteredMediaTree = new TreeDefinition(typeof(FilteredMediaTree), + new umbraco.BusinessLogic.ApplicationTree(true, false, 0, + mediaTree.Tree.ApplicationAlias, + "FilteredMediaTree", + contentTree.Tree.Title, + contentTree.Tree.IconClosed, + contentTree.Tree.IconOpened, + "umbraco.editorControls", + "MultiNodeTreePicker.FilteredMediaTree", + contentTree.Tree.Action), + contentTree.App); + + //add it to the collection at runtime + TreeDefinitionCollection.Instance.Add(filteredContentTree); + TreeDefinitionCollection.Instance.Add(filteredMediaTree); + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public MNTP_DataEditor() + { + this.MediaTypesWithThumbnails = new string[] { "image" }; + ShowThumbnailsForMedia = true; + TreeToRender = "content"; + MaxNodeCount = -1; + MinNodeCount = 0; + StartNodeId = uQuery.RootNodeId; + ShowToolTips = true; + ControlHeight = 200; + } + + + + #region Protected members + + /// + /// + /// + protected CustomValidator MinItemsValidator; + + /// + /// + /// + protected CustomTreeControl TreePickerControl; + + /// + /// + /// + protected Repeater SelectedValues; + + /// + /// + /// + protected HiddenField PickedValue; + + /// + /// + /// + protected HtmlGenericControl RightColumn; + #endregion + + #region public Properties + + /// + /// gets/sets the value based on an array of IDs selected + /// + public string[] SelectedIds + { + get + { + List val = new List(); + var splitVals = PickedValue.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + //this will make sure only the node count specified is saved + //into umbraco, even if people try to hack the front end for whatever reason. + if (MaxNodeCount >= 0) + { + for (var i = 0; i < splitVals.Length; i++) + { + if (i < MaxNodeCount) + { + val.Add(splitVals[i]); + } + else break; + } + } + else + { + val = splitVals.ToList(); + } + return val.ToArray(); + } + set + { + XmlValue = ConvertToXDocument(value); + } + } + + /// + /// get/set the value for the selected nodes in xml format + /// + public XDocument XmlValue + { + get + { + return ConvertToXDocument(SelectedIds); + } + set + { + if (value == null) + { + SelectedValues.DataSource = null; + PickedValue.Value = ""; + } + else + { + //set the data source for the repeater and hidden field + var nodes = value.Descendants("nodeId"); + SelectedValues.DataSource = nodes; + PickedValue.Value = string.Join(",", nodes.Select(x => x.Value).ToArray()); + } + } + } + + /// + /// The property name being edited with the current data editor. This is used for the min items validation statement. + /// + public string PropertyName { get; set; } + + /// + /// The tree type alias to render + /// + public string TreeToRender { get; set; } + + /// + /// An xpath filter to match nodes that will be disabled from being clicked + /// + public string XPathFilter { get; set; } + + /// + /// The minimum amount of nodes that can be selected + /// + public int MinNodeCount { get; set; } + + /// + /// The maximum amount of nodes that can be selected + /// + public int MaxNodeCount { get; set; } + + /// + /// The start node id + /// + public int StartNodeId { get; set; } + + /// + /// The start node selection type + /// + public NodeSelectionType StartNodeSelectionType { get; set; } + + /// + /// The xpath expression type to select the start node when the StartNodeSelectionType is XPath + /// + public XPathExpressionType StartNodeXPathExpressionType { get; set; } + + /// + /// The XPath expression to use to determine the start node when the StartNodeSelectionType is XPath + /// + public string StartNodeXPathExpression { get; set; } + + /// + /// Gets or sets a value indicating whether [show tool tips]. + /// + /// true if [show tool tips]; otherwise, false. + /// Shows/Hides the tooltip info bubble. + public bool ShowToolTips { get; set; } + + /// + /// The XPathFilterType to match + /// + public XPathFilterType XPathFilterMatchType { get; set; } + + /// + /// Gets or sets a value indicating whether [show thumbnails for media]. + /// + /// + /// true if [show thumbnails for media]; otherwise, false. + /// + /// Whether or not to show thumbnails for media + public bool ShowThumbnailsForMedia { get; set; } + + /// + /// A list of media type names that can have thumbnails (i.e. 'image') + /// + public string[] MediaTypesWithThumbnails { get; set; } + + /// + /// This is set by the data type and allows us to save a cookie value + /// for persistence for the data type. + /// + public int DataTypeDefinitionId { get; set; } + + /// + /// The height of the tree control box in pixels + /// + public int ControlHeight { get; set; } + + #endregion + + /// + /// Initialize the control, make sure children are created + /// + /// An object that contains the event data. + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + + EnsureChildControls(); + } + + /// + /// Add the resources (sytles/scripts) + /// + /// + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + //add the js/css required + this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerStyles.css", umbraco.cms.businesslogic.datatype.ClientDependencyType.Css); + this.RegisterEmbeddedClientResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerScripts.js", umbraco.cms.businesslogic.datatype.ClientDependencyType.Javascript); + + //update the tree type (we need to do this each time because i don't think view state works with these controls) + switch (TreeToRender) + { + case "media": + TreePickerControl.TreeType = "FilteredMediaTree"; + TreePickerControl.App = "media"; + break; + case "content": + default: + TreePickerControl.TreeType = "FilteredContentTree"; + TreePickerControl.App = "content"; + break; + } + + if (Page.IsPostBack) + { + //since it is a post back, bind the data source to the view state values + XmlValue = ConvertToXDocument(SelectedIds); + } + + //bind the repeater if theres a data source, or if there's no datasource but this is a postback (i.e. nodes deleted) + if (SelectedValues.DataSource != null || Page.IsPostBack) + { + SelectedValues.DataBind(); + } + + } + + /// + /// Creates the child controls for this control + /// + protected override void CreateChildControls() + { + base.CreateChildControls(); + + EnsureChildControls(); + + //create the tree control + TreePickerControl = new CustomTreeControl + { + ID = "TreePicker", + IsDialog = true, + ShowContextMenu = false, + DialogMode = TreeDialogModes.id, + Height = Unit.Pixel(ControlHeight), + StartNodeID = StartNodeId + }; + + //create the hidden field + PickedValue = new HiddenField { ID = "PickedValue" }; + + //create the right column + RightColumn = new HtmlGenericControl("div") { ID = "RightColumn" }; + RightColumn.Attributes.Add("class", "right propertypane"); + + //create the repeater + SelectedValues = new Repeater + { + //EnableViewState = false, + ID = "SelectedValues", + ItemTemplate = new SelectedItemsTemplate() + }; + + SelectedValues.ItemDataBound += SelectedValues_ItemDataBound; + + //add the repeater to the right column + RightColumn.Controls.Add(SelectedValues); + + MinItemsValidator = new CustomValidator() + { + ID = "MinItemsValidator", + ErrorMessage = + string.Format(MNTPResources.Val_MinItemsInvalid, MinNodeCount) + }; + MinItemsValidator.ServerValidate += new ServerValidateEventHandler(MinItemsValidator_ServerValidate); + + //add the controls + this.Controls.Add(MinItemsValidator); + this.Controls.Add(TreePickerControl); + this.Controls.Add(PickedValue); + this.Controls.Add(RightColumn); + } + + + + /// + /// Ensure the repeater is data bound + /// + public override void DataBind() + { + base.DataBind(); + SelectedValues.DataBind(); + } + + void MinItemsValidator_ServerValidate(object source, ServerValidateEventArgs args) + { + args.IsValid = true; + if (MinNodeCount > 0 && SelectedIds.Length < MinNodeCount) + { + args.IsValid = false; + } + } + + /// + /// Event handler for the selected node repeater. + /// This will fill in all of the text values, icons, etc.. for nodes based on their ID. + /// + /// + /// + void SelectedValues_ItemDataBound(object sender, RepeaterItemEventArgs e) + { + var liSelectNode = (HtmlGenericControl)e.Item.FindControl("SelectedNodeListItem"); + var lnkSelectNode = (HtmlAnchor)e.Item.FindControl("SelectedNodeLink"); + var litSelectNodeName = (Literal)e.Item.FindControl("SelectedNodeText"); + var infoButton = (HtmlAnchor)e.Item.FindControl("InfoButton"); + + //hide the info button if tooltips are hidden + if (!ShowToolTips) + { + infoButton.Style.Add(HtmlTextWriterStyle.Display, "none"); + } + + var thisNode = (XElement)e.Item.DataItem; + int thisNodeId; + if (int.TryParse(thisNode.Value, out thisNodeId)) + { + umbraco.cms.businesslogic.Content loadedNode; + + try + { + loadedNode = new umbraco.cms.businesslogic.Content(thisNodeId); + + //add the node id + liSelectNode.Attributes["rel"] = thisNodeId.ToString(); + //add the path to be referenced + liSelectNode.Attributes["umb:nodedata"] = loadedNode.Path; + lnkSelectNode.HRef = "javascript:void(0);"; + litSelectNodeName.Text = loadedNode.Text; + + if (loadedNode.IsTrashed) + { + //need to flag this to be removed which will be done after all items are data bound + liSelectNode.Attributes["rel"] = "trashed"; + } + else + { + //we need to set the icon + if (loadedNode.ContentTypeIcon.StartsWith(".spr")) + lnkSelectNode.Attributes["class"] += " " + loadedNode.ContentTypeIcon.TrimStart('.'); + else + { + //it's a real icon, so make it a background image + lnkSelectNode.Style.Add(HtmlTextWriterStyle.BackgroundImage, + string.Format("url('{0}')", IconPath + loadedNode.ContentTypeIcon)); + //set the nospr class since it's not a sprite + lnkSelectNode.Attributes["class"] += " noSpr"; + } + + //show the media preview if media and allowed + if (TreeToRender == "media" && ShowThumbnailsForMedia) + { + var imgPreview = (ImageViewer)e.Item.FindControl("ImgPreview"); + //show the thubmnail controls + imgPreview.Visible = true; + + //add the item class + var item = (HtmlGenericControl)e.Item.FindControl("Item"); + item.Attributes["class"] += " thumb-item"; + + //item.Style.Add(HtmlTextWriterStyle.Height, "50px"); + ////make the content sit beside the item + //var inner = (HtmlGenericControl)e.Item.FindControl("InnerItem"); + //inner.Style.Add(HtmlTextWriterStyle.Width, "224px"); + + //check if it's a thumbnail type element, we need to check both schemas + if (MediaTypesWithThumbnails.Select(x => x.ToUpper()) + .Contains(loadedNode.ContentType.Alias.ToUpper())) + { + imgPreview.MediaId = thisNodeId; + imgPreview.DataBind(); + } + } + } + + } + catch (ArgumentException) + { + //the node no longer exists, so we display a msg + litSelectNodeName.Text = "NODE NO LONGER EXISTS"; + } + } + } + + /// + /// set the nodekey to the id of this datatype + /// + /// + /// this is how get the xpath out of the cookie to know how the tree knows how to filter things. + /// generally the nodekey is used for a string id, but we'll use it for something different. + /// + /// + protected override void OnPreRender(EventArgs e) + { + base.OnPreRender(e); + + TreePickerControl.NodeKey = this.DataTypeDefinitionId.ToString(); + + SavePersistentValuesForTree(XPathFilter); + } + + /// + /// Override render to control the exact output of what is rendered this includes instantiating the jquery plugin + /// + /// The object that receives the server control content. + /// + /// Generally i don't like to do this but there's a few div's, etc... to render so this makes more sense. + /// + protected override void Render(HtmlTextWriter writer) + { + //
+ //
+ //
Select items
+ //
+ //
+ // + //
+ //
+ //
+ //
+ + RenderTooltip(writer); + + writer.AddAttribute("class", (!MinItemsValidator.IsValid ? "error " : "") + "multiNodePicker clearfix"); + writer.AddAttribute("id", this.ClientID); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + + writer.AddAttribute("class", "header propertypane"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.Write("Select Items"); + writer.RenderEndTag(); + writer.RenderEndTag(); + + writer.AddAttribute("class", "left propertypane"); + writer.AddStyleAttribute(HtmlTextWriterStyle.Height, ((ControlHeight + 10).ToString() + "px")); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + //add the tree control here + TreePickerControl.RenderControl(writer); + writer.RenderEndTag(); + + RightColumn.RenderControl(writer); + + //render the hidden field + PickedValue.RenderControl(writer); + + writer.RenderEndTag(); //end multiNodePicker div + + var tooltipAjaxUrl = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + @"/controls/Tree/CustomTreeService.asmx/GetNodeInfo"; + + //add jquery window load event to create the js tree picker + var jsMethod = string.Format("jQuery('#{0}').MultiNodeTreePicker('{1}', {2}, '{3}', {4}, {5}, '{6}', '{7}');", + TreePickerControl.ClientID, + this.ClientID, + MaxNodeCount, + tooltipAjaxUrl, + ShowToolTips.ToString().ToLower(), + (TreeToRender == "media" && ShowThumbnailsForMedia).ToString().ToLower(), + IOHelper.ResolveUrl(SystemDirectories.Umbraco), + TreeToRender); + var js = "jQuery(window).load(function() { " + jsMethod + " });"; + + writer.WriteLine(""); + + } + + /// + /// converts a list of Ids to the XDocument structure + /// + /// The value. + /// + private XDocument ConvertToXDocument(IEnumerable val) + { + if (val.Count() > 0) + { + return new XDocument(new XElement("MultiNodePicker", + new XAttribute("type", TreeToRender), + val.Select(x => new XElement("nodeId", x.ToString())))); + } + else + { + //return null to support recursive values + return null; + + //return an empty node set + //return new XDocument(new XElement("MultiNodePicker")); + } + } + + /// + /// this will render the tooltip object on the page so long as another + /// one hasn't already been registered. There should only be one tooltip. + /// + private void RenderTooltip(HtmlTextWriter writer) + { + if (this.Page.Items.Contains("MNTPTooltip")) + { + return; + } + + //render the tooltip holder + //
+ //
+ //
+ //
+ //this.Page.Controls.AddAt(0, new LiteralControl("
")); + writer.AddAttribute("id", "MNTPTooltip"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.AddAttribute("class", "throbber"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderEndTag(); //end throbber + writer.AddAttribute("class", "tooltipInfo"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderEndTag(); //end tooltipInfo + writer.RenderEndTag(); //end tooltipo + + //ensure we add this to our page items so it's not duplicated + this.Page.Items.Add("MNTPTooltip", true); + } + + /// + /// This will update the multi-node tree picker data which is used to store + /// the xpath data and xpath match type for this control id. + /// + /// The xpath. + /// + /// This will save the data into a cookie and also into the request cookie. It must save + /// it to both locations in case the request cookie has been changed and the request cookie + /// is different than the response cookie. + /// + private void SavePersistentValuesForTree(string xpath) + { + + //create the output cookie with all of the values of the request cookie + + var newCookie = HttpContext.Current.Response.Cookies[MNTP_DataType.PersistenceCookieName] ?? new HttpCookie(MNTP_DataType.PersistenceCookieName); + + //store the xpath for this data type definition + newCookie.MntpAddXPathFilter(this.DataTypeDefinitionId, xpath); + //store the match type + newCookie.MntpAddXPathFilterType(this.DataTypeDefinitionId, XPathFilterMatchType); + //store the start node id + newCookie.MntpAddStartNodeId(this.DataTypeDefinitionId, StartNodeId); + //store the start node selection type + newCookie.MntpAddStartNodeSelectionType(this.DataTypeDefinitionId, StartNodeSelectionType); + //store the start node xpath expression type + newCookie.MntpAddStartNodeXPathExpressionType(this.DataTypeDefinitionId, StartNodeXPathExpressionType); + //store the start node xpath expression + newCookie.MntpAddStartNodeXPathExpression(this.DataTypeDefinitionId, StartNodeXPathExpression); + //store the current editing node if found + if (!string.IsNullOrEmpty(HttpContext.Current.Request["id"])) + { + var id = 0; + if (int.TryParse(HttpContext.Current.Request["id"], out id)) + { + newCookie.MntpAddCurrentEditingNode(this.DataTypeDefinitionId, id); + } + } + + HttpContext.Current.Response.Cookies.Add(newCookie); + + //add it to the request cookies too, thus overriding any old data + if (HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName] != null && HttpContext.Current.Request.Cookies[MNTP_DataType.PersistenceCookieName].Values.Count > 0) + { + //remove the incoming one and replace with new one + HttpContext.Current.Request.Cookies.Remove(MNTP_DataType.PersistenceCookieName); + } + HttpContext.Current.Request.Cookies.Add(newCookie); + + } + + /// + /// A reference path to where the icons are actually stored as compared to where the tree themes folder is + /// + private static readonly string IconPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/images/umbraco/"; + } }