uComponents: Added MNTP to the core!

This commit is contained in:
leekelleher
2012-04-28 13:57:04 -01:00
parent a0d9e668bd
commit c2c4bb7413
23 changed files with 3791 additions and 3 deletions

View File

@@ -0,0 +1,194 @@
using System;
using System.Linq;
using System.Web;
using umbraco.cms.businesslogic;
using umbraco.cms.presentation.Trees;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// BaseTree extensions for MultiNodeTreePicker.
/// </summary>
public static class BaseTreeExtensions
{
internal const int NoAccessId = -123456789;
internal const int NoChildNodesId = -897987654;
/// <summary>
/// Determines if it needs to render a null tree based on the start node id and returns true if it is the case.
/// </summary>
/// <param name="tree"></param>
/// <param name="startNodeId"></param>
/// <param name="rootNode"></param>
/// <param name="app"></param>
/// <returns></returns>
internal static bool SetNullTreeRootNode(this BaseTree tree, int startNodeId, ref XmlTreeNode rootNode, string app)
{
if (startNodeId == NoAccessId)
{
rootNode = new NullTree(app).RootNode;
rootNode.Text = "You do not have permission to view this tree";
rootNode.HasChildren = false;
rootNode.Source = string.Empty;
return true;
}
if (startNodeId == NoChildNodesId)
{
rootNode = new NullTree(app).RootNode;
rootNode.Text = "[No start node found]";
rootNode.HasChildren = false;
rootNode.Source = string.Empty;
return true;
}
return false;
}
/// <summary>
/// Used to determine the start node id while taking into account a user's security
/// </summary>
/// <param name="tree"></param>
/// <param name="definedStartNode"></param>
/// <param name="userStartNode"></param>
/// <returns></returns>
internal static int GetStartNodeId(this BaseTree tree, Content definedStartNode, Content userStartNode)
{
if (userStartNode == null)
{
throw new ArgumentNullException("userStartNode");
}
//the output start node id
var determinedStartNodeId = uQuery.RootNodeId;
if (definedStartNode == null)
{
//if the defined (desired) start node is null (could not be found), return NoChildNodesId
determinedStartNodeId = NoChildNodesId;
}
else if (definedStartNode.Id == uQuery.RootNodeId)
{
//if the id is -1, then the start node is the user's start node
determinedStartNodeId = userStartNode.Id;
}
else if (definedStartNode.Path.Split(',').Contains(userStartNode.Id.ToString()))
{
//If User's start node id is found in the defined path, then the start node id
//can (allowed) be the defined path.
//This should always work for administrator (-1)
determinedStartNodeId = definedStartNode.Id;
}
else if (userStartNode.Path.Split(',').Contains(definedStartNode.Id.ToString()))
{
//if the defined start node id is found in the user's path, then the start node id
//can only be the user's, not the actual start
determinedStartNodeId = userStartNode.Id;
}
else if (!definedStartNode.Path.Split(',').Contains(userStartNode.Id.ToString()))
{
//they should not have any access!
determinedStartNodeId = NoAccessId;
}
return determinedStartNodeId;
}
/// <summary>
/// Returns the data type id for the current base tree
/// </summary>
/// <remarks>
/// The data type definition id is persisted between request as a query string.
/// This is used to retreive values from the cookie which are easier persisted values
/// than trying to append everything to custom query strings.
/// </remarks>
/// <param name="tree"></param>
/// <returns></returns>
internal static int GetDataTypeId(this BaseTree tree)
{
var id = -1;
int.TryParse(tree.NodeKey, out id);
return id;
}
/// <summary>
/// return the xpath statement stored in the cookie for this control id
/// </summary>
/// <param name="tree"></param>
/// <param name="dataTypeDefId"></param>
/// <returns></returns>
internal static string GetXPathFromCookie(this BaseTree tree, int dataTypeDefId)
{
//try to read an existing cookie
var cookie = HttpContext.Current.Request.Cookies["MultiNodeTreePicker"];
if (cookie != null && cookie.Values.Count > 0)
{
return cookie.MntpGetXPathFilter(dataTypeDefId);
}
return "";
}
/// <summary>
/// Returns the xpath filter from the cookie for the current data type
/// </summary>
/// <param name="tree"></param>
/// <param name="dataTypeDefId"></param>
/// <returns></returns>
internal static XPathFilterType GetXPathFilterTypeFromCookie(this BaseTree tree, int dataTypeDefId)
{
//try to read an existing cookie
var cookie = HttpContext.Current.Request.Cookies["MultiNodeTreePicker"];
if (cookie != null && cookie.Values.Count > 0)
{
return cookie.MntpGetXPathFilterType(dataTypeDefId);
}
return XPathFilterType.Disable;
}
/// <summary>
/// Helper method to return the persisted cookied value for the tree
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tree"></param>
/// <param name="output"></param>
/// <param name="defaultVal"></param>
/// <returns></returns>
internal static T GetPersistedCookieValue<T>(this BaseTree tree, Func<HttpCookie, T> output, T defaultVal)
{
var cookie = HttpContext.Current.Request.Cookies["MultiNodeTreePicker"];
if (cookie != null && cookie.Values.Count > 0)
{
return output(cookie);
}
return defaultVal;
}
/// <summary>
/// This will return the normal service url based on id but will also ensure that the data type definition id is passed through as the nodeKey param
/// </summary>
/// <param name="tree">The tree.</param>
/// <param name="id">The id.</param>
/// <param name="dataTypeDefId">The data type def id.</param>
/// <returns></returns>
/// <remarks>
/// We only need to set the custom source to pass in our extra NodeKey data.
/// By default the system will use one or the other: Id or NodeKey, in this case
/// we are sort of 'tricking' the system and we require both.
/// Umbraco allows you to theoretically pass in any source as long as it meets the standard
/// which means you can pass around any arbitrary data to your trees in the form of a query string,
/// though it's just a bit convoluted to do so.
/// </remarks>
internal static string GetTreeServiceUrlWithParams(this BaseTree tree, int id, int dataTypeDefId)
{
var url = tree.GetTreeServiceUrl(id);
//append the node key
return url + "&nodeKey=" + dataTypeDefId;
}
}
}

View File

@@ -0,0 +1,221 @@
using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using umbraco.cms.businesslogic.web;
using umbraco.cms.presentation.Trees;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// FilteredContentTree for the MultiNodeTreePicker
/// </summary>
public class FilteredContentTree : BaseContentTree
{
/// <summary>
/// Initializes a new instance of the <see cref="FilteredContentTree"/> class.
/// </summary>
/// <param name="app">The app.</param>
public FilteredContentTree(string app)
: base(app)
{
}
/// <summary>
///
/// </summary>
private Document m_UserStartNodeDoc;
/// <summary>
///
/// </summary>
private Document m_DefinedStartNodeDoc;
/// <summary>
/// The start node id determined by the defined id and by the user's defined id
/// </summary>
private int? m_DeterminedStartNodeId = null;
/// <summary>
/// Returns the Document object of the starting node for the current User. This ensures
/// that the Document object is only instantiated once.
/// </summary>
protected Document UserStartNodeDoc
{
get
{
if (m_UserStartNodeDoc == null)
{
if (CurrentUser.StartNodeId <= 0)
{
return new Document(uQuery.RootNodeId, true);
}
m_UserStartNodeDoc = new Document(CurrentUser.StartNodeId);
}
return m_UserStartNodeDoc;
}
}
/// <summary>
/// Returns the Document object of the starting node that is defined in the prevalue editor. This ensures
/// that the Document object is only instantiated once.
/// </summary>
protected Document DefinedStartNodeDoc
{
get
{
if (m_DefinedStartNodeDoc == null)
{
var startNodeSelectionType =
this.GetPersistedCookieValue(x => x.MntpGetStartNodeSelectionType(this.GetDataTypeId()),
NodeSelectionType.Picker);
switch (startNodeSelectionType)
{
case NodeSelectionType.Picker:
//if it is a picker, then find the start node id
var definedId = this.GetPersistedCookieValue(
x => x.MntpGetStartNodeId(this.GetDataTypeId()), uQuery.RootNodeId);
//return a document with id -1 (don't set this up as it will exception!)
m_DefinedStartNodeDoc = (definedId > 0) ? new Document(definedId) : new Document(uQuery.RootNodeId, true);
break;
case NodeSelectionType.XPathExpression:
//if it is an expression, then we need to find the start node based on the xpression type, etc...
var expressionType =
this.GetPersistedCookieValue(
x => x.MntpGetStartNodeXPathExpressionType(this.GetDataTypeId()),
XPathExpressionType.Global);
//the default expression should match both schema types
var xpath =
this.GetPersistedCookieValue(
x => x.MntpGetStartNodeXPathExpression(this.GetDataTypeId()), "//*[number(@id)>0]");
switch (expressionType)
{
case XPathExpressionType.Global:
//if its a global expression, then we need to run the xpath against the entire tree
var nodes = uQuery.GetNodesByXPath(xpath);
//we'll just use the first node found (THERE SHOULD ONLY BE ONE)
m_DefinedStartNodeDoc = nodes.Any() ? new Document(nodes.First().Id) : null;
break;
case XPathExpressionType.FromCurrent:
//if it's a relative query, then it cannot start with //!
if (xpath.StartsWith("/"))
{
throw new InvalidOperationException("A relative xpath query cannot start with a slash");
}
//if it's a FromCurrent expression, then we need to run the xpath from this node and below
var currId =
this.GetPersistedCookieValue(
x => x.MntpGetCurrentEditingNode(this.GetDataTypeId()), uQuery.RootNodeId);
var currNode = umbraco.library.GetXmlNodeById(currId.ToString());
if (currNode.MoveNext())
{
if (currNode.Current != null)
{
var result = currNode.Current.Select(xpath);
//set it to the first node found (if there is one), otherwise to -1
if (result.Current != null)
m_DefinedStartNodeDoc = result.MoveNext() ? uQuery.GetDocument(result.Current.GetAttribute("id", string.Empty)) : null;
else
m_DefinedStartNodeDoc = null;
}
else
m_DefinedStartNodeDoc = null;
}
else
{
m_DefinedStartNodeDoc = null;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return m_DefinedStartNodeDoc;
}
}
#region Overridden methods
/// <summary>
/// Determines the allowed start node id based on the users start node id and the
/// defined start node id in the data type.
/// </summary>
public override int StartNodeID
{
get
{
//we only need to determine the id once
if (m_DeterminedStartNodeId.HasValue)
{
return m_DeterminedStartNodeId.Value;
}
//based on security principles, get the actual start node id
m_DeterminedStartNodeId = this.GetStartNodeId(DefinedStartNodeDoc, UserStartNodeDoc);
return m_DeterminedStartNodeId.Value;
}
}
/// <summary>
/// Creates the root node.
/// </summary>
/// <param name="rootNode">The root node.</param>
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
if (!this.SetNullTreeRootNode(StartNodeID, ref rootNode, app))
{
rootNode.Action = "javascript:openContent(-1);";
rootNode.Source = this.GetTreeServiceUrlWithParams(StartNodeID, this.GetDataTypeId());
if (StartNodeID > 0)
{
var startNode = new Document(StartNodeID);
rootNode.Text = startNode.Text;
rootNode.Icon = startNode.ContentTypeIcon;
}
}
}
/// <summary>
/// Called when [render node].
/// </summary>
/// <param name="xNode">The x node.</param>
/// <param name="doc">The doc.</param>
protected override void OnRenderNode(ref XmlTreeNode xNode, Document doc)
{
base.OnRenderNode(ref xNode, doc);
var dataTypeId = this.GetDataTypeId();
var xpath = this.GetXPathFromCookie(dataTypeId);
var xPathType = this.GetXPathFilterTypeFromCookie(dataTypeId);
// resolves any Umbraco params in the XPath
xpath = uQuery.ResolveXPath(xpath);
var xDoc = new XmlDocument();
XmlNode xmlDoc;
if (!doc.Published)
{
xmlDoc = doc.ToPreviewXml(xDoc);
}
else
{
xmlDoc = doc.ToXml(xDoc, false);
}
var xmlString = "<root>" + xmlDoc.OuterXml + "</root>";
var xml = XElement.Parse(xmlString);
xNode.DetermineClickable(xpath, xPathType, xml);
//ensure that the NodeKey is passed through
xNode.Source = this.GetTreeServiceUrlWithParams(int.Parse(xNode.NodeID), dataTypeId);
}
#endregion
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Xml;
using System.Xml.Linq;
using umbraco.cms.businesslogic.media;
using umbraco.cms.presentation.Trees;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// FilteredMediaTree for the MultiNodeTreePicker.
/// </summary>
public class FilteredMediaTree : BaseMediaTree
{
/// <summary>
/// Initializes a new instance of the <see cref="FilteredMediaTree"/> class.
/// </summary>
/// <param name="app">The app.</param>
public FilteredMediaTree(string app)
: base(app)
{
}
/// <summary>
///
/// </summary>
private Media m_UserStartNodeMedia;
/// <summary>
///
/// </summary>
private Media m_DefinedStartNodeMedia;
/// <summary>
/// Returns the Media object of the starting node for the current User. This ensures
/// that the Media object is only instantiated once.
/// </summary>
protected Media UserStartNodeDoc
{
get
{
if (m_UserStartNodeMedia == null)
{
if (CurrentUser.StartMediaId <= 0)
{
return new Media(-1, true);
}
m_UserStartNodeMedia = new Media(CurrentUser.StartMediaId);
}
return m_UserStartNodeMedia;
}
}
/// <summary>
/// Returns the Media object of the starting node that is defined in the prevalue editor. This ensures
/// that the Media object is only instantiated once.
/// </summary>
protected Media DefinedStartNodeMedia
{
get
{
if (m_DefinedStartNodeMedia == null)
{
var definedId = this.GetPersistedCookieValue(x => x.MntpGetStartNodeId(this.GetDataTypeId()), -1);
if (definedId <= 0)
{
return new Media(-1, true);
}
m_DefinedStartNodeMedia = new Media(definedId);
}
return m_DefinedStartNodeMedia;
}
}
/// <summary>
/// The start node id determined by the defined id and by the user's defined id
/// </summary>
private int? m_DeterminedStartNodeId = null;
#region Overridden methods
/// <summary>
/// Determines the allowed start node id based on the users start node id and the
/// defined start node id in the data type.
/// </summary>
public override int StartNodeID
{
get
{
//we only need to determine the id once
if (m_DeterminedStartNodeId.HasValue)
{
return m_DeterminedStartNodeId.Value;
}
m_DeterminedStartNodeId = this.GetStartNodeId(DefinedStartNodeMedia, UserStartNodeDoc);
return m_DeterminedStartNodeId.Value;
}
}
/// <summary>
/// Creates the root node.
/// </summary>
/// <param name="rootNode">The root node.</param>
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
if (!this.SetNullTreeRootNode(StartNodeID, ref rootNode, app))
{
rootNode.Action = "javascript:openContent(-1);";
rootNode.Source = this.GetTreeServiceUrlWithParams(StartNodeID, this.GetDataTypeId());
if (StartNodeID > 0)
{
var startNode = new Media(StartNodeID);
rootNode.Text = startNode.Text;
rootNode.Icon = startNode.ContentTypeIcon;
}
}
}
/// <summary>
/// Called when [before node render].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="node">The node.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected override void OnBeforeNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e)
{
var xpath = this.GetXPathFromCookie(this.GetDataTypeId());
var xPathType = this.GetXPathFilterTypeFromCookie(this.GetDataTypeId());
var xDoc = new XmlDocument();
var xmlNode = umbraco.library.GetMedia(int.Parse(node.NodeID), false).Current.OuterXml;
var xmlString = "<root>" + xmlNode + "</root>";
var xml = XElement.Parse(xmlString);
node.DetermineClickable(xpath, xPathType, xml);
//ensure that the NodeKey is passed through
node.Source = this.GetTreeServiceUrlWithParams(int.Parse(node.NodeID), this.GetDataTypeId());
base.OnBeforeNodeRender(ref sender, ref node, e);
}
#endregion
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Linq;
using System.Web;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// A helper class to store and retrieve cookie values for the MNTP cookie.
/// </summary>
/// <remarks>
/// The cookie is used to persist values from the client to the server since
/// it is much more complicated to try to persist these values between ajax request,
/// given the tree's current architecture.
/// </remarks>
public static class HttpCookieExtensions
{
private enum CookieVals
{
/// <summary>
/// XPath filter
/// </summary>
Xpf,
/// <summary>
/// XPath filter type
/// </summary>
Xpft,
/// <summary>
/// Start node
/// </summary>
Sn,
/// <summary>
/// Start node xpath expression type
/// </summary>
Snxet,
/// <summary>
/// Start node select type
/// </summary>
Snst,
/// <summary>
/// Start node xpath expression
/// </summary>
Snxe,
/// <summary>
/// Current editing node id
/// </summary>
Ceni
}
#region Setters
internal static void MntpAddXPathFilter(this HttpCookie c, int id, string xpath)
{
c[string.Concat(CookieVals.Xpf, "_", id)] = xpath;
}
internal static void MntpAddXPathFilterType(this HttpCookie c, int id, XPathFilterType type)
{
c[string.Concat(CookieVals.Xpft, "_", id)] = ((int)type).ToString();
}
internal static void MntpAddStartNodeId(this HttpCookie c, int id, int startNodeId)
{
c[string.Concat(CookieVals.Sn, "_", id)] = startNodeId.ToString();
}
internal static void MntpAddStartNodeXPathExpressionType(this HttpCookie c, int id, XPathExpressionType xPathExpressionType)
{
c[string.Concat(CookieVals.Snxet, "_", id)] = ((int)xPathExpressionType).ToString();
}
internal static void MntpAddStartNodeSelectionType(this HttpCookie c, int id, NodeSelectionType startNodeSelectionType)
{
c[string.Concat(CookieVals.Snst, "_", id)] = ((int)startNodeSelectionType).ToString();
}
internal static void MntpAddStartNodeXPathExpression(this HttpCookie c, int id, string xPathExpression)
{
c[string.Concat(CookieVals.Snxe, "_", id)] = xPathExpression;
}
internal static void MntpAddCurrentEditingNode(this HttpCookie c, int id, int currEditingNodeId)
{
c[string.Concat(CookieVals.Ceni, "_", id)] = currEditingNodeId.ToString();
}
#endregion
#region Getters
internal static string MntpGetXPathFilter(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Xpf, dataTypeId)
? c.Values[string.Concat(CookieVals.Xpf, "_", dataTypeId)]
: string.Empty;
}
internal static XPathFilterType MntpGetXPathFilterType(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Xpft, dataTypeId)
? (XPathFilterType) Enum.ToObject(typeof (XPathFilterType),
int.Parse(
c.Values[string.Concat(CookieVals.Xpft, "_", dataTypeId)]))
: XPathFilterType.Disable;
}
internal static int MntpGetStartNodeId(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Sn, dataTypeId)
? int.Parse(c.Values[string.Concat(CookieVals.Sn, "_", dataTypeId)])
: -1;
}
internal static XPathExpressionType MntpGetStartNodeXPathExpressionType(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Snxet, dataTypeId)
? (XPathExpressionType)
Enum.ToObject(typeof(XPathExpressionType),
int.Parse(c.Values[string.Concat(CookieVals.Snxet, "_", dataTypeId)]))
: XPathExpressionType.Global;
}
internal static NodeSelectionType MntpGetStartNodeSelectionType(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Snst, dataTypeId)
? (NodeSelectionType)
Enum.ToObject(typeof(NodeSelectionType),
int.Parse(c.Values[string.Concat(CookieVals.Snst, "_", dataTypeId)]))
: NodeSelectionType.Picker;
}
internal static string MntpGetStartNodeXPathExpression(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Snxe, dataTypeId)
? c.Values[string.Concat(CookieVals.Snxe, "_", dataTypeId)]
: string.Empty;
}
internal static int MntpGetCurrentEditingNode(this HttpCookie c, int dataTypeId)
{
return c.ValidateCookieVal(CookieVals.Ceni, dataTypeId)
? int.Parse(c.Values[string.Concat(CookieVals.Ceni, "_", dataTypeId)])
: -1;
}
private static bool ValidateCookieVal(this HttpCookie c, CookieVals val, int dataTypeId)
{
return dataTypeId == 0 ? false : (c.Values.Keys.Cast<string>().Where(x => x == string.Concat(val, "_", dataTypeId)).Any());
}
#endregion
}
}

View File

@@ -0,0 +1,378 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.488
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class MNTPResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal MNTPResources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("uComponents.DataTypes.MultiNodeTreePicker.MNTPResources", typeof(MNTPResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The maximum number of items that are allowed to be selected by a content editor. If an unlimited number of node selections should be allowed, then enter -1 as the value.
/// </summary>
public static string Desc_MaxItemsAllowed {
get {
return ResourceManager.GetString("Desc_MaxItemsAllowed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The minimum number of items that are allowed to be selected by a content editor..
/// </summary>
public static string Desc_MinItemsAllowed {
get {
return ResourceManager.GetString("Desc_MinItemsAllowed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Nodes can be selected from a parent node (Node Picker) or by an XPath expression. NOTE:When using an XPath expression, only published nodes can be shown..
/// </summary>
public static string Desc_NodeSelectionType {
get {
return ResourceManager.GetString("Desc_NodeSelectionType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to If enabled and &apos;media&apos; is selected as the tree type then a thumbnail will be rendered for each item selected..
/// </summary>
public static string Desc_ShowThumbnails {
get {
return ResourceManager.GetString("Desc_ShowThumbnails", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to If enabled will show an information icon next to each node selected. When the icon is clicked, a tooltip is shown displaying the nodes extended properties..
/// </summary>
public static string Desc_ShowTooltips {
get {
return ResourceManager.GetString("Desc_ShowTooltips", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Define the starting node that should be rendered for the picker. This will ensure that the correct security measures are in place by checking the defined start node ID for the user accessing the picker. In some cases, if the user is not able to view the node, then the picker will render an error message..
/// </summary>
public static string Desc_StartNodeId {
get {
return ResourceManager.GetString("Desc_StartNodeId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You can specify to store the data in Umbraco as comma seperated or as XML. By default it is stored as XML which makes it easier to use the saved value in XSLT, however, storing it as comma seperated makes it easier to work with the data using the API such as Node factory..
/// </summary>
public static string Desc_StoreAsComma {
get {
return ResourceManager.GetString("Desc_StoreAsComma", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This XPath expression is used to select a starting node and depends on the XPath expression type chosen (global or from current). IMPORTANT: This XPath expression should be written to match ONE node, if the expression matches more than one node, then the first node matched will be used as the start node..
/// </summary>
public static string Desc_XPathExpression {
get {
return ResourceManager.GetString("Desc_XPathExpression", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The XPath expression that is evaluated to match a start node can be evaluated at a global tree level, or matched from the current node being edited..
/// </summary>
public static string Desc_XPathExpressionType {
get {
return ResourceManager.GetString("Desc_XPathExpressionType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An xpath filter to match nodes that will be either enabled or disabled from being clicked (depending on what is selected for the XPath filter type). This XPath filter is for one node only so it should be formatted to select only one node. The XML to XPath against is the same as the Umbraco XML for one node.&lt;br/&gt;&lt;br/&gt;Example: /*[name()=&apos;myNodeType&apos; or name()=&apos;yourNodeType&apos;] &lt;br/&gt;&lt;br/&gt;The above would make all nodes of types: myNodeType or yourNodeType not selectable in the tree.
/// </summary>
public static string Desc_XPathFilter {
get {
return ResourceManager.GetString("Desc_XPathFilter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Should the XPath filter match nodes to enable nodes or disable nodes. If Enable is selected, this means that only nodes that match the XPath filter will be allowed to be selected in the tree picker and vise versa for Disabled.
/// </summary>
public static string Desc_XPathFilterType {
get {
return ResourceManager.GetString("Desc_XPathFilterType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Node Picker.
/// </summary>
public static string Item_NodeSelectionType_Picker {
get {
return ResourceManager.GetString("Item_NodeSelectionType_Picker", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to XPath Expression.
/// </summary>
public static string Item_NodeSelectionType_XPath {
get {
return ResourceManager.GetString("Item_NodeSelectionType_XPath", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to From Current.
/// </summary>
public static string Item_XPathExpressionType_CurrentNode {
get {
return ResourceManager.GetString("Item_XPathExpressionType_CurrentNode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Global.
/// </summary>
public static string Item_XPathExpressionType_Global {
get {
return ResourceManager.GetString("Item_XPathExpressionType_Global", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Disable.
/// </summary>
public static string Item_XPathMatchType_Disable {
get {
return ResourceManager.GetString("Item_XPathMatchType_Disable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enable.
/// </summary>
public static string Item_XPathMatchType_Enable {
get {
return ResourceManager.GetString("Item_XPathMatchType_Enable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pixel height of the tree control box.
/// </summary>
public static string Lbl_ControlHeight {
get {
return ResourceManager.GetString("Lbl_ControlHeight", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Maximum node selections.
/// </summary>
public static string Lbl_MaxItemsAllowed {
get {
return ResourceManager.GetString("Lbl_MaxItemsAllowed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Minimum node selections.
/// </summary>
public static string Lbl_MinItemsAllowed {
get {
return ResourceManager.GetString("Lbl_MinItemsAllowed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Node selection type.
/// </summary>
public static string Lbl_NodeSelectionType {
get {
return ResourceManager.GetString("Lbl_NodeSelectionType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select tree type.
/// </summary>
public static string Lbl_SelectTreeType {
get {
return ResourceManager.GetString("Lbl_SelectTreeType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show tooltip for selected item.
/// </summary>
public static string Lbl_ShowItemInfoTooltipCheckBox {
get {
return ResourceManager.GetString("Lbl_ShowItemInfoTooltipCheckBox", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show thumbnails for media items?.
/// </summary>
public static string Lbl_ShowThumbnails {
get {
return ResourceManager.GetString("Lbl_ShowThumbnails", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Start node ID.
/// </summary>
public static string Lbl_StartNodeId {
get {
return ResourceManager.GetString("Lbl_StartNodeId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Data as CSV or XML? .
/// </summary>
public static string Lbl_StoreAsComma {
get {
return ResourceManager.GetString("Lbl_StoreAsComma", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to XPath expression.
/// </summary>
public static string Lbl_XPathExpression {
get {
return ResourceManager.GetString("Lbl_XPathExpression", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to XPath type.
/// </summary>
public static string Lbl_XPathExpressionType {
get {
return ResourceManager.GetString("Lbl_XPathExpressionType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to XPath filter.
/// </summary>
public static string Lbl_XPathFilter {
get {
return ResourceManager.GetString("Lbl_XPathFilter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to XPath filter type.
/// </summary>
public static string Lbl_XPathFilterType {
get {
return ResourceManager.GetString("Lbl_XPathFilterType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please enter a pixel value between 1 - 999.
/// </summary>
public static string Val_ControlHeightMsg {
get {
return ResourceManager.GetString("Val_ControlHeightMsg", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please enter only numbers.
/// </summary>
public static string Val_MaxItemsMsg {
get {
return ResourceManager.GetString("Val_MaxItemsMsg", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The highlighted Multi Node Tree Picker property requires a minimum node selection of {0}.
/// </summary>
public static string Val_MinItemsInvalid {
get {
return ResourceManager.GetString("Val_MinItemsInvalid", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please enter only numbers.
/// </summary>
public static string Val_MinItemsMsg {
get {
return ResourceManager.GetString("Val_MinItemsMsg", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A relative xpath expression cannot start with &quot;/&quot; or &quot;//&quot;.
/// </summary>
public static string Val_RelativeXpath {
get {
return ResourceManager.GetString("Val_RelativeXpath", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,225 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Desc_MaxItemsAllowed" xml:space="preserve">
<value>The maximum number of items that are allowed to be selected by a content editor. If an unlimited number of node selections should be allowed, then enter -1 as the value</value>
</data>
<data name="Desc_MinItemsAllowed" xml:space="preserve">
<value>The minimum number of items that are allowed to be selected by a content editor.</value>
</data>
<data name="Desc_NodeSelectionType" xml:space="preserve">
<value>Nodes can be selected from a parent node (Node Picker) or by an XPath expression. NOTE:When using an XPath expression, only published nodes can be shown.</value>
</data>
<data name="Desc_ShowThumbnails" xml:space="preserve">
<value>If enabled and 'media' is selected as the tree type then a thumbnail will be rendered for each item selected.</value>
</data>
<data name="Desc_ShowTooltips" xml:space="preserve">
<value>If enabled will show an information icon next to each node selected. When the icon is clicked, a tooltip is shown displaying the nodes extended properties.</value>
</data>
<data name="Desc_StartNodeId" xml:space="preserve">
<value>Define the starting node that should be rendered for the picker. This will ensure that the correct security measures are in place by checking the defined start node ID for the user accessing the picker. In some cases, if the user is not able to view the node, then the picker will render an error message.</value>
</data>
<data name="Desc_StoreAsComma" xml:space="preserve">
<value>You can specify to store the data in Umbraco as comma seperated or as XML. By default it is stored as XML which makes it easier to use the saved value in XSLT, however, storing it as comma seperated makes it easier to work with the data using the API such as Node factory.</value>
</data>
<data name="Desc_XPathExpression" xml:space="preserve">
<value>This XPath expression is used to select a starting node and depends on the XPath expression type chosen (global or from current). IMPORTANT: This XPath expression should be written to match ONE node, if the expression matches more than one node, then the first node matched will be used as the start node.</value>
</data>
<data name="Desc_XPathExpressionType" xml:space="preserve">
<value>The XPath expression that is evaluated to match a start node can be evaluated at a global tree level, or matched from the current node being edited.</value>
</data>
<data name="Desc_XPathFilter" xml:space="preserve">
<value>An xpath filter to match nodes that will be either enabled or disabled from being clicked (depending on what is selected for the XPath filter type). This XPath filter is for one node only so it should be formatted to select only one node. The XML to XPath against is the same as the Umbraco XML for one node.&lt;br/&gt;&lt;br/&gt;Example: /*[name()='myNodeType' or name()='yourNodeType'] &lt;br/&gt;&lt;br/&gt;The above would make all nodes of types: myNodeType or yourNodeType not selectable in the tree</value>
</data>
<data name="Desc_XPathFilterType" xml:space="preserve">
<value>Should the XPath filter match nodes to enable nodes or disable nodes. If Enable is selected, this means that only nodes that match the XPath filter will be allowed to be selected in the tree picker and vise versa for Disabled</value>
</data>
<data name="Item_NodeSelectionType_Picker" xml:space="preserve">
<value>Node Picker</value>
</data>
<data name="Item_NodeSelectionType_XPath" xml:space="preserve">
<value>XPath Expression</value>
</data>
<data name="Item_XPathExpressionType_CurrentNode" xml:space="preserve">
<value>From Current</value>
</data>
<data name="Item_XPathExpressionType_Global" xml:space="preserve">
<value>Global</value>
</data>
<data name="Item_XPathMatchType_Disable" xml:space="preserve">
<value>Disable</value>
</data>
<data name="Item_XPathMatchType_Enable" xml:space="preserve">
<value>Enable</value>
</data>
<data name="Lbl_ControlHeight" xml:space="preserve">
<value>Pixel height of the tree control box</value>
</data>
<data name="Lbl_MaxItemsAllowed" xml:space="preserve">
<value>Maximum node selections</value>
</data>
<data name="Lbl_MinItemsAllowed" xml:space="preserve">
<value>Minimum node selections</value>
</data>
<data name="Lbl_NodeSelectionType" xml:space="preserve">
<value>Node selection type</value>
</data>
<data name="Lbl_SelectTreeType" xml:space="preserve">
<value>Select tree type</value>
</data>
<data name="Lbl_ShowItemInfoTooltipCheckBox" xml:space="preserve">
<value>Show tooltip for selected item</value>
</data>
<data name="Lbl_ShowThumbnails" xml:space="preserve">
<value>Show thumbnails for media items?</value>
</data>
<data name="Lbl_StartNodeId" xml:space="preserve">
<value>Start node ID</value>
</data>
<data name="Lbl_StoreAsComma" xml:space="preserve">
<value>Data as CSV or XML? </value>
</data>
<data name="Lbl_XPathExpression" xml:space="preserve">
<value>XPath expression</value>
</data>
<data name="Lbl_XPathExpressionType" xml:space="preserve">
<value>XPath type</value>
</data>
<data name="Lbl_XPathFilter" xml:space="preserve">
<value>XPath filter</value>
</data>
<data name="Lbl_XPathFilterType" xml:space="preserve">
<value>XPath filter type</value>
</data>
<data name="Val_ControlHeightMsg" xml:space="preserve">
<value>Please enter a pixel value between 1 - 999</value>
</data>
<data name="Val_MaxItemsMsg" xml:space="preserve">
<value>Please enter only numbers</value>
</data>
<data name="Val_MinItemsInvalid" xml:space="preserve">
<value>The highlighted Multi Node Tree Picker property requires a minimum node selection of {0}</value>
</data>
<data name="Val_MinItemsMsg" xml:space="preserve">
<value>Please enter only numbers</value>
</data>
<data name="Val_RelativeXpath" xml:space="preserve">
<value>A relative xpath expression cannot start with "/" or "//"</value>
</data>
</root>

View File

@@ -0,0 +1,692 @@
using System;
using System.Collections.Generic;
using System.Linq;
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.cms.presentation.Trees;
using umbraco.controls.Images;
using umbraco.controls.Tree;
using umbraco.IO;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// The user interface to display to the content editor
/// </summary>
[ClientDependency( ClientDependencyType.Javascript, "ui/jqueryui.js", "UmbracoClient")]
[ClientDependency(ClientDependencyType.Javascript, "controls/Images/ImageViewer.js", "UmbracoRoot")]
public class MNTP_DataEditor : Control, INamingContainer
{
#region Static Constructor
/// <summary>
/// This adds our filtered tree definition to the TreeDefinitionCollection at runtime
/// instead of having to declare it in the database
/// </summary>
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,
"uComponents.Core",
"DataTypes.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,
"uComponents.Core",
"DataTypes.MultiNodeTreePicker.FilteredMediaTree",
contentTree.Tree.Action),
contentTree.App);
//add it to the collection at runtime
TreeDefinitionCollection.Instance.Add(filteredContentTree);
TreeDefinitionCollection.Instance.Add(filteredMediaTree);
}
}
}
}
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="MNTP_DataEditor"/> class.
/// </summary>
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
/// <summary>
/// Used for locking code blocks
/// </summary>
private static readonly object m_Locker = new object();
#endregion
#region Protected members
/// <summary>
///
/// </summary>
protected CustomValidator MinItemsValidator;
/// <summary>
///
/// </summary>
protected CustomTreeControl TreePickerControl;
/// <summary>
///
/// </summary>
protected Repeater SelectedValues;
/// <summary>
///
/// </summary>
protected HiddenField PickedValue;
/// <summary>
///
/// </summary>
protected HtmlGenericControl RightColumn;
#endregion
#region public Properties
/// <summary>
/// gets/sets the value based on an array of IDs selected
/// </summary>
public string[] SelectedIds
{
get
{
List<string> val = new List<string>();
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);
}
}
/// <summary>
/// get/set the value for the selected nodes in xml format
/// </summary>
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());
}
}
}
/// <summary>
/// The property name being edited with the current data editor. This is used for the min items validation statement.
/// </summary>
public string PropertyName { get; set; }
/// <summary>
/// The tree type alias to render
/// </summary>
public string TreeToRender { get; set; }
/// <summary>
/// An xpath filter to match nodes that will be disabled from being clicked
/// </summary>
public string XPathFilter { get; set; }
/// <summary>
/// The minimum amount of nodes that can be selected
/// </summary>
public int MinNodeCount { get; set; }
/// <summary>
/// The maximum amount of nodes that can be selected
/// </summary>
public int MaxNodeCount { get; set; }
/// <summary>
/// The start node id
/// </summary>
public int StartNodeId { get; set; }
/// <summary>
/// The start node selection type
/// </summary>
public NodeSelectionType StartNodeSelectionType { get; set; }
/// <summary>
/// The xpath expression type to select the start node when the StartNodeSelectionType is XPath
/// </summary>
public XPathExpressionType StartNodeXPathExpressionType { get; set; }
/// <summary>
/// The XPath expression to use to determine the start node when the StartNodeSelectionType is XPath
/// </summary>
public string StartNodeXPathExpression { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [show tool tips].
/// </summary>
/// <value><c>true</c> if [show tool tips]; otherwise, <c>false</c>.</value>
/// <remarks>Shows/Hides the tooltip info bubble.</remarks>
public bool ShowToolTips { get; set; }
/// <summary>
/// The XPathFilterType to match
/// </summary>
public XPathFilterType XPathFilterMatchType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [show thumbnails for media].
/// </summary>
/// <value>
/// <c>true</c> if [show thumbnails for media]; otherwise, <c>false</c>.
/// </value>
/// <remarks>Whether or not to show thumbnails for media</remarks>
public bool ShowThumbnailsForMedia { get; set; }
/// <summary>
/// A list of media type names that can have thumbnails (i.e. 'image')
/// </summary>
public string[] MediaTypesWithThumbnails { get; set; }
/// <summary>
/// This is set by the data type and allows us to save a cookie value
/// for persistence for the data type.
/// </summary>
public int DataTypeDefinitionId { get; set; }
/// <summary>
/// The height of the tree control box in pixels
/// </summary>
public int ControlHeight { get; set; }
#endregion
/// <summary>
/// Initialize the control, make sure children are created
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
EnsureChildControls();
}
/// <summary>
/// Add the resources (sytles/scripts)
/// </summary>
/// <param name="e"></param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//add the js/css required
this.AddAllMNTPClientDependencies();
//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();
}
}
/// <summary>
/// Creates the child controls for this control
/// </summary>
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);
}
/// <summary>
/// Ensure the repeater is data bound
/// </summary>
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;
}
}
/// <summary>
/// Event handler for the selected node repeater.
/// This will fill in all of the text values, icons, etc.. for nodes based on their ID.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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 = "<i>NODE NO LONGER EXISTS</i>";
}
}
}
/// <summary>
/// set the nodekey to the id of this datatype
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
TreePickerControl.NodeKey = this.DataTypeDefinitionId.ToString();
SavePersistentValuesForTree(XPathFilter);
}
/// <summary>
/// Override render to control the exact output of what is rendered this includes instantiating the jquery plugin
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
/// <remarks>
/// Generally i don't like to do this but there's a few div's, etc... to render so this makes more sense.
/// </remarks>
protected override void Render(HtmlTextWriter writer)
{
//<div class="multiTreePicker">
// <div class="header propertypane">
// <div>Select items</div>
// </div>
// <div class="left propertypane">
// <umb:tree runat="server" ID="TreePickerControl"
// CssClass="myTreePicker" Mode="Standard"
// DialogMode="id" ShowContextMenu="false"
// IsDialog="true" TreeType="content" />
// </div>
// <div class="right propertypane">
// </div>
//</div>
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) + @"/plugins/MultiNodePicker/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("<script type='text/javascript'>" + js + "</script>");
}
/// <summary>
/// converts a list of Ids to the XDocument structure
/// </summary>
/// <param name="val">The value.</param>
/// <returns></returns>
private XDocument ConvertToXDocument(IEnumerable<string> 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"));
}
}
/// <summary>
/// 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.
/// </summary>
private void RenderTooltip(HtmlTextWriter writer)
{
if (this.Page.Items.Contains("MNTPTooltip"))
{
return;
}
//render the tooltip holder
//<div class="tooltip">
// <div class="throbber"></div>
// <div class="tooltipInfo"></div>
//</div>
//this.Page.Controls.AddAt(0, new LiteralControl("<div id='MNTPTooltip'><div class='throbber'></div><div class='tooltipInfo'></div></div>"));
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xpath">The xpath.</param>
/// <remarks>
/// 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.
/// </remarks>
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);
}
/// <summary>
/// A reference path to where the icons are actually stored as compared to where the tree themes folder is
/// </summary>
private static readonly string IconPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/images/umbraco/";
}
}

View File

@@ -0,0 +1,207 @@
using System;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using umbraco.cms.businesslogic.datatype;
using umbraco.interfaces;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// Multi-node tree picker data type
/// </summary>
public class MNTP_DataType : AbstractDataEditor
{
/// <summary>
/// Initializes a new instance of the <see cref="MNTP_DataType"/> class.
/// </summary>
public MNTP_DataType()
{
RenderControl = m_Tree;
m_Tree.Init += Tree_Init;
m_Tree.Load += Tree_Load;
DataEditorControl.OnSave += DataEditorControl_OnSave;
}
private umbraco.interfaces.IData m_Data;
/// <summary>
/// The internal tree picker control to render
/// </summary>
readonly MNTP_DataEditor m_Tree = new MNTP_DataEditor();
/// <summary>
/// Internal pre value editor to render
/// </summary>
MNTP_PrevalueEditor m_PreValues;
///<summary>
///</summary>
public override Guid Id
{
get
{
return new Guid(DataTypeGuids.MultiNodeTreePickerId);
}
}
///<summary>
///</summary>
public override string DataTypeName
{
get
{
return "Multi-Node Tree Picker";
}
}
///<summary>
///</summary>
public override IData Data
{
get
{
if (this.m_Data == null)
{
m_Data = StoreAsCommaDelimited ? new umbraco.cms.businesslogic.datatype.DefaultData(this) : new XmlData(this);
}
return m_Data;
}
}
/// <summary>
/// Value indicating whether to store as comma seperated or Xml
/// </summary>
public bool StoreAsCommaDelimited
{
get
{
return ((MNTP_PrevalueEditor)PrevalueEditor).StoreAsCommaDelimited;
}
}
/// <summary>
/// Initialize the tree, here's where we can set some initial properties for the tree
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Tree_Init(object sender, EventArgs e)
{
var preVal = ((MNTP_PrevalueEditor)PrevalueEditor);
m_Tree.TreeToRender = preVal.SelectedTreeType;
m_Tree.XPathFilter = preVal.XPathFilter;
m_Tree.MaxNodeCount = preVal.MaxNodeCount;
m_Tree.ShowToolTips = preVal.ShowToolTip;
m_Tree.XPathFilterMatchType = preVal.XPathFilterMatchType;
m_Tree.StartNodeId = preVal.StartNodeId;
m_Tree.DataTypeDefinitionId = DataTypeDefinitionId;
m_Tree.ShowThumbnailsForMedia = preVal.ShowThumbnailsForMedia;
m_Tree.PropertyName = this.DataTypeName;
m_Tree.StartNodeSelectionType = preVal.StartNodeSelectionType;
m_Tree.StartNodeXPathExpression = preVal.StartNodeXPathExpression;
m_Tree.StartNodeXPathExpressionType = preVal.StartNodeXPathExpressionType;
m_Tree.ControlHeight = preVal.ControlHeight;
m_Tree.MinNodeCount = preVal.MinNodeCount;
}
/// <summary>
/// Set the data source for the editor
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Tree_Load(object sender, EventArgs e)
{
if (!m_Tree.Page.IsPostBack)
{
// set the value of the control
if (Data.Value != null && !string.IsNullOrEmpty(Data.Value.ToString()))
{
var strVal = this.Data.Value.ToString();
//check how the data is stored
if (StoreAsCommaDelimited)
{
//need to check if it was XML, if so, we'll clear the data (Error checkign)
if (!strVal.StartsWith("<"))
{
m_Tree.SelectedIds = this.Data.Value.ToString().Split(',');
}
else
{
//this must have been saved as XML before, we'll reset it but this will cause a loss of data
m_Tree.SelectedIds = new string[] { };
}
}
else
{
try
{
var xmlDoc = XDocument.Parse(this.Data.Value.ToString());
m_Tree.XmlValue = xmlDoc;
}
catch (XmlException)
{
//not a valid xml doc, must have been saved as csv before.
//set to empty val, this will cause a loss of data
m_Tree.XmlValue = null;
}
}
}
}
}
/// <summary>
/// Handle the saving event, need to give data to Umbraco
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
void DataEditorControl_OnSave(EventArgs e)
{
string val;
if (StoreAsCommaDelimited)
{
val = string.Join(",", m_Tree.SelectedIds);
}
else
{
val = m_Tree.XmlValue == null ? string.Empty : m_Tree.XmlValue.ToString();
}
Data.Value = !string.IsNullOrEmpty(val) ? val : null;
}
/// <summary>
/// return a custom pre value editor
/// </summary>
public override IDataPrevalue PrevalueEditor
{
get { return m_PreValues ?? (m_PreValues = new MNTP_PrevalueEditor(this)); }
}
internal const string PersistenceCookieName = "MultiNodeTreePicker";
/// <summary>
/// Helper method to ensure the pesistence cookie is cleared.
/// This is used on app startup and after editing the pre-value editor
/// </summary>
internal static void ClearCookiePersistence()
{
if (HttpContext.Current == null)
{
return;
}
if (HttpContext.Current.Response.Cookies[PersistenceCookieName] != null)
{
HttpContext.Current.Response.Cookies[PersistenceCookieName].Expires = DateTime.Now.AddDays(-1);
}
}
}
}

View File

@@ -0,0 +1,745 @@
using System;
using System.Collections;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
// using uComponents.Core;
// using uComponents.DataTypes.Shared.Extensions;
using umbraco.cms.businesslogic.datatype;
using umbraco.interfaces;
using umbraco.uicontrols.TreePicker;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// The pre-value editor for the multi node tree picker.
/// </summary>
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
/// <summary>
/// The chosen tree type to render
/// </summary>
public string SelectedTreeType
{
get
{
return GetPreValue(PropertyIndex.TreeType, x => x.Value, "content");
}
}
/// <summary>
/// An xpath filter to disable nodes to be selectable
/// </summary>
public string XPathFilter
{
get
{
return GetPreValue(PropertyIndex.XPathFilter, x => x.Value, string.Empty);
}
}
/// <summary>
/// The number of nodes this picker will support picking
/// </summary>
public int MaxNodeCount
{
get
{
return GetPreValue(PropertyIndex.MaxNodeCount, x =>
{
var max = -1;
return int.TryParse(x.Value, out max) ? max : -1;
}, -1);
}
}
/// <summary>
/// The minimum number of nodes this picker will support picking
/// </summary>
public int MinNodeCount
{
get
{
return GetPreValue(PropertyIndex.MinNodeCount, x =>
{
var min = 0;
return int.TryParse(x.Value, out min) ? min : 0;
}, 0);
}
}
/// <summary>
/// A boolean value indicating whether or not to show the informational tool tips
/// </summary>
public bool ShowToolTip
{
get
{
return GetPreValue(PropertyIndex.ShowToolTip, x =>
{
var show = true;
return bool.TryParse(x.Value, out show) ? show : true;
}, true);
}
}
/// <summary>
/// Value to check if the data should be stored as CSV or XML
/// </summary>
public bool StoreAsCommaDelimited
{
get
{
return GetPreValue(PropertyIndex.StoreAsCommaDelimited, x =>
{
var asComma = 0;
return int.TryParse(x.Value, out asComma) ? asComma == 1 : false;
}, false);
}
}
/// <summary>
/// The XPath expression used when the node type selection is Xpath
/// </summary>
public string StartNodeXPathExpression
{
get
{
return GetPreValue(PropertyIndex.StartNodeXPathExpression, x => x.Value, string.Empty);
}
}
/// <summary>
/// The type of xpath expression used for the xpathexpressiontext if using an xpath node selection
/// </summary>
public XPathExpressionType StartNodeXPathExpressionType
{
get
{
return GetPreValue(PropertyIndex.StartNodeXPathExpressionType,
x => (XPathExpressionType)Enum.ToObject(typeof(XPathExpressionType), int.Parse(x.Value)),
XPathExpressionType.Global);
}
}
/// <summary>
/// The type of selection type to use for the start node
/// </summary>
public NodeSelectionType StartNodeSelectionType
{
get
{
return GetPreValue(PropertyIndex.StartNodeSelectionType,
x => (NodeSelectionType)Enum.ToObject(typeof(NodeSelectionType), int.Parse(x.Value)),
NodeSelectionType.Picker);
}
}
/// <summary>
/// The type of xpath filter applied
/// </summary>
public XPathFilterType XPathFilterMatchType
{
get
{
return GetPreValue(PropertyIndex.XPathFilterType,
x => (XPathFilterType)Enum.ToObject(typeof(XPathFilterType), int.Parse(x.Value)),
XPathFilterType.Disable);
}
}
/// <summary>
/// The start node id used when the node selection is a picker
/// </summary>
public int StartNodeId
{
get
{
return GetPreValue(PropertyIndex.StartNodeId, x =>
{
var max = 0;
return int.TryParse(x.Value, out max) ? max : uQuery.RootNodeId;
}, uQuery.RootNodeId);
}
}
/// <summary>
/// A boolean value indicating whether or not to show the thumbnails for media
/// </summary>
public bool ShowThumbnailsForMedia
{
get
{
return GetPreValue(PropertyIndex.ShowThumbnails, x =>
{
var show = true;
bool.TryParse(x.Value, out show);
return show;
}, true);
}
}
///<summary>
/// Returns the control height in pixels
///</summary>
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
/// <summary>
/// The control height text box
/// </summary>
protected TextBox ControlHeightTextBox;
/// <summary>
///
/// </summary>
protected RadioButtonList StartNodeSelectionTypeRadioButtons;
/// <summary>
/// The start node id content picker
/// </summary>
protected SimpleContentPicker StartContentNodeIdPicker;
/// <summary>
/// The start node id media picker
/// </summary>
protected SimpleMediaPicker StartMediaNodeIdPicker;
/// <summary>
/// XPath expression type radio button list
/// </summary>
protected RadioButtonList StartNodeXPathExpressionTypeRadioButtons;
/// <summary>
///
/// </summary>
protected TextBox StartNodeXPathExpressionTextBox;
/// <summary>
///
/// </summary>
protected CheckBox ShowThumbnailsForMediaCheckBox;
/// <summary>
///
/// </summary>
protected DropDownList TreeTypeDropDown;
/// <summary>
///
/// </summary>
protected TextBox XPathFilterTextBox;
/// <summary>
/// Text box for maximum amount of items
/// </summary>
protected TextBox MaxItemsTextBox;
/// <summary>
/// Text box for minimum amount of items
/// </summary>
protected TextBox MinItemsTextBox;
/// <summary>
/// Minimum items validator
/// </summary>
protected RegularExpressionValidator NumbersMinItemsValidator;
/// <summary>
/// Validator for validating relative xpath expressions
/// </summary>
protected CustomValidator RelativeXpathValidator;
/// <summary>
///
/// </summary>
protected RegularExpressionValidator NumbersMaxItemsValidator;
/// <summary>
///
/// </summary>
protected RegularExpressionValidator ControlHeightValidatator;
/// <summary>
///
/// </summary>
protected CheckBox ShowItemInfoTooltipCheckBox;
/// <summary>
///
/// </summary>
protected RadioButtonList StoreAsCommaDelimitedRadioButtons;
/// <summary>
///
/// </summary>
protected RadioButtonList XPathFilterTypeRadioButtons;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="MNTP_PrevalueEditor"/> class.
/// </summary>
/// <param name="dataType">Type of the data.</param>
public MNTP_PrevalueEditor(umbraco.cms.businesslogic.datatype.BaseDataType dataType)
{
this.m_DataType = dataType;
}
/// <summary>
/// Override on init to ensure child controls
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.EnsureChildControls();
this.AddResourceToClientDependency("uComponents.DataTypes.Shared.Resources.Styles.PrevalueEditor.css", ClientDependency.Core.ClientDependencyType.Css);
}
/// <summary>
/// Ensures the css to render this control is included.
/// Binds the saved value to the drop down.
/// </summary>
/// <param name="e"></param>
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();
}
}
/// <summary>
/// Creates child controls for this control
/// </summary>
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;
}
}
/// <summary>
/// Helper method to add a server side pre value row
/// </summary>
/// <param name="lbl"></param>
/// <param name="description"></param>
/// <param name="ctl"></param>
/// <remarks>
/// Using server side syntax because of the post backs and because i don't want to manage the view state manually
/// </remarks>
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.InnerText = description;
desc.Attributes.Add("class", "description");
desc.Controls.Add(descSpan);
div.Controls.Add(desc);
}
this.Controls.Add(div);
}
/// <summary>
/// Hides/Shows controls based on the selection of other controls
/// </summary>
/// <param name="e"></param>
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;
}
}
/// <summary>
/// render our own custom markup
/// </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)
{
writer.AddAttribute(HtmlTextWriterAttribute.Class, "uComponents");
writer.RenderBeginTag(HtmlTextWriterTag.Div); //start 'uComponents'
base.Render(writer);
writer.RenderEndTag(); //end 'uComponents'
}
/// <summary>
/// Lazy loads the prevalues for this data type
/// </summary>
/// <returns></returns>
private SortedList GetPreValues()
{
if (m_PreValues == null)
{
m_PreValues = PreValues.GetPreValues(m_DataType.DataTypeDefinitionId);
}
return m_PreValues;
}
#region IDataPrevalue Members
///<summary>
/// returns this as it's own editor
///</summary>
public Control Editor
{
get { return this; }
}
/// <summary>
/// Saves data to Umbraco
/// </summary>
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();
}
/// <summary>
/// Used to determine the index number of where the property is saved in the pre values repository
/// </summary>
private enum PropertyIndex
{
TreeType,
XPathFilter,
MaxNodeCount,
ShowToolTip,
StoreAsCommaDelimited,
XPathFilterType,
StartNodeId,
ShowThumbnails,
StartNodeSelectionType,
StartNodeXPathExpressionType,
StartNodeXPathExpression,
ControlHeight,
MinNodeCount
}
/// <summary>
/// Helper method to save/create pre value values in the db
/// </summary>
/// <param name="propIndex"></param>
/// <param name="value"></param>
/// <param name="currentVals"></param>
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);
}
}
///<summary>
/// Generic method to return a strongly typed object from the pre value bucket
///</summary>
///<param name="index"></param>
///<param name="output"></param>
///<param name="defaultVal"></param>
///<typeparam name="T"></typeparam>
///<returns></returns>
private T GetPreValue<T>(PropertyIndex index, Func<PreValue, T> output, T defaultVal)
{
var vals = GetPreValues();
return vals.Count >= (int)index + 1 ? output((PreValue)vals[(int)index]) : defaultVal;
}
#endregion
}
}

View File

@@ -0,0 +1,45 @@
using System.Web.UI;
using umbraco.cms.businesslogic.datatype;
[assembly: WebResource("umbraco.editorControls.MultiNodeTreePicker.jquery.tooltip.min.js", "application/x-javascript")]
[assembly: WebResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerScripts.js", "application/x-javascript")]
[assembly: WebResource("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerStyles.css", "text/css", PerformSubstitution = true)]
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// Extension methods for this namespace
/// </summary>
public static class MultiNodeTreePickerExtensions
{
/// <summary>
/// Adds the JS/CSS required for the MultiNodeTreePicker
/// </summary>
/// <param name="ctl"></param>
public static void AddAllMNTPClientDependencies(this Control ctl)
{
//get the urls for the embedded resources
AddCssMNTPClientDependencies(ctl);
AddJsMNTPClientDependencies(ctl);
}
/// <summary>
/// Adds the CSS required for the MultiNodeTreePicker
/// </summary>
/// <param name="ctl"></param>
public static void AddCssMNTPClientDependencies(this Control ctl)
{
ctl.AddResourceToClientDependency("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerStyles.css", ClientDependencyType.Css);
}
/// <summary>
/// Adds the JS required for the MultiNodeTreePicker
/// </summary>
/// <param name="ctl"></param>
public static void AddJsMNTPClientDependencies(this Control ctl)
{
ctl.AddResourceToClientDependency("umbraco.editorControls.MultiNodeTreePicker.MultiNodePickerScripts.js", ClientDependencyType.Javascript);
ctl.AddResourceToClientDependency("umbraco.editorControls.MultiNodeTreePicker.jquery.tooltip.min.js", ClientDependencyType.Javascript);
}
}
}

View File

@@ -0,0 +1,18 @@
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
///
/// </summary>
public enum NodeSelectionType
{
/// <summary>
///
/// </summary>
Picker,
/// <summary>
///
/// </summary>
XPathExpression
}
}

View File

@@ -0,0 +1,81 @@
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using umbraco.controls.Images;
using umbraco.IO;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// The item template for the selected items repeater
/// </summary>
internal class SelectedItemsTemplate : ITemplate
{
#region ITemplate Members
/// <summary>
/// Creates the template for the repeater item
/// </summary>
/// <param name="container"></param>
public void InstantiateIn(Control container)
{
var itemDiv = new HtmlGenericControl("div");
itemDiv.ID = "Item";
itemDiv.Attributes.Add("class", "item");
var page = (Page)HttpContext.Current.CurrentHandler;
var imgPreview = (ImageViewer)page.LoadControl(
string.Concat(SystemDirectories.Umbraco, "/controls/Images/ImageViewer.ascx"));
imgPreview.ID = "ImgPreview";
imgPreview.Visible = false; //hidden by default
imgPreview.ViewerStyle = ImageViewer.Style.Basic;
itemDiv.Controls.Add(imgPreview);
var infoBtn = new HtmlAnchor();
infoBtn.ID = "InfoButton";
infoBtn.HRef = "javascript:void(0);";
infoBtn.Attributes.Add("class", "info");
itemDiv.Controls.Add(infoBtn);
var innerDiv = new HtmlGenericControl("div");
innerDiv.ID = "InnerItem";
innerDiv.Attributes.Add("class", "inner");
innerDiv.Controls.Add(
new LiteralControl(@"<ul class=""rightNode"">"));
var liSelectNode = new HtmlGenericControl("li");
liSelectNode.Attributes.Add("class", "closed");
liSelectNode.ID = "SelectedNodeListItem";
innerDiv.Controls.Add(liSelectNode);
var selectedNodeLink = new HtmlAnchor();
selectedNodeLink.ID = "SelectedNodeLink";
selectedNodeLink.Attributes.Add("class", "sprTree");
selectedNodeLink.Attributes.Add("title", "Sync tree");
innerDiv.Controls.Add(selectedNodeLink);
var selectedNodeText = new Literal();
selectedNodeText.ID = "SelectedNodeText";
innerDiv.Controls.Add(selectedNodeText);
selectedNodeLink.Controls.Add(new LiteralControl("<div>"));
selectedNodeLink.Controls.Add(selectedNodeText);
selectedNodeLink.Controls.Add(new LiteralControl("</div>"));
liSelectNode.Controls.Add(selectedNodeLink);
innerDiv.Controls.Add(
new LiteralControl(@"</ul><a class='close' title='Remove' href='javascript:void(0);'></a>"));
itemDiv.Controls.Add(innerDiv);
container.Controls.Add(itemDiv);
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// An enumerator for the XPath expression.
/// </summary>
public enum XPathExpressionType
{
/// <summary>
///
/// </summary>
Global,
/// <summary>
///
/// </summary>
FromCurrent
}
}

View File

@@ -0,0 +1,18 @@
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// An enumerator for the XPath filter, for either enable/disable.
/// </summary>
public enum XPathFilterType
{
/// <summary>
/// Disables the XPath filter.
/// </summary>
Disable,
/// <summary>
/// Enables the XPath filter.
/// </summary>
Enable
}
}

View File

@@ -0,0 +1,78 @@
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using umbraco.cms.presentation.Trees;
namespace umbraco.editorControls.MultiNodeTreePicker
{
/// <summary>
/// XmlTreeNode extensions for the MultiNodeTreePicker.
/// </summary>
public static class XmlTreeNodeExtensions
{
//public static void DetermineSelected(this XmlTreeNode node)
//{
//}
/// <summary>
/// Determines if the node should be clickable based on the xpath given
/// </summary>
/// <param name="node">The node.</param>
/// <param name="xpath">The xpath.</param>
/// <param name="type">The type.</param>
/// <param name="xml">The XML.</param>
public static void DetermineClickable(this XmlTreeNode node, string xpath, XPathFilterType type, XElement xml)
{
if (!string.IsNullOrEmpty(xpath))
{
try
{
var matched = xml.XPathSelectElements(xpath);
if (matched.Count() > 0)
{
if (type == XPathFilterType.Disable)
{
//add the non-clickable color to the node
node.Style.AddCustom("uc-treenode-noclick");
}
else
{
//add the non-clickable color to the node
node.Style.AddCustom("uc-treenode-click");
}
}
else
{
if (type == XPathFilterType.Disable)
{
//ensure the individual node is the correct color
node.Style.AddCustom("uc-treenode-click");
}
else
{
//ensure the individual node is the correct color
node.Style.AddCustom("uc-treenode-noclick");
}
}
}
catch (XPathException)
{
node.Text = "uComponents: XPath Error!";
}
}
else
{
if (type == XPathFilterType.Disable)
{
//ensure the individual node is the correct color
node.Style.AddCustom("uc-treenode-click");
}
else
{
//ensure the individual node is the correct color
node.Style.AddCustom("uc-treenode-noclick");
}
}
}
}
}