using System; using System.Collections; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using umbraco.cms.businesslogic.datatype; using umbraco.interfaces; using umbraco.uicontrols.TreePicker; namespace umbraco.editorControls.MultiNodeTreePicker { /// /// The pre-value editor for the multi node tree picker. /// public class MNTP_PrevalueEditor : Control, IDataPrevalue { private readonly umbraco.cms.businesslogic.datatype.BaseDataType m_DataType; private static readonly object Locker = new object(); private SortedList m_PreValues = null; #region Public properties /// /// The chosen tree type to render /// public string SelectedTreeType { get { return GetPreValue(PropertyIndex.TreeType, x => x.Value, "content"); } } /// /// An xpath filter to disable nodes to be selectable /// public string XPathFilter { get { return GetPreValue(PropertyIndex.XPathFilter, x => x.Value, string.Empty); } } /// /// The number of nodes this picker will support picking /// public int MaxNodeCount { get { return GetPreValue(PropertyIndex.MaxNodeCount, x => { var max = -1; return int.TryParse(x.Value, out max) ? max : -1; }, -1); } } /// /// The minimum number of nodes this picker will support picking /// public int MinNodeCount { get { return GetPreValue(PropertyIndex.MinNodeCount, x => { var min = 0; return int.TryParse(x.Value, out min) ? min : 0; }, 0); } } /// /// A boolean value indicating whether or not to show the informational tool tips /// public bool ShowToolTip { get { return GetPreValue(PropertyIndex.ShowToolTip, x => { var show = true; return bool.TryParse(x.Value, out show) ? show : true; }, true); } } /// /// Value to check if the data should be stored as CSV or XML /// public bool StoreAsCommaDelimited { get { return GetPreValue(PropertyIndex.StoreAsCommaDelimited, x => { var asComma = 0; return int.TryParse(x.Value, out asComma) ? asComma == 1 : false; }, false); } } /// /// The XPath expression used when the node type selection is Xpath /// public string StartNodeXPathExpression { get { return GetPreValue(PropertyIndex.StartNodeXPathExpression, x => x.Value, string.Empty); } } /// /// The type of xpath expression used for the xpathexpressiontext if using an xpath node selection /// public XPathExpressionType StartNodeXPathExpressionType { get { return GetPreValue(PropertyIndex.StartNodeXPathExpressionType, x => (XPathExpressionType)Enum.ToObject(typeof(XPathExpressionType), int.Parse(x.Value)), XPathExpressionType.Global); } } /// /// The type of selection type to use for the start node /// public NodeSelectionType StartNodeSelectionType { get { return GetPreValue(PropertyIndex.StartNodeSelectionType, x => (NodeSelectionType)Enum.ToObject(typeof(NodeSelectionType), int.Parse(x.Value)), NodeSelectionType.Picker); } } /// /// The type of xpath filter applied /// public XPathFilterType XPathFilterMatchType { get { return GetPreValue(PropertyIndex.XPathFilterType, x => (XPathFilterType)Enum.ToObject(typeof(XPathFilterType), int.Parse(x.Value)), XPathFilterType.Disable); } } /// /// The start node id used when the node selection is a picker /// public int StartNodeId { get { return GetPreValue(PropertyIndex.StartNodeId, x => { var max = 0; return int.TryParse(x.Value, out max) ? max : uQuery.RootNodeId; }, uQuery.RootNodeId); } } /// /// A boolean value indicating whether or not to show the thumbnails for media /// public bool ShowThumbnailsForMedia { get { return GetPreValue(PropertyIndex.ShowThumbnails, x => { var show = true; bool.TryParse(x.Value, out show); return show; }, true); } } /// /// Returns the control height in pixels /// public int ControlHeight { get { return GetPreValue(PropertyIndex.ControlHeight, x => { var max = 0; return int.TryParse(x.Value, out max) ? max : 200; }, 200); } } #endregion #region Protected properties /// /// The control height text box /// protected TextBox ControlHeightTextBox; /// /// /// protected RadioButtonList StartNodeSelectionTypeRadioButtons; /// /// The start node id content picker /// protected SimpleContentPicker StartContentNodeIdPicker; /// /// The start node id media picker /// protected SimpleMediaPicker StartMediaNodeIdPicker; /// /// XPath expression type radio button list /// protected RadioButtonList StartNodeXPathExpressionTypeRadioButtons; /// /// /// protected TextBox StartNodeXPathExpressionTextBox; /// /// /// protected CheckBox ShowThumbnailsForMediaCheckBox; /// /// /// protected DropDownList TreeTypeDropDown; /// /// /// protected TextBox XPathFilterTextBox; /// /// Text box for maximum amount of items /// protected TextBox MaxItemsTextBox; /// /// Text box for minimum amount of items /// protected TextBox MinItemsTextBox; /// /// Minimum items validator /// protected RegularExpressionValidator NumbersMinItemsValidator; /// /// Validator for validating relative xpath expressions /// protected CustomValidator RelativeXpathValidator; /// /// /// protected RegularExpressionValidator NumbersMaxItemsValidator; /// /// /// protected RegularExpressionValidator ControlHeightValidatator; /// /// /// protected CheckBox ShowItemInfoTooltipCheckBox; /// /// /// protected RadioButtonList StoreAsCommaDelimitedRadioButtons; /// /// /// protected RadioButtonList XPathFilterTypeRadioButtons; #endregion /// /// Initializes a new instance of the class. /// /// Type of the data. public MNTP_PrevalueEditor(umbraco.cms.businesslogic.datatype.BaseDataType dataType) { this.m_DataType = dataType; } /// /// Override on init to ensure child controls /// /// An object that contains the event data. protected override void OnInit(EventArgs e) { base.OnInit(e); this.EnsureChildControls(); this.RegisterEmbeddedClientResource("umbraco.editorControls.PrevalueEditor.css", ClientDependencyType.Css); } /// /// Ensures the css to render this control is included. /// Binds the saved value to the drop down. /// /// protected override void OnLoad(EventArgs e) { base.OnLoad(e); //add the css required //// this.AddCssMNTPClientDependencies(); //let view state handle the rest if (!Page.IsPostBack) { TreeTypeDropDown.SelectedValue = SelectedTreeType; XPathFilterTextBox.Text = XPathFilter; MaxItemsTextBox.Text = MaxNodeCount.ToString(); ShowItemInfoTooltipCheckBox.Checked = ShowToolTip; StoreAsCommaDelimitedRadioButtons.SelectedIndex = StoreAsCommaDelimited ? 1 : 0; XPathFilterTypeRadioButtons.SelectedIndex = XPathFilterMatchType == XPathFilterType.Disable ? 0 : 1; ShowThumbnailsForMediaCheckBox.Checked = ShowThumbnailsForMedia; StartNodeXPathExpressionTextBox.Text = StartNodeXPathExpression; StartNodeXPathExpressionTypeRadioButtons.SelectedIndex = StartNodeXPathExpressionType == XPathExpressionType.Global ? 0 : 1; StartNodeSelectionTypeRadioButtons.SelectedIndex = StartNodeSelectionType == NodeSelectionType.Picker ? 0 : 1; switch (SelectedTreeType.ToLower()) { case "content": StartContentNodeIdPicker.Value = StartNodeId.ToString(); break; case "media": default: StartMediaNodeIdPicker.Value = StartNodeId.ToString(); break; } ControlHeightTextBox.Text = ControlHeight.ToString(); MinItemsTextBox.Text = MinNodeCount.ToString(); } } /// /// Creates child controls for this control /// protected override void CreateChildControls() { base.CreateChildControls(); TreeTypeDropDown = new DropDownList { ID = "TreeTypeList" }; TreeTypeDropDown.Items.Add(new ListItem("Content", "content")); TreeTypeDropDown.Items.Add(new ListItem("Media", "media")); TreeTypeDropDown.AutoPostBack = true; AddPreValueRow(MNTPResources.Lbl_SelectTreeType, "", TreeTypeDropDown); StartNodeSelectionTypeRadioButtons = new RadioButtonList { ID = "NodeSelectionTypeRadioButtons" }; StartNodeSelectionTypeRadioButtons.Items.Add(MNTPResources.Item_NodeSelectionType_Picker); StartNodeSelectionTypeRadioButtons.Items.Add(new ListItem(MNTPResources.Item_NodeSelectionType_XPath, MNTPResources.Item_NodeSelectionType_XPath.Replace(" ", ""))); StartNodeSelectionTypeRadioButtons.RepeatDirection = RepeatDirection.Horizontal; StartNodeSelectionTypeRadioButtons.AutoPostBack = true; AddPreValueRow(MNTPResources.Lbl_NodeSelectionType, MNTPResources.Desc_NodeSelectionType, StartNodeSelectionTypeRadioButtons); StartContentNodeIdPicker = new SimpleContentPicker { ID = "StartNodeIdTextBox" }; AddPreValueRow(MNTPResources.Lbl_StartNodeId, MNTPResources.Desc_StartNodeId, StartContentNodeIdPicker); StartMediaNodeIdPicker = new SimpleMediaPicker { ID = "StartMediaNodeIdPicker" }; AddPreValueRow(MNTPResources.Lbl_StartNodeId, MNTPResources.Desc_StartNodeId, StartMediaNodeIdPicker); ShowThumbnailsForMediaCheckBox = new CheckBox { ID = "ShowThumbnailsForMedia" }; AddPreValueRow(MNTPResources.Lbl_ShowThumbnails, MNTPResources.Desc_ShowThumbnails, ShowThumbnailsForMediaCheckBox); StartNodeXPathExpressionTypeRadioButtons = new RadioButtonList { ID = "XPathExpressionTypeRadioButtons" }; StartNodeXPathExpressionTypeRadioButtons.Items.Add(MNTPResources.Item_XPathExpressionType_Global); StartNodeXPathExpressionTypeRadioButtons.Items.Add(new ListItem(MNTPResources.Item_XPathExpressionType_CurrentNode, MNTPResources.Item_XPathExpressionType_CurrentNode.Replace(" ", ""))); StartNodeXPathExpressionTypeRadioButtons.RepeatDirection = RepeatDirection.Horizontal; AddPreValueRow(MNTPResources.Lbl_XPathExpressionType, MNTPResources.Desc_XPathExpressionType, StartNodeXPathExpressionTypeRadioButtons); StartNodeXPathExpressionTextBox = new TextBox { ID = "XPathExpressionTextBox", Width = Unit.Pixel(400) }; RelativeXpathValidator = new CustomValidator() { ID = "RelativeXpathValidator", ControlToValidate = "XPathExpressionTextBox", ErrorMessage = MNTPResources.Val_RelativeXpath, CssClass = "validator" }; RelativeXpathValidator.ServerValidate += new ServerValidateEventHandler(RelativeXpathValidator_ServerValidate); AddPreValueRow(MNTPResources.Lbl_XPathExpression, MNTPResources.Desc_XPathExpression, StartNodeXPathExpressionTextBox, RelativeXpathValidator); XPathFilterTypeRadioButtons = new RadioButtonList { ID = "XPathMatchTypeRadioButtons" }; XPathFilterTypeRadioButtons.Items.Add(MNTPResources.Item_XPathMatchType_Disable); XPathFilterTypeRadioButtons.Items.Add(MNTPResources.Item_XPathMatchType_Enable); XPathFilterTypeRadioButtons.RepeatDirection = RepeatDirection.Horizontal; AddPreValueRow(MNTPResources.Lbl_XPathFilterType, MNTPResources.Desc_XPathFilterType, XPathFilterTypeRadioButtons); XPathFilterTextBox = new TextBox { ID = "XPathFilter", Width = Unit.Pixel(400) }; AddPreValueRow(MNTPResources.Lbl_XPathFilter, MNTPResources.Desc_XPathFilter, XPathFilterTextBox); MaxItemsTextBox = new TextBox { ID = "MaxItemsCount", Width = Unit.Pixel(50) }; NumbersMaxItemsValidator = new RegularExpressionValidator { ID = "NumbersMaxItemsValidator", ControlToValidate = "MaxItemsCount", CssClass = "validator", ErrorMessage = MNTPResources.Val_MaxItemsMsg, ValidationExpression = @"^-{0,1}\d*\.{0,1}\d+$" }; AddPreValueRow(MNTPResources.Lbl_MaxItemsAllowed, MNTPResources.Desc_MaxItemsAllowed, MaxItemsTextBox, NumbersMaxItemsValidator); MinItemsTextBox = new TextBox { ID = "MinItemsCount", Width = Unit.Pixel(50) }; NumbersMinItemsValidator = new RegularExpressionValidator { ID = "NumbersMinItemsValidator", ControlToValidate = "MinItemsCount", CssClass = "validator", ErrorMessage = MNTPResources.Val_MinItemsMsg, ValidationExpression = @"^\d{1,3}$" }; AddPreValueRow(MNTPResources.Lbl_MinItemsAllowed, MNTPResources.Desc_MinItemsAllowed, MinItemsTextBox, NumbersMinItemsValidator); ShowItemInfoTooltipCheckBox = new CheckBox { ID = "ShowItemInfoTooltipCheckBox" }; AddPreValueRow(MNTPResources.Lbl_ShowItemInfoTooltipCheckBox, MNTPResources.Desc_ShowTooltips, ShowItemInfoTooltipCheckBox); StoreAsCommaDelimitedRadioButtons = new RadioButtonList { ID = "StoreAsCommaDelimitedRadioButtons" }; StoreAsCommaDelimitedRadioButtons.Items.Add("XML"); StoreAsCommaDelimitedRadioButtons.Items.Add("CSV"); StoreAsCommaDelimitedRadioButtons.RepeatDirection = RepeatDirection.Horizontal; AddPreValueRow(MNTPResources.Lbl_StoreAsComma, MNTPResources.Desc_StoreAsComma, StoreAsCommaDelimitedRadioButtons); ControlHeightTextBox = new TextBox() { ID = "ControlHeightTextBox", Width = Unit.Pixel(50) }; ControlHeightValidatator = new RegularExpressionValidator { ID = "ControlHeightValidator", ControlToValidate = "ControlHeightTextBox", CssClass = "validator", ErrorMessage = MNTPResources.Val_ControlHeightMsg, ValidationExpression = @"^\d{1,3}$" }; AddPreValueRow(MNTPResources.Lbl_ControlHeight, "", ControlHeightTextBox, ControlHeightValidatator); } void RelativeXpathValidator_ServerValidate(object source, ServerValidateEventArgs args) { args.IsValid = true; if (TreeTypeDropDown.SelectedIndex == 0 && StartNodeXPathExpressionTypeRadioButtons.SelectedIndex == 1 && StartNodeXPathExpressionTextBox.Text.StartsWith("/")) { args.IsValid = false; } } /// /// Helper method to add a server side pre value row /// /// /// /// /// /// Using server side syntax because of the post backs and because i don't want to manage the view state manually /// private void AddPreValueRow(string lbl, string description, params Control[] ctl) { var div = new HtmlGenericControl("div"); div.Attributes.Add("class", "row clearfix"); var label = new HtmlGenericControl("div"); label.Attributes.Add("class", "label"); var span = new HtmlGenericControl("span"); span.InnerText = lbl; label.Controls.Add(span); var field = new HtmlGenericControl("div"); field.Attributes.Add("class", "field"); foreach (var c in ctl) { field.Controls.Add(c); } div.Controls.Add(label); div.Controls.Add(field); if (!string.IsNullOrEmpty(description)) { var desc = new HtmlGenericControl("div"); var descSpan = new HtmlGenericControl("span"); descSpan.InnerHtml = description; desc.Attributes.Add("class", "description"); desc.Controls.Add(descSpan); div.Controls.Add(desc); } this.Controls.Add(div); } /// /// Hides/Shows controls based on the selection of other controls /// /// protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); //we can only show the node selection type based on content if (TreeTypeDropDown.SelectedIndex == 0) { this.StartNodeSelectionTypeRadioButtons.Parent.Parent.Visible = true; } else { this.StartNodeSelectionTypeRadioButtons.Parent.Parent.Visible = false; } //if media is selected, or the node type selection is a picker type if (TreeTypeDropDown.SelectedIndex == 1 || StartNodeSelectionTypeRadioButtons.SelectedIndex == 0) { StartNodeXPathExpressionTypeRadioButtons.Parent.Parent.Visible = false; StartNodeXPathExpressionTextBox.Parent.Parent.Visible = false; switch (TreeTypeDropDown.SelectedIndex) { case 0: //content selected StartContentNodeIdPicker.Parent.Parent.Visible = true; StartMediaNodeIdPicker.Parent.Parent.Visible = false; ShowThumbnailsForMediaCheckBox.Parent.Parent.Visible = false; break; case 1: default: //media selected: StartContentNodeIdPicker.Parent.Parent.Visible = false; StartMediaNodeIdPicker.Parent.Parent.Visible = true; ShowThumbnailsForMediaCheckBox.Parent.Parent.Visible = true; break; } } else { //since it's an xpath expression, not node picker, hide all node pickers StartContentNodeIdPicker.Parent.Parent.Visible = false; StartMediaNodeIdPicker.Parent.Parent.Visible = false; ShowThumbnailsForMediaCheckBox.Parent.Parent.Visible = false; StartNodeXPathExpressionTypeRadioButtons.Parent.Parent.Visible = true; StartNodeXPathExpressionTextBox.Parent.Parent.Visible = true; } } /// /// render our own custom markup /// /// The object that receives the server control content. protected override void Render(HtmlTextWriter writer) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "PrevalueEditor"); writer.RenderBeginTag(HtmlTextWriterTag.Div); //start 'PrevalueEditor' base.Render(writer); writer.RenderEndTag(); //end 'PrevalueEditor' } /// /// Lazy loads the prevalues for this data type /// /// private SortedList GetPreValues() { if (m_PreValues == null) { m_PreValues = PreValues.GetPreValues(m_DataType.DataTypeDefinitionId); } return m_PreValues; } #region IDataPrevalue Members /// /// returns this as it's own editor /// public Control Editor { get { return this; } } /// /// Saves data to Umbraco /// public void Save() { if (!Page.IsValid) { return; } //it will always be text since people may save a huge amount of selected nodes and serializing to xml could be large. m_DataType.DBType = umbraco.cms.businesslogic.datatype.DBTypes.Ntext; //need to lock this operation since multiple inserts are happening and if 2 threads reach here at the same time, there //could be issues. lock (Locker) { var vals = GetPreValues(); //store the tree type SavePreValue(PropertyIndex.TreeType, TreeTypeDropDown.SelectedValue, vals); //store the xpath SavePreValue(PropertyIndex.XPathFilter, XPathFilterTextBox.Text, vals); //store the max node count SavePreValue(PropertyIndex.MaxNodeCount, string.IsNullOrEmpty(MaxItemsTextBox.Text) ? "-1" : MaxItemsTextBox.Text, vals); //store the 'show tooltips' SavePreValue(PropertyIndex.ShowToolTip, ShowItemInfoTooltipCheckBox.Checked.ToString(), vals); //store the 'as comma' SavePreValue(PropertyIndex.StoreAsCommaDelimited, StoreAsCommaDelimitedRadioButtons.SelectedIndex.ToString(), vals); //the xpath filter type SavePreValue(PropertyIndex.XPathFilterType, XPathFilterTypeRadioButtons.SelectedIndex.ToString(), vals); //based on the media type selected, we need to get the correct start node id //from the correct control. var startNodeId = -1; switch (TreeTypeDropDown.SelectedIndex) { case 0: int.TryParse(StartContentNodeIdPicker.Value, out startNodeId); break; case 1: default: int.TryParse(StartMediaNodeIdPicker.Value, out startNodeId); break; } //store the start node id SavePreValue(PropertyIndex.StartNodeId, startNodeId.ToString(), vals); //store the 'show thumbnails' SavePreValue(PropertyIndex.ShowThumbnails, ShowThumbnailsForMediaCheckBox.Checked.ToString(), vals); //store the 'node selection type' SavePreValue(PropertyIndex.StartNodeSelectionType, StartNodeSelectionTypeRadioButtons.SelectedIndex.ToString(), vals); //store the 'xpath expression type' SavePreValue(PropertyIndex.StartNodeXPathExpressionType, StartNodeXPathExpressionTypeRadioButtons.SelectedIndex.ToString(), vals); //save the 'xpath expression' SavePreValue(PropertyIndex.StartNodeXPathExpression, StartNodeXPathExpressionTextBox.Text, vals); //save the control height SavePreValue(PropertyIndex.ControlHeight, ControlHeightTextBox.Text, vals); //save the min amount SavePreValue(PropertyIndex.MinNodeCount, MinItemsTextBox.Text, vals); } //once everything is saved, clear the cookie vals MNTP_DataType.ClearCookiePersistence(); } /// /// Used to determine the index number of where the property is saved in the pre values repository /// private enum PropertyIndex { TreeType, XPathFilter, MaxNodeCount, ShowToolTip, StoreAsCommaDelimited, XPathFilterType, StartNodeId, ShowThumbnails, StartNodeSelectionType, StartNodeXPathExpressionType, StartNodeXPathExpression, ControlHeight, MinNodeCount } /// /// Helper method to save/create pre value values in the db /// /// /// /// private void SavePreValue(PropertyIndex propIndex, string value, SortedList currentVals) { var index = (int)propIndex; if (currentVals.Count >= ((int)propIndex + 1)) { //update ((PreValue)currentVals[index]).Value = value; ((PreValue)currentVals[index]).Save(); } else { //insert PreValue.MakeNew(m_DataType.DataTypeDefinitionId, value); } } /// /// Generic method to return a strongly typed object from the pre value bucket /// /// /// /// /// /// private T GetPreValue(PropertyIndex index, Func output, T defaultVal) { var vals = GetPreValues(); return vals.Count >= (int)index + 1 ? output((PreValue)vals[(int)index]) : defaultVal; } #endregion } }