1+ /*==============================================================================================================================
2+ | Author Ignia, LLC
3+ | Client Ignia, LLC
4+ | Project Topics Library
5+ \=============================================================================================================================*/
6+ using System ;
7+ using System . Collections . Generic ;
8+ using System . Linq ;
9+ using Ignia . Topics . Attributes ;
10+ using Ignia . Topics . Internal . Diagnostics ;
11+ using Ignia . Topics . Metadata ;
12+
13+ namespace Ignia . Topics . Web . Editor {
14+
15+ /*============================================================================================================================
16+ | CLASS: CONFIGURABLE ATTRIBUTE DESCRIPTOR
17+ \---------------------------------------------------------------------------------------------------------------------------*/
18+ /// <summary>
19+ /// Represents metadata for describing an attribute, including information on how it will be presented and validated in the
20+ /// editor.
21+ /// </summary>
22+ /// <remarks>
23+ /// Extends the out-of-the-box <see cref="AttributeDescriptor"/> with a version that can be extended via a <see
24+ /// cref="DefaultConfiguration"/>. This is used by the legacy ASP.NET WebForms version of the OnTopic Editor. Newer releases
25+ /// instead use the <see cref="AttributeTypeDescriptor"/> content types, which provide configurable values via attributes.
26+ /// </remarks>
27+ public class ConfigurableAttributeDescriptor : AttributeDescriptor {
28+
29+ /*==========================================================================================================================
30+ | PRIVATE VARIABLES
31+ \-------------------------------------------------------------------------------------------------------------------------*/
32+ private Dictionary < string , string > _configuration ;
33+ private ModelType ? _modelType = null ;
34+
35+ /*==========================================================================================================================
36+ | CONSTRUCTOR
37+ \-------------------------------------------------------------------------------------------------------------------------*/
38+ /// <summary>
39+ /// Initializes a new instance of a <see cref="ConfigurableAttributeDescriptor"/> class with a <see cref="Topic.Key"/>,
40+ /// <see cref="Topic.ContentType"/>, and, optionally, <see cref="Topic.Parent"/>, <see cref="Topic.Id"/>.
41+ /// </summary>
42+ /// <remarks>
43+ /// By default, when creating new attributes, the <see cref="AttributeValue"/>s for both <see cref="Topic.Key"/> and <see
44+ /// cref="Topic.ContentType"/> will be set to <see cref="AttributeValue.IsDirty"/>, which is required in order to
45+ /// correctly save new topics to the database. When the <paramref name="id"/> parameter is set, however, the <see
46+ /// cref="AttributeValue.IsDirty"/> property is set to <c>false</c> on <see cref="Topic.Key"/> as well as on <see
47+ /// cref="Topic.ContentType"/>, since it is assumed these are being set to the same values currently used in the
48+ /// persistence store.
49+ /// </remarks>
50+ /// <param name="key">A string representing the key for the new topic instance.</param>
51+ /// <param name="contentType">A string representing the key of the target content type.</param>
52+ /// <param name="id">The unique identifier assigned by the data store for an existing topic.</param>
53+ /// <param name="parent">Optional topic to set as the new topic's parent.</param>
54+ /// <exception cref="ArgumentException">
55+ /// Thrown when the class representing the content type is found, but doesn't derive from <see cref="Topic"/>.
56+ /// </exception>
57+ /// <returns>A strongly-typed instance of the <see cref="Topic"/> class based on the target content type.</returns>
58+ public ConfigurableAttributeDescriptor (
59+ string key ,
60+ string contentType ,
61+ Topic parent ,
62+ int id = - 1
63+ ) : base (
64+ key ,
65+ contentType ,
66+ parent ,
67+ id
68+ ) {
69+ _configuration = new Dictionary < string , string > ( ) ;
70+ }
71+
72+ /*==========================================================================================================================
73+ | PROPERTY: MODEL TYPE
74+ \-------------------------------------------------------------------------------------------------------------------------*/
75+ /// <inheritdoc />
76+ public override ModelType ModelType {
77+ get {
78+
79+ /*----------------------------------------------------------------------------------------------------------------------
80+ | Return cached value
81+ \---------------------------------------------------------------------------------------------------------------------*/
82+ if ( _modelType . HasValue ) {
83+ return _modelType . Value ;
84+ }
85+
86+ /*----------------------------------------------------------------------------------------------------------------------
87+ | Determine value
88+ \---------------------------------------------------------------------------------------------------------------------*/
89+ var editorType = EditorType ;
90+
91+ Contract . Assume (
92+ editorType ,
93+ $ "The 'EditorType' property is not set for the '{ Key } ' attribute, and thus a 'ModelType' cannot be determined."
94+ ) ;
95+
96+ if ( editorType . LastIndexOf ( "." , StringComparison . InvariantCulture ) >= 0 ) {
97+ editorType = editorType . Substring ( 0 , editorType . LastIndexOf ( "." , StringComparison . InvariantCulture ) ) ;
98+ }
99+
100+ if ( new [ ] { "Relationships" , "TokenizedTopicList" } . Contains ( editorType ) ) {
101+ _modelType = ModelType . Relationship ;
102+ }
103+ else if (
104+ new [ ] { "TopicPointer" } . Contains ( editorType ) ||
105+ Key . EndsWith ( "Id" , StringComparison . InvariantCultureIgnoreCase ) &&
106+ ! Key . Equals ( "Id" , StringComparison . InvariantCultureIgnoreCase )
107+ ) {
108+ _modelType = ModelType . Reference ;
109+ }
110+ else if ( new [ ] { "TopicList" } . Contains ( editorType ) ) {
111+ _modelType = ModelType . NestedTopic ;
112+ }
113+ else {
114+ _modelType = ModelType . ScalarValue ;
115+ }
116+
117+ /*----------------------------------------------------------------------------------------------------------------------
118+ | Return value
119+ \---------------------------------------------------------------------------------------------------------------------*/
120+ return _modelType . Value ;
121+
122+ }
123+ }
124+
125+ /*==========================================================================================================================
126+ | PROPERTY: EDITOR TYPE
127+ \-------------------------------------------------------------------------------------------------------------------------*/
128+ /// <inheritdoc />
129+ public override string EditorType => Attributes . GetValue ( "Type" , "" ) ;
130+
131+ /*==========================================================================================================================
132+ | PROPERTY: DEFAULT CONFIGURATION
133+ \-------------------------------------------------------------------------------------------------------------------------*/
134+ /// <summary>
135+ /// Gets or sets the default configuration.
136+ /// </summary>
137+ /// <remarks>
138+ /// <para>
139+ /// When an attribute is bound to an attribute type control in the editor, the default configuration is injected into
140+ /// the control's configuration. This allows attribute type-specific properties to be set on a per-attribute basis.
141+ /// </para>
142+ /// <para>
143+ /// Properties available for configuration are up to the control associated with the <see cref="EditorType"/>, and the
144+ /// format will be dependent on the framework in which the attribute type control is written. For example, for ASP.NET
145+ /// User Controls as well as AngularJS Directives, the format is Property1="Value" Propert2="Value".
146+ /// </para>
147+ /// </remarks>
148+ public string DefaultConfiguration {
149+ get => Attributes . GetValue ( "DefaultConfiguration" , "" ) ?? "" ;
150+ set {
151+ SetAttributeValue ( "DefaultConfiguration" , value ) ;
152+ _configuration . Clear ( ) ;
153+ }
154+ }
155+
156+ /*==========================================================================================================================
157+ | PROPERTY: CONFIGURATION
158+ \-------------------------------------------------------------------------------------------------------------------------*/
159+ /// <summary>
160+ /// Retrieves a dictionary representing a parsed collection of key/value pairs from the
161+ /// <see cref="DefaultConfiguration"/>.
162+ /// </summary>
163+ public IDictionary < string , string > Configuration {
164+ get {
165+ if ( _configuration . Count . Equals ( 0 ) && DefaultConfiguration . Length > 0 ) {
166+ _configuration = DefaultConfiguration
167+ . Split ( ' ' )
168+ . Select ( value => value . Split ( '=' ) )
169+ . ToDictionary (
170+ pair => pair [ 0 ] ,
171+ pair => pair . Count ( ) . Equals ( 2 ) ? pair [ 1 ] ? . Replace ( "\" " , "" ) : null
172+ ) ;
173+ }
174+ return _configuration ;
175+ }
176+ }
177+
178+ /*==========================================================================================================================
179+ | PROPERTY: GET CONFIGURATION VALUE
180+ \-------------------------------------------------------------------------------------------------------------------------*/
181+ /// <summary>
182+ /// Retrieves a configuration value from the <see cref="Configuration"/> dictionary; if the value doesn't exist, then
183+ /// optionally returns a default value.
184+ /// </summary>
185+ public string GetConfigurationValue ( string key , string defaultValue = null ) {
186+ Contract . Requires ( ! String . IsNullOrWhiteSpace ( key ) ) ;
187+ if ( Configuration . ContainsKey ( key ) && Configuration [ key ] != null ) {
188+ return Configuration [ key ] ;
189+ }
190+ return defaultValue ;
191+ }
192+
193+ /*==========================================================================================================================
194+ | PROPERTY: STORE IN BLOB?
195+ \-------------------------------------------------------------------------------------------------------------------------*/
196+ /// <summary>
197+ /// Gets or sets whether or not the attribute is stored as part of the attributes XML.
198+ /// </summary>
199+ /// <remarks>
200+ /// <para>
201+ /// By default, all attributes are stored in the blob, which means they may not be loaded initially, and not accessible
202+ /// to in-memory queries. This is more efficient to store, and is required for larger values.
203+ /// <para>
204+ /// Attributes that are needed to provide indexes, sitemaps, navigation, etc. should be indexed so that they're always
205+ /// available in memory without requiring an additional database query. These increase the memory requirements of the
206+ /// application, but reduce the number of database round-trips required for topics that are accessed outside of a single
207+ /// page. For instance, the title and description of a topic may be cross-referenced on other pages or as part of the
208+ /// navigation, and should thus be indexed. Indexed attributes are those not stored as a blob.
209+ /// </para>
210+ /// <para>
211+ /// This property and its corresponding attribute was named <c>StoreInBlob</c> in versions of OnTopic prior to 4.0.
212+ /// </para>
213+ /// </remarks>
214+ [ AttributeSetter ]
215+ public virtual bool StoreInBlob {
216+ get => Attributes . GetBoolean ( "StoreInBlob" , true ) ;
217+ set => SetAttributeValue ( "StoreInBlob" , value ? "1" : "0" ) ;
218+ }
219+
220+ } //Class
221+ } //Namespace
0 commit comments