Compare commits

..

72 Commits

Author SHA1 Message Date
kgiszewski 5c95f59219 Bump version number 2015-08-19 09:04:41 -04:00
kgiszewski 01b59f28f1 Merge branch 'pr/287' into rc/ui-1 2015-08-19 08:33:54 -04:00
Mark f40e023420 Changed the way tags are stripped from tinymce content
See stackoverflow for a reference
2015-08-19 13:02:55 +02:00
Kenn Jacobsen 6d9c32e30d Localization 2015-08-19 12:40:44 +02:00
Kenn Jacobsen 2f48e9da9a Merge branch 'master' into ui-overhaul 2015-08-19 12:19:54 +02:00
Kenn Jacobsen 2aa71e48a3 Prettify the "default add" button a little 2015-08-19 11:57:39 +02:00
Kenn Jacobsen e6732d2a37 Close fieldtype picker when clicked outside it 2015-08-19 11:57:13 +02:00
Kenn Jacobsen a7dca93781 Moved some CSS hover rules to a more appropriate target 2015-08-19 07:35:41 +02:00
kjac 0ec0e213a2 Make the delete icon red on hover 2015-08-18 23:11:44 +02:00
kjac 6919c5a8b4 A bit more styling of carets + make empty headers clickable 2015-08-18 23:09:40 +02:00
kjac 6505323297 Removed obsolete dropdown toggles 2015-08-18 19:03:36 +02:00
kgiszewski ec8df1d465 Housekeeping 2015-08-18 13:03:11 -04:00
kjac 451f2773a4 Add fieldtype picker
...and removed old dropdowns. Plus a bit more styling.
2015-08-18 17:59:20 +02:00
Kenn Jacobsen f366305583 Overhaul WIP 2015-08-18 15:10:37 +02:00
kgiszewski 43a72ed5e5 Use relative paths instead of tilde due to core compatibility issues 2015-08-17 12:16:38 -04:00
kgiszewski 709198f943 Merge branch 'master' of github.com:imulus/Archetype 2015-08-17 11:53:44 -04:00
Kevin Giszewski 77f72ed72e Merge pull request #283 from kipusoep/master
Fixes for Virtual Directories #156
2015-08-17 11:47:52 -04:00
kgiszewski a03fe3af32 Remove some cruft 2015-08-17 11:47:08 -04:00
Stefan Kip 697a797c7e Fixes for Virtual Directories #156 2015-08-14 14:49:41 +02:00
kgiszewski cfe014ee45 Bump version number 2015-07-08 13:26:06 -04:00
kgiszewski d62fa05ab5 Handle 7.2.6 issue 2015-07-01 09:25:18 -04:00
kjac 662cf4c3bf More formatting 2015-06-26 14:17:52 +02:00
kjac d0b8314134 Formatting issue 2015-06-26 14:09:12 +02:00
kjac 8eb4786976 Revert API method GetByGuid() to its previous state
All default prevalue enrichment is now handled by the API method
GetAllPropertyEditors()
2015-06-26 14:08:00 +02:00
kjac 20155b5ca6 Move filenames collection to temporary editor state container 2015-06-26 11:30:51 +02:00
kjac 16c310f3c6 Ensure correct default prevalues for imagecropper 2015-06-26 11:27:54 +02:00
kjac 99d570dc97 Handle upload and image cropper clientside 2015-06-25 07:59:16 +02:00
kjac 2ae3e3ade9 Merge with imulus/master 2015-06-23 17:21:26 +02:00
Kevin Giszewski d4718e358c Merge pull request #279 from leekelleher/enhancement/helper-log-exception
ArchetypeHelper - added exception to Umbraco log
2015-06-18 08:05:10 -04:00
Kevin Giszewski bb91eb7036 Merge pull request #280 from leekelleher/upgrade/courier-assemblies
Upgraded Courier assemblies.
2015-06-18 08:03:58 -04:00
leekelleher 484f2c22a3 Added exception to Umbraco log, as VS was complaining about an unused ex. 2015-06-18 12:20:02 +01:00
leekelleher a2121cd8a7 Upgraded Courier assemblies.
Amended references to Courier constants (seems that there was some restructuring within Courier)
2015-06-18 12:17:44 +01:00
kgiszewski 2fb922fd6c Rework dropdown label to check for a value first 2015-06-15 15:56:45 -04:00
kgiszewski c2986d39a9 Added coreDropdown to label helpers 2015-06-15 15:46:51 -04:00
kgiszewski 8ee5488261 Merge branch 'master' of github.com:imulus/Archetype 2015-06-15 14:49:19 -04:00
kgiszewski c60c9e1c5d Fix #276 2015-06-15 14:48:50 -04:00
kgiszewski 8610f69c88 Bump version to v1.8 2015-06-04 13:26:19 -04:00
kgiszewski 34b4209de7 For some reason sorting the RTE reared its head again and I killed it (again). 2015-06-02 10:49:47 -04:00
Kevin Giszewski b6145c9275 Merge pull request #275 from imulus/feature/label-library
Allow args to be passed to label templates and begin label library
2015-06-01 13:54:43 -04:00
kgiszewski f51ca65659 Expose archetypePropertyEditorResource to controller 2015-06-01 13:53:18 -04:00
kgiszewski d026af9fd2 Expose archetypeCacheService on controller 2015-06-01 13:49:34 -04:00
kgiszewski 11fff5da1a Add media picker support 2015-06-01 13:43:42 -04:00
kgiszewski 788a5dd720 Wire up MNTP 2015-06-01 13:35:53 -04:00
kgiszewski 24e232b2cc Renaming binge 2015-06-01 12:12:33 -04:00
kgiszewski 5bdcb23933 Cache datatypes as they come back from server 2015-06-01 11:16:12 -04:00
kgiszewski 34b3ed3b47 Add archetypeCacheService 2015-06-01 10:13:29 -04:00
kgiszewski 50a486a9ac Discovered we should probably use a datatype cache based on the directive, so maybe a archetypeCacheService? 2015-06-01 10:09:22 -04:00
kgiszewski 4f4128a744 Fix some broken-ness 2015-06-01 10:02:32 -04:00
kgiszewski 4851797a83 Refactor a ton of things, add archetypeLabelService 2015-06-01 09:45:10 -04:00
kgiszewski 54a435dce7 Begin archetypeService 2015-05-29 08:55:08 -04:00
kgiszewski 03a170d716 Add RTE label template 2015-05-27 13:56:33 -04:00
kgiszewski 37976abc84 Handle arg detection better and change UrlPicker functionname 2015-05-27 13:47:23 -04:00
kgiszewski f7e96438be Handle null ref 2015-05-27 13:41:58 -04:00
kgiszewski f1c3caae2d Allow args to be passed to label templates and begin label library 2015-05-27 13:31:25 -04:00
kgiszewski 8f8078e12f Merge branch 'master' into pr/272 2015-05-26 08:33:34 -04:00
kgiszewski dbc9b872d8 Fix #274 2015-05-26 08:28:32 -04:00
Nicholas-Westby eef1b94586 Hide long fieldset headers with an ellipsis before overlapping buttons: https://github.com/imulus/Archetype/issues/271 2015-05-23 00:01:25 -07:00
kgiszewski 9bbf366fe2 Bump version to v1.7.5 2015-05-11 15:32:46 -04:00
kgiszewski 368a9f6c07 Fix for #268 2015-05-11 15:31:46 -04:00
kgiszewski d3b3f80f3d Add Umbraco as a dependency 2015-05-11 15:20:28 -04:00
kgiszewski 11f7881dd7 Merge branch 'master' of github.com:imulus/Archetype 2015-05-04 13:46:07 -04:00
kgiszewski 9894357469 Fix #268 (This is a temp fix until a better solution can be found) 2015-05-04 13:45:49 -04:00
kjac 1cbb7b3173 Merge with master 2014-06-16 16:57:51 +02:00
kjac 71be453ada Move property files collection clean-up
Moved the cleaning up of files selected for each property to the
SerializeForPersistence() method
2014-06-01 07:48:47 +02:00
kjac efae05b3ae Merge branch 'master' into upload-imagecrop-datatypes 2014-05-29 11:17:11 +02:00
kjac 84acd43ccf Enrich datatypes with default prevalues
Datatypes can have default prevalues, that are not saved as part of the
config and thus need to be added when loading the datatype - e.g. the
"focalPoint" prevalue for the image cropper datatype.
2014-05-29 11:14:45 +02:00
kjac 9fe756bacb Merge branch 'master' into upload-imagecrop-datatypes 2014-05-21 23:01:44 +02:00
kjac cd03b5a4f9 Test and fine tune 3rd level nested archetypes 2014-05-20 22:19:21 +02:00
kjac 07a62d7edf More file upload handling WIP 2014-05-20 21:28:29 +02:00
kjac a6c166d926 WIP handle file selection
Use FileManager to register the files selected for properties contained
within an Archetype
2014-05-18 18:19:20 +02:00
kjac c1bc555443 Merge branch 'master' into current-value-to-value-editor 2014-05-16 13:44:46 +02:00
kjac d4166f33e8 Correct currentValue for ValueEditor conversion
Ensure that the currentValue parameter sent to the fieldset property
ValueEditor ConvertEditorToDb() method contains the value that was
previously entered (if any) and not the one that was just entered.
2014-05-14 11:32:54 +02:00
51 changed files with 1188 additions and 508 deletions
+8 -4
View File
@@ -57,10 +57,14 @@ module.exports = function(grunt) {
'app/directives/archetypeproperty.js',
'app/directives/archetypesubmitwatcher.js',
'app/directives/archetypecustomview.js',
'app/directives/localize.js',
'app/services/localization.js',
'app/helpers/labelhelpers.js',
'app/resources/propertyeditor.js'
'app/directives/archetypeLocalize.js',
'app/directives/archetypeclickoutside.js',
'app/services/archetypeLocalizationService.js',
'app/helpers/sampleLabelHelpers.js',
'app/resources/archetypePropertyEditorResource.js',
'app/services/archetypeService.js',
'app/services/archetypeLabelService.js',
'app/services/archetypeCacheService.js'
],
dest: '<%= basePath %>/js/archetype.js'
}
@@ -62,7 +62,7 @@ namespace Archetype.Courier.DataResolvers
{
foreach (var property in config.Fieldsets.SelectMany(x => x.Properties))
{
item.Dependencies.Add(property.DataTypeGuid.ToString(), ProviderIDCollection.dataTypeItemProviderGuid);
item.Dependencies.Add(property.DataTypeGuid.ToString(), ItemProviderIds.dataTypeItemProviderGuid);
}
item.Prevalues[0].Value = JsonConvert.SerializeObject(config, Formatting.None);
@@ -76,7 +76,7 @@ namespace Archetype.Courier.DataResolvers
if (propertyData != null && propertyData.Value != null)
{
var dataType = ExecutionContext.DatabasePersistence.RetrieveItem<DataType>(new ItemIdentifier(propertyData.DataType.ToString(),
ProviderIDCollection.dataTypeItemProviderGuid));
ItemProviderIds.dataTypeItemProviderGuid));
//Fetch the Prevalues for the current Property's DataType (if its an 'Archetype config')
var prevalue = dataType.Prevalues.FirstOrDefault(x => x.Alias.ToLowerInvariant().Equals("archetypeconfig"));
@@ -94,7 +94,7 @@ namespace Archetype.Courier.DataResolvers
if (archetype != null)
{
// get the `PropertyItemProvider` from the collection.
var propertyItemProvider = ItemProviderCollection.Instance.GetProvider(ProviderIDCollection.propertyDataItemProviderGuid, ExecutionContext);
var propertyItemProvider = ItemProviderCollection.Instance.GetProvider(ItemProviderIds.propertyDataItemProviderGuid, ExecutionContext);
foreach (var property in archetype.Fieldsets.SelectMany(x => x.Properties))
{
@@ -111,7 +111,7 @@ namespace Archetype.Courier.DataResolvers
new ContentProperty
{
Alias = property.Alias,
DataType = ExecutionContext.DatabasePersistence.GetUniqueId(property.DataTypeId, NodeObjectTypes.DataType),
DataType = ExecutionContext.DatabasePersistence.GetUniqueId(property.DataTypeId, UmbracoNodeObjectTypeIds.DataType),
PropertyEditorAlias = property.PropertyEditorAlias,
Value = property.Value
}
@@ -157,7 +157,7 @@ namespace Archetype.Courier.DataResolvers
// (if packaging) add a dependency for the property's data-type
if (direction == Direction.Packaging)
item.Dependencies.Add(firstDataType.DataType.ToString(), ProviderIDCollection.dataTypeItemProviderGuid);
item.Dependencies.Add(firstDataType.DataType.ToString(), ItemProviderIds.dataTypeItemProviderGuid);
}
}
}
@@ -179,7 +179,7 @@ namespace Archetype.Courier.DataResolvers
{
var dataType = ExecutionContext.DatabasePersistence.RetrieveItem<DataType>(
new ItemIdentifier(property.DataTypeGuid.ToString(),
ProviderIDCollection.dataTypeItemProviderGuid));
ItemProviderIds.dataTypeItemProviderGuid));
if (dataType == null)
continue;
@@ -203,7 +203,7 @@ namespace Archetype.Courier.DataResolvers
{
propertyInst.DataTypeGuid = property.DataTypeGuid.ToString();
propertyInst.DataTypeId = ExecutionContext.DatabasePersistence.GetNodeId(
property.DataTypeGuid, NodeObjectTypes.DataType);
property.DataTypeGuid, UmbracoNodeObjectTypeIds.DataType);
propertyInst.PropertyEditorAlias = property.PropertyEditorAlias;
}
}
@@ -8,6 +8,9 @@ using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.Editors;
using Umbraco.Core.PropertyEditors;
using Archetype.Extensions;
using Newtonsoft.Json;
namespace Archetype.Api
{
@@ -19,7 +22,7 @@ namespace Archetype.Api
{
return
global::Umbraco.Core.PropertyEditors.PropertyEditorResolver.Current.PropertyEditors
.Select(x => new {defaultPreValues = x.DefaultPreValues, alias = x.Alias, view = x.ValueEditor.View});
.Select(x => new {defaultPreValues = x.DefaultPreValuesForArchetype(), alias = x.Alias, view = x.ValueEditor.View});
}
public object GetAll()
@@ -35,6 +38,7 @@ namespace Archetype.Api
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var dataTypeDisplay = Mapper.Map<IDataTypeDefinition, DataTypeDisplay>(dataType);
return new { selectedEditor = dataTypeDisplay.SelectedEditor, preValues = dataTypeDisplay.PreValues };
}
@@ -234,6 +234,7 @@
<Compile Include="Extensions\ArchetypeHelper.cs" />
<Compile Include="Extensions\HtmlHelperExtensions.cs" />
<Compile Include="Extensions\ArchetypePropertyModelExtensions.cs" />
<Compile Include="Extensions\PropertyEditorExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Models\ArchetypeModel.cs" />
<Compile Include="Models\ArchetypePreValue.cs" />
@@ -3,6 +3,7 @@ using System.Linq;
using Archetype.Models;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
@@ -42,6 +43,7 @@ namespace Archetype.Extensions
}
catch (Exception ex)
{
LogHelper.Error<ArchetypeHelper>("DeserializeJsonToArchetype", ex);
}
return archetype;
@@ -66,6 +68,7 @@ namespace Archetype.Extensions
}
catch (Exception ex)
{
LogHelper.Error<ArchetypeHelper>("DeserializeJsonToArchetype", ex);
}
return archetype;
@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core.PropertyEditors;
namespace Archetype.Extensions
{
public static class PropertyEditorExtensions
{
/// <summary>
/// Formats the default prevalues for a property editor for use within an Archetype clientside context
/// </summary>
/// <param name="propertyEditor">The property editor</param>
/// <returns>The formatted default prevalues</returns>
public static IDictionary<string, object> DefaultPreValuesForArchetype(this PropertyEditor propertyEditor)
{
if (propertyEditor.DefaultPreValues == null || propertyEditor.DefaultPreValues.Any() == false)
{
return propertyEditor.DefaultPreValues;
}
var view = propertyEditor.ValueEditor.View.ToLowerInvariant();
// This is the extension point for default prevalues formatting, in case we need to handle any other
// property editors later on. It should be replaced with a switch statement by then, or maybe some fancy
// auto discovery of formatters :)
if (view == "imagecropper")
{
propertyEditor.FormatImageCropperDefaultPreValuesForArchetype();
}
return propertyEditor.DefaultPreValues;
}
/// <summary>
/// Format the default prevalues of the image cropper property editor
/// </summary>
/// <param name="propertyEditor"></param>
/// <remarks>
/// In order for the image cropper to work clientside, we need to make sure it's default prevalue "focalpoint" is returned
/// as a JSON object and not as the string it's defined as on the image cropper property editor.
/// </remarks>
private static void FormatImageCropperDefaultPreValuesForArchetype(this PropertyEditor propertyEditor)
{
const string focalPointKey = "focalPoint";
if (propertyEditor.DefaultPreValues.ContainsKey(focalPointKey) == false || propertyEditor.DefaultPreValues[focalPointKey] == null)
{
return;
}
var focalPoint = propertyEditor.DefaultPreValues[focalPointKey].ToString();
if (string.IsNullOrEmpty(focalPoint))
{
return;
}
// translate the JSON string to a JSON object
propertyEditor.DefaultPreValues[focalPointKey] = JsonConvert.DeserializeObject(focalPoint);
}
}
}
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core;
using System;
namespace Archetype.Models
{
@@ -16,6 +17,9 @@ namespace Archetype.Models
[JsonProperty("properties")]
public IEnumerable<ArchetypePropertyModel> Properties;
[JsonProperty("id")]
public Guid Id { get; set; }
public ArchetypeFieldsetModel()
{
Properties = new List<ArchetypePropertyModel>();
@@ -84,4 +88,4 @@ namespace Archetype.Models
#endregion
}
}
}
@@ -1,7 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -30,15 +30,22 @@ namespace Archetype.Models
public string SerializeForPersistence()
{
// clear the editor state before serializing (it's temporary state data)
foreach(var property in Fieldsets.SelectMany(f => f.Properties.Where(p => p.EditorState != null)).ToList())
{
property.EditorState = null;
}
var json = JObject.Parse(JsonConvert.SerializeObject(this, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }));
var propertiesToRemove = new String[] { "propertyEditorAlias", "dataTypeId", "dataTypeGuid", "hostContentType" };
json.Descendants().OfType<JProperty>()
.Where(p => propertiesToRemove.Contains(p.Name))
.ToList()
.ForEach(x => x.Remove());
return json.ToString(Formatting.None);
.ForEach(x => x.Remove());
return json.ToString(Formatting.None);
}
}
}
@@ -26,12 +26,15 @@ namespace Archetype.Models
[JsonProperty("dataTypeGuid")]
internal string DataTypeGuid { get; set; }
// container for temporary editor state from the Umbraco backend
[JsonProperty("editorState")]
internal UmbracoEditorState EditorState { get; set; }
[JsonProperty("hostContentType")]
internal PublishedContentType HostContentType { get; set; }
public T GetValue<T>()
{
// Try Umbraco's PropertyValueConverters
var converters = UmbracoContext.Current != null ? PropertyValueConvertersResolver.Current.Converters : Enumerable.Empty<IPropertyValueConverter>();
if (!string.IsNullOrWhiteSpace(this.PropertyEditorAlias) && converters.Any())
@@ -88,5 +91,12 @@ namespace Archetype.Models
return Attempt<T>.Fail();
}
internal class UmbracoEditorState
{
// container for the names of any files selected for a property in the Umbraco backend
[JsonProperty("fileNames")]
public IEnumerable<string> FileNames;
}
}
}
@@ -1,4 +1,4 @@
using System.Reflection;
[assembly: AssemblyVersion("1.7.4")]
[assembly: AssemblyFileVersion("1.7.4")]
[assembly: AssemblyVersion("1.10")]
[assembly: AssemblyFileVersion("1.10-ui-rc1")]
@@ -1,155 +1,191 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Archetype.Extensions;
using ClientDependency.Core;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.PropertyEditors;
namespace Archetype.PropertyEditors
{
[PropertyEditorAsset(ClientDependencyType.Javascript, "/App_Plugins/Archetype/js/archetype.js")]
[PropertyEditor(Constants.PropertyEditorAlias, "Archetype", "/App_Plugins/Archetype/views/archetype.html", ValueType = "JSON")]
public class ArchetypePropertyEditor : PropertyEditor
{
#region Pre Value Editor
protected override PreValueEditor CreatePreValueEditor()
{
return new ArchetypePreValueEditor();
}
internal class ArchetypePreValueEditor : PreValueEditor
{
[PreValueField("archetypeConfig", "Config", "/App_Plugins/Archetype/views/archetype.config.html",
Description = "(Required) Describe your Archetype.")]
public string Config { get; set; }
[PreValueField("hideLabel", "Hide Label", "boolean",
Description = "Hide the Umbraco property title and description, making the Archetype span the entire page width")]
public bool HideLabel { get; set; }
}
#endregion
#region Value Editor
protected override PropertyValueEditor CreateValueEditor()
{
return new ArchetypePropertyValueEditor(base.CreateValueEditor());
}
internal class ArchetypePropertyValueEditor : PropertyValueEditorWrapper
{
protected JsonSerializerSettings _jsonSettings;
public ArchetypePropertyValueEditor(PropertyValueEditor wrapped)
: base(wrapped) { }
public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
{
if (property.Value == null || property.Value.ToString() == "")
return string.Empty;
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
foreach (var fieldset in archetype.Fieldsets)
{
foreach (var propDef in fieldset.Properties.Where(p => p.DataTypeGuid != null))
{
try
{
if(propDef == null || propDef.DataTypeGuid == null) continue;
var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
var propType = new PropertyType(dtd) { Alias = propDef.Alias };
var prop = new Property(propType, propDef.Value);
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
propDef.Value = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService);
}
catch (Exception ex)
{
LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
}
}
}
return archetype.SerializeForPersistence();
}
public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
{
if (property.Value == null || property.Value.ToString() == "")
return string.Empty;;
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
foreach (var fieldset in archetype.Fieldsets)
{
foreach (var propDef in fieldset.Properties.Where(p => p.DataTypeGuid != null))
{
try
{
var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
var propType = new PropertyType(dtd) { Alias = propDef.Alias };
var prop = new Property(propType, propDef.Value);
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
propDef.Value = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, dataTypeService);
}
catch (Exception ex)
{
LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
}
}
}
return archetype;
}
public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
{
if (editorValue.Value == null || editorValue.Value.ToString() == "")
return string.Empty;
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(editorValue.Value.ToString(), editorValue.PreValues);
foreach (var fieldset in archetype.Fieldsets)
{
foreach (var propDef in fieldset.Properties.Where(p => p.DataTypeGuid != null))
{
try
{
var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id);
var propData = new ContentPropertyData(propDef.Value, preValues, new Dictionary<string, object>());
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
propDef.Value = propEditor.ValueEditor.ConvertEditorToDb(propData, propDef.Value);
}
catch (Exception ex)
{
LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
}
}
}
return archetype.SerializeForPersistence();
}
internal virtual PropertyEditor GetPropertyEditor(IDataTypeDefinition dtd)
{
if (dtd.Id != 0)
return PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
return dtd.PropertyEditorAlias.Equals(Constants.PropertyEditorAlias)
? new ArchetypePropertyEditor()
: (PropertyEditor)new TextboxPropertyEditor();
}
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Archetype.Extensions;
using ClientDependency.Core;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.Models.ContentEditing;
namespace Archetype.PropertyEditors
{
[PropertyEditorAsset(ClientDependencyType.Javascript, "../App_Plugins/Archetype/js/archetype.js")]
[PropertyEditor(Constants.PropertyEditorAlias, "Archetype", "../App_Plugins/Archetype/views/archetype.html", ValueType = "JSON")]
public class ArchetypePropertyEditor : PropertyEditor
{
#region Pre Value Editor
protected override PreValueEditor CreatePreValueEditor()
{
return new ArchetypePreValueEditor();
}
internal class ArchetypePreValueEditor : PreValueEditor
{
[PreValueField("archetypeConfig", "Config", "../App_Plugins/Archetype/views/archetype.config.html",
Description = "(Required) Describe your Archetype.")]
public string Config { get; set; }
[PreValueField("hideLabel", "Hide Label", "boolean",
Description = "Hide the Umbraco property title and description, making the Archetype span the entire page width")]
public bool HideLabel { get; set; }
}
#endregion
#region Value Editor
protected override PropertyValueEditor CreateValueEditor()
{
return new ArchetypePropertyValueEditor(base.CreateValueEditor());
}
internal class ArchetypePropertyValueEditor : PropertyValueEditorWrapper
{
protected JsonSerializerSettings _jsonSettings;
public ArchetypePropertyValueEditor(PropertyValueEditor wrapped)
: base(wrapped)
{
}
public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
{
if(property.Value == null || property.Value.ToString() == "")
return string.Empty;
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
foreach (var fieldset in archetype.Fieldsets)
{
foreach (var propDef in fieldset.Properties.Where(p => p.DataTypeGuid != null))
{
try
{
if(propDef == null || propDef.DataTypeGuid == null) continue;
var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
var propType = new PropertyType(dtd) {Alias = propDef.Alias};
var prop = new Property(propType, propDef.Value);
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
propDef.Value = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService);
}
catch (Exception ex)
{
LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
}
}
}
return archetype.SerializeForPersistence();
}
public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
{
if(property.Value == null || property.Value.ToString() == "")
return string.Empty;
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(property.Value.ToString(), propertyType.DataTypeDefinitionId);
foreach (var fieldset in archetype.Fieldsets)
{
foreach (var propDef in fieldset.Properties.Where(p => p.DataTypeGuid != null))
{
try
{
var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
var propType = new PropertyType(dtd) {Alias = propDef.Alias};
var prop = new Property(propType, propDef.Value);
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
propDef.Value = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, dataTypeService);
}
catch (Exception ex)
{
LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
}
}
}
return archetype;
}
public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
{
if(editorValue.Value == null || editorValue.Value.ToString() == "")
return string.Empty;
// attempt to deserialize the current property value as an Archetype
var currentArchetype = currentValue != null ? ArchetypeHelper.Instance.DeserializeJsonToArchetype(currentValue.ToString(), editorValue.PreValues) : null;
var archetype = ArchetypeHelper.Instance.DeserializeJsonToArchetype(editorValue.Value.ToString(), editorValue.PreValues);
// get all files uploaded via the file manager (if any)
var uploadedFiles = editorValue.AdditionalData.ContainsKey("files") ? editorValue.AdditionalData["files"] as IEnumerable<ContentItemFile> : null;
foreach (var fieldset in archetype.Fieldsets)
{
// assign an id to the fieldset if it has none (e.g. newly created fieldset)
fieldset.Id = fieldset.Id == Guid.Empty ? Guid.NewGuid() : fieldset.Id;
// find the corresponding fieldset in the current Archetype value (if any)
var currentFieldset = currentArchetype != null ? currentArchetype.Fieldsets.FirstOrDefault(f => f.Id == fieldset.Id) : null;
foreach (var propDef in fieldset.Properties)
{
try
{
// find the corresponding property in the current Archetype value (if any)
var currentProperty = currentFieldset != null ? currentFieldset.Properties.FirstOrDefault(p => p.Alias == propDef.Alias) : null;
var dtd = ArchetypeHelper.Instance.GetDataTypeByGuid(Guid.Parse(propDef.DataTypeGuid));
var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id);
var additionalData = new Dictionary<string, object>();
// figure out if we need to pass a files collection in the additional data to the property value editor
if(uploadedFiles != null)
{
if(dtd.PropertyEditorAlias == Constants.PropertyEditorAlias)
{
// it's a nested Archetype - just pass all uploaded files to the value editor
additionalData["files"] = uploadedFiles.ToList();
}
else if (propDef.EditorState != null && propDef.EditorState.FileNames != null && propDef.EditorState.FileNames.Any())
{
// pass the uploaded files that belongs to this property (if any) to the value editor
var propertyFiles = propDef.EditorState.FileNames.Select(f => uploadedFiles.FirstOrDefault(u => u.FileName == f)).Where(f => f != null).ToList();
if(propertyFiles.Any())
{
additionalData["files"] = propertyFiles;
}
}
}
var propData = new ContentPropertyData(propDef.Value, preValues, additionalData);
var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
// make sure to send the current property value (if any) to the PE ValueEditor
propDef.Value = propEditor.ValueEditor.ConvertEditorToDb(propData, currentProperty != null ? currentProperty.Value : null);
}
catch (Exception ex)
{
LogHelper.Error<ArchetypePropertyValueEditor>(ex.Message, ex);
}
}
}
return archetype.SerializeForPersistence();
}
internal virtual PropertyEditor GetPropertyEditor(IDataTypeDefinition dtd)
{
if(dtd.Id != 0)
return PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias);
return dtd.PropertyEditorAlias.Equals(Constants.PropertyEditorAlias)
? new ArchetypePropertyEditor()
: (PropertyEditor) new TextboxPropertyEditor();
}
}
#endregion
}
}
+2 -2
View File
@@ -104,7 +104,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio
//ini the properties
_.each($scope.archetypeConfigRenderModel.fieldsets, function(fieldset){
$scope.focusProperty(fieldset.properties);
$scope.focusProperty(fieldset.properties);
});
//setup JSON.stringify helpers
@@ -290,5 +290,5 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio
}
//archetype css
assetsService.loadCss("/App_Plugins/Archetype/css/archetype.css");
assetsService.loadCss("../App_Plugins/Archetype/css/archetype.css");
});
+124 -70
View File
@@ -1,4 +1,4 @@
angular.module("umbraco").controller("Imulus.ArchetypeController", function ($scope, $http, assetsService, angularHelper, notificationsService, $timeout, entityResource) {
angular.module("umbraco").controller("Imulus.ArchetypeController", function ($scope, $http, assetsService, angularHelper, notificationsService, $timeout, fileManager, entityResource, archetypeService, archetypeLabelService, archetypeCacheService, archetypePropertyEditorResource) {
//$scope.model.value = "";
$scope.model.hideLabel = $scope.model.config.hideLabel == 1;
@@ -15,99 +15,118 @@
// store the umbraco property alias to help generate unique IDs. Hopefully there's a better way to get this in the future :)
$scope.umbracoHostPropertyAlias = $scope.$parent.$parent.model.alias;
$scope.overlayMenu = {
show: false,
style: {}
};
init();
//hold references to helper resources
//hold references to helper resources
$scope.resources = {
entityResource: entityResource
entityResource: entityResource,
archetypePropertyEditorResource: archetypePropertyEditorResource
}
//hold references to helper services
$scope.services = {
archetypeService: archetypeService,
archetypeLabelService: archetypeLabelService,
archetypeCacheService: archetypeCacheService
}
//helper to get $eval the labelTemplate
$scope.getFieldsetTitle = function(fieldsetConfigModel, fieldsetIndex) {
if(!fieldsetConfigModel)
return "";
var fieldset = $scope.model.value.fieldsets[fieldsetIndex];
var fieldsetConfig = $scope.getConfigFieldsetByAlias(fieldset.alias);
var template = fieldsetConfigModel.labelTemplate;
if (template.length < 1)
return fieldsetConfig.label;
var rgx = /{{(.*?)}}*/g;
var results;
var parsedTemplate = template;
while ((results = rgx.exec(template)) !== null) {
// split the template in case it consists of multiple property aliases and/or functions
var parts = results[1].split("|");
var templateLabelValue = "";
for(var i = 0; i < parts.length; i++) {
// stop looking for a template label value if a previous template part already yielded a value
if(templateLabelValue != "") {
break;
}
var part = parts[i];
//test for function
var beginIndexOf = part.indexOf("(");
var endIndexOf = part.indexOf(")");
if(beginIndexOf != -1 && endIndexOf != -1)
{
var functionName = part.substring(0, beginIndexOf);
var propertyAlias = part.substring(beginIndexOf + 1, endIndexOf);
templateLabelValue = executeFunctionByName(functionName, window, $scope.getPropertyValueByAlias(fieldset, propertyAlias), $scope);
}
else {
propertyAlias = part;
templateLabelValue = $scope.getPropertyValueByAlias(fieldset, propertyAlias);
}
}
parsedTemplate = parsedTemplate.replace(results[0], templateLabelValue);
}
return parsedTemplate;
};
function executeFunctionByName(functionName, context) {
var args = Array.prototype.slice.call(arguments).splice(2);
var namespaces = functionName.split(".");
var func = namespaces.pop();
for(var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
if(context && context[func]) {
return context[func].apply(this, args);
}
return "";
$scope.getFieldsetTitle = function (fieldsetConfigModel, fieldsetIndex) {
return archetypeLabelService.getFieldsetTitle($scope, fieldsetConfigModel, fieldsetIndex);
}
var draggedRteSettings;
var rteClass = ".mce-tinymce";
//sort config
$scope.sortableOptions = {
axis: 'y',
cursor: "move",
handle: ".handle",
start: function(ev, ui) {
ui.item.parent().find('.umb-rte textarea').each(function () {
tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id'));
draggedRteSettings = {};
ui.item.parent().find(rteClass).each(function () {
// remove all RTEs in the dragged row and save their settings
var $element = $(this);
var wrapperId = $element.attr('id');
var $textarea = $element.siblings('textarea');
var textareaId = $textarea.attr('id');
draggedRteSettings[textareaId] = _.findWhere(tinyMCE.editors, { id: textareaId }).settings;
tinyMCE.execCommand('mceRemoveEditor', false, wrapperId);
});
},
update: function (ev, ui) {
$scope.setDirty();
},
stop: function (ev, ui) {
ui.item.parent().find('.umb-rte textarea').each(function () {
tinyMCE.execCommand('mceAddEditor', false, $(this).attr('id'));
ui.item.parent().find(rteClass).each(function () {
var $element = $(this);
var wrapperId = $element.attr('id');
var $textarea = $element.siblings('textarea');
var textareaId = $textarea.attr('id');
draggedRteSettings[textareaId] = draggedRteSettings[textareaId] || _.findWhere(tinyMCE.editors, { id: textareaId }).settings;
tinyMCE.execCommand('mceRemoveEditor', false, wrapperId);
tinyMCE.init(draggedRteSettings[textareaId]);
});
}
};
//handles a fieldset add
$scope.openFieldsetPicker = function ($index, event) {
if ($scope.canAdd() == false) {
return;
}
$scope.overlayMenu.fieldsets = [];
_.each($scope.model.config.fieldsets, function (fieldset) {
var icon = fieldset.icon;
$scope.overlayMenu.fieldsets.push({
alias: fieldset.alias,
label: fieldset.label,
icon: (fieldset.icon || "icon-document-dashed-line") // default icon if none is chosen
});
$scope.overlayMenu.index = $index;
});
// sanity check
if ($scope.overlayMenu.fieldsets.length == 0) {
return;
}
if ($scope.overlayMenu.fieldsets.length == 1) {
// only one fieldset type - no need to display the picker
$scope.addRow($scope.overlayMenu.fieldsets[0].alias, $index);
return;
}
// calculate overlay position
// - yeah... it's jQuery (ungh!) but that's how the Grid does it.
var offset = $(event.target).offset();
var scrollTop = $(event.target).closest(".umb-panel-body").scrollTop();
if (offset.top < 400) {
$scope.overlayMenu.style.top = 300 + scrollTop;
}
else {
$scope.overlayMenu.style.top = offset.top - 150 + scrollTop;
}
$scope.overlayMenu.show = true;
};
$scope.closeFieldsetPicker = function () {
$scope.overlayMenu.show = false;
};
$scope.pickFieldset = function (fieldsetAlias, $index) {
$scope.closeFieldsetPicker();
$scope.addRow(fieldsetAlias, $index);
};
$scope.addRow = function (fieldsetAlias, $index) {
if ($scope.canAdd()) {
if ($scope.model.config.fieldsets) {
@@ -122,9 +141,13 @@
$scope.model.value.fieldsets.push(newFieldset);
}
}
$scope.setDirty();
$scope.$broadcast("archetypeAddFieldset", {index: $index, visible: countVisible()});
newFieldset.collapse = $scope.model.config.enableCollapsing ? true : false;
$scope.focusFieldset(newFieldset);
}
}
@@ -134,6 +157,7 @@
if (confirm('Are you sure you want to remove this?')) {
$scope.setDirty();
$scope.model.value.fieldsets.splice($index, 1);
$scope.$broadcast("archetypeRemoveFieldset", {index: $index});
}
}
}
@@ -285,11 +309,36 @@
//developerMode helpers
$scope.model.value.toString = stringify;
// issue 114: register handler for file selection
$scope.model.value.setFiles = setFiles;
//encapsulate stringify (should be built into browsers, not sure of IE support)
function stringify() {
return JSON.stringify(this);
}
// issue 114: handler for file selection
function setFiles(files) {
// get all currently selected files from file manager
var currentFiles = fileManager.getFiles();
// get the files already selected for this archetype (by alias)
var archetypeFiles = [];
_.each(currentFiles, function (item) {
if (item.alias === $scope.model.alias) {
archetypeFiles.push(item.file);
}
});
// add the newly selected files
_.each(files, function (file) {
archetypeFiles.push(file);
});
// update the selected files for this archetype (by alias)
fileManager.setFiles($scope.model.alias, archetypeFiles);
}
//watch for changes
$scope.$watch('model.value', function (v) {
if ($scope.model.config.developerMode) {
@@ -299,6 +348,11 @@
$scope.model.value.toString = stringify;
}
}
// issue 114: re-register handler for files selection and reset the currently selected files on the file manager
$scope.model.value.setFiles = setFiles;
fileManager.setFiles($scope.model.alias, []);
// reset submit watcher counter on save
$scope.activeSubmitWatcher = 0;
});
@@ -393,7 +447,7 @@
}
//archetype css
assetsService.loadCss("/App_Plugins/Archetype/css/archetype.css");
assetsService.loadCss("../App_Plugins/Archetype/css/archetype.css");
//custom css
if($scope.model.config.customCssPath)
+29
View File
@@ -0,0 +1,29 @@
angular.module("umbraco.directives")
.directive('archetypeClickOutside', function ($timeout, $parse) {
return {
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
var fn = $parse(attrs.archetypeClickOutside);
// add click event handler (delayed so we don't trigger the callback immediately if this directive itself was triggered by a mouse click)
$timeout(function () {
$(document).on("click", mouseClick);
}, 500);
function mouseClick(event) {
if($(event.target).closest(element).length > 0) {
return;
}
var callback = function () {
fn(scope, { $event: event });
};
scope.$apply(callback);
}
// unbind event
scope.$on('$destroy', function () {
$(document).off("click", mouseClick);
});
}
};
});
+72 -146
View File
@@ -1,75 +1,33 @@
angular.module("umbraco.directives").directive('archetypeProperty', function ($compile, $http, archetypePropertyEditorResource, umbPropEditorHelper, $timeout, $rootScope, $q, editorState) {
function getFieldsetByAlias(fieldsets, alias)
{
return _.find(fieldsets, function(fieldset){
return fieldset.alias == alias;
});
}
function getPropertyIndexByAlias(properties, alias){
for (var i in properties)
{
if (properties[i].alias == alias) {
return i;
}
}
}
function getPropertyByAlias(fieldset, alias){
return _.find(fieldset.properties, function(property){
return property.alias == alias;
});
}
//helper that returns a JS ojbect from 'value' string or the original string
function jsonOrString(value, developerMode, debugLabel){
if(value && typeof value == 'string'){
try{
if(developerMode == '1'){
console.log("Trying to parse " + debugLabel + ": " + value);
}
value = JSON.parse(value);
}
catch(exception)
{
if(developerMode == '1'){
console.log("Failed to parse " + debugLabel + ".");
}
}
}
if(value && developerMode == '1'){
console.log(debugLabel + " post-parsing: ");
console.log(value);
}
return value;
}
angular.module("umbraco.directives").directive('archetypeProperty', function ($compile, $http, archetypePropertyEditorResource, umbPropEditorHelper, $timeout, $rootScope, $q, fileManager, editorState, archetypeService, archetypeCacheService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var configFieldsetModel = getFieldsetByAlias(scope.archetypeConfig.fieldsets, scope.fieldset.alias);
var configFieldsetModel = archetypeService.getFieldsetByAlias(scope.archetypeConfig.fieldsets, scope.fieldset.alias);
var view = "";
var label = configFieldsetModel.properties[scope.propertyConfigIndex].label;
var dataTypeGuid = configFieldsetModel.properties[scope.propertyConfigIndex].dataTypeGuid;
var config = null;
var alias = configFieldsetModel.properties[scope.propertyConfigIndex].alias;
var defaultValue = configFieldsetModel.properties[scope.propertyConfigIndex].value;
var propertyAlias = getUniquePropertyAlias(scope);
propertyAliasParts = [];
var propertyAliasParts = [];
var propertyAlias = archetypeService.getUniquePropertyAlias(scope, propertyAliasParts);
//try to convert the defaultValue to a JS object
defaultValue = jsonOrString(defaultValue, scope.archetypeConfig.developerMode, "defaultValue");
defaultValue = archetypeService.jsonOrString(defaultValue, scope.archetypeConfig.developerMode, "defaultValue");
//grab info for the selected datatype, prepare for view
archetypePropertyEditorResource.getDataType(dataTypeGuid, scope.archetypeConfig.enableDeepDatatypeRequests, editorState.current.contentTypeAlias, scope.propertyEditorAlias, alias, editorState.current.id).then(function (data) {
//transform preValues array into object expected by propertyeditor views
var configObj = {};
_.each(data.preValues, function(p) {
configObj[p.key] = p.value;
});
config = configObj;
//caching for use by label templates later
archetypeCacheService.addDatatypeToCache(data, dataTypeGuid);
//determine the view to use [...] and load it
archetypePropertyEditorResource.getPropertyEditorMapping(data.selectedEditor).then(function(propertyEditor) {
var pathToView = umbPropEditorHelper.getViewPath(propertyEditor.view);
@@ -82,92 +40,12 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
var mergedConfig = _.extend(defaultConfigObj, config);
loadView(pathToView, mergedConfig, defaultValue, alias, propertyAlias, scope, element, ngModelCtrl, propertyValueChanged);
loadView(pathToView, mergedConfig, defaultValue, alias, propertyAlias, scope, element, ngModelCtrl, configFieldsetModel);
});
});
scope.$on("archetypeFormSubmitting", function (ev, args) {
// validate all fieldset properties
_.each(scope.fieldset.properties, function (property) {
validateProperty(scope.fieldset, property);
});
// set invalid state if the fieldset contains invalid properties
// we need an unique validation key per fieldset in this ngModelCtrl scope, which leaves us with a potential problem:
// if the user invalidates the last fieldset, attempts (and fails) to save and and subsequently deletes the invalid fieldset,
// the validation key will persist on ngModelCtrl and thus the form will not submit.
// to fix this problem, the controller probably needs to raise an event (in removeRow) that can be intercepted here to
// clear the validation key.
var validationKey = "validation-f" + scope.fieldsetIndex;
ngModelCtrl.$setValidity(validationKey, scope.fieldset.isValid);
});
// called when the value of any property in a fieldset changes
function propertyValueChanged(fieldset, property) {
// it's the Umbraco way to hide the invalid state when altering an invalid property, even if the new value isn't valid either
property.isValid = true;
setFieldsetValidity(fieldset);
}
// validate a property in a fieldset
function validateProperty(fieldset, property) {
var propertyConfig = getPropertyByAlias(configFieldsetModel, property.alias);
if (propertyConfig) {
// use property.value !== property.value to check for NaN values on numeric inputs
if (propertyConfig.required && (property.value == null || property.value === "" || property.value !== property.value)) {
property.isValid = false;
}
// issue 116: RegEx validate property value
// Only validate the property value if anything has been entered - RegEx is considered a supplement to "required".
if (property.isValid == true && propertyConfig.regEx && property.value) {
var regEx = new RegExp(propertyConfig.regEx);
if (regEx.test(property.value) == false) {
property.isValid = false;
}
}
}
setFieldsetValidity(fieldset);
}
function setFieldsetValidity(fieldset) {
// mark the entire fieldset as invalid if there are any invalid properties in the fieldset, otherwise mark it as valid
fieldset.isValid =
_.find(fieldset.properties, function (property) {
return property.isValid == false
}) == null;
}
}
var propertyAliasParts = [];
var getUniquePropertyAlias = function (currentScope) {
if (currentScope.hasOwnProperty('fieldsetIndex') && currentScope.hasOwnProperty('property') && currentScope.hasOwnProperty('propertyConfigIndex'))
{
var currentPropertyAlias = "f" + currentScope.fieldsetIndex + "-" + currentScope.property.alias + "-p" + currentScope.propertyConfigIndex;
propertyAliasParts.push(currentPropertyAlias);
}
else if (currentScope.hasOwnProperty('isPreValue')) // Crappy way to identify this is the umbraco property scope
{
var umbracoPropertyAlias = currentScope.$parent.$parent.property.alias; // Crappy way to get the umbraco host alias once we identify its scope
propertyAliasParts.push(umbracoPropertyAlias);
}
if (currentScope.$parent)
getUniquePropertyAlias(currentScope.$parent);
return _.unique(propertyAliasParts).reverse().join("-");
};
var getFieldset = function(scope) {
return scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex];
}
var getFieldsetProperty = function (scope) {
return getFieldset(scope).properties[scope.renderModelPropertyIndex];
}
function loadView(view, config, defaultValue, alias, propertyAlias, scope, element, ngModelCtrl, propertyValueChanged) {
function loadView(view, config, defaultValue, alias, propertyAlias, scope, element, ngModelCtrl, configFieldsetModel) {
if (view)
{
$http.get(view, { cache: true }).success(function (data) {
@@ -183,15 +61,19 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
scope.model.config = {};
//ini the property value after test to make sure a prop exists in the renderModel
scope.renderModelPropertyIndex = getPropertyIndexByAlias(getFieldset(scope).properties, alias);
scope.renderModelPropertyIndex = archetypeService.getPropertyIndexByAlias(archetypeService.getFieldset(scope).properties, alias);
if (!scope.renderModelPropertyIndex)
{
getFieldset(scope).properties.push(JSON.parse('{"alias": "' + alias + '", "value": "' + defaultValue + '"}'));
scope.renderModelPropertyIndex = getPropertyIndexByAlias(getFieldset(scope).properties, alias);
archetypeService.getFieldset(scope).properties.push(JSON.parse('{"alias": "' + alias + '", "value": "' + defaultValue + '"}'));
scope.renderModelPropertyIndex = archetypeService.getPropertyIndexByAlias(archetypeService.getFieldset(scope).properties, alias);
}
scope.renderModel = {};
scope.model.value = getFieldsetProperty(scope).value;
scope.model.value = archetypeService.getFieldsetProperty(scope).value;
//init the property editor state
archetypeService.getFieldsetProperty(scope).editorState = {};
//set the config from the prevalues
scope.model.config = config;
@@ -211,30 +93,74 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
}
}
//console.log(scope.model.config);
//upload datatype hack
if(view.indexOf('fileupload.html') != -1) {
scope.propertyForm = scope.form;
scope.model.validation = {};
scope.model.validation.mandatory = 0;
}
//some items need an alias
scope.model.alias = "archetype-property-" + propertyAlias;
//some items also need an id (file upload for example)
scope.model.id = propertyAlias;
//watch for changes since there is no two-way binding with the local model.value
scope.$watch('model.value', function (newValue, oldValue) {
getFieldsetProperty(scope).value = newValue;
archetypeService.getFieldsetProperty(scope).value = newValue;
// notify the linker that the property value changed
propertyValueChanged(getFieldset(scope), getFieldsetProperty(scope));
archetypeService.propertyValueChanged(archetypeService.getFieldset(scope), archetypeService.getFieldsetProperty(scope));
});
scope.$on('archetypeFormSubmitting', function (ev, args) {
// validate all fieldset properties
_.each(scope.fieldset.properties, function (property) {
archetypeService.validateProperty(scope.fieldset, property, configFieldsetModel);
});
var validationKey = "validation-f" + scope.fieldsetIndex;
ngModelCtrl.$setValidity(validationKey, scope.fieldset.isValid);
// did the value change (if it did, it most likely did so during the "formSubmitting" event)
var currentValue = getFieldsetProperty(scope).value;
var property = archetypeService.getFieldsetProperty(scope);
var currentValue = property.value;
if (currentValue != scope.model.value) {
getFieldsetProperty(scope).value = scope.model.value;
archetypeService.getFieldsetProperty(scope).value = scope.model.value;
// notify the linker that the property value changed
propertyValueChanged(getFieldset(scope), getFieldsetProperty(scope));
archetypeService.propertyValueChanged(archetypeService.getFieldset(scope), archetypeService.getFieldsetProperty(scope));
}
});
// issue 114: handle file selection on property editors
scope.$on("filesSelected", function (event, args) {
// populate the fileNames collection on the property editor state
var property = archetypeService.getFieldsetProperty(scope);
property.editorState.fileNames = [];
_.each(args.files, function (item) {
property.editorState.fileNames.push(item.name);
});
// remove the files set for this property
// NOTE: we can't use property.alias because the file manager registers the selected files on the assigned Archetype property alias (e.g. "archetype-property-archetype-property-archetype-property-content-0-2-0-1-0-0")
fileManager.setFiles(scope.model.alias, []);
// now tell the containing Archetype to pick up the selected files
scope.archetypeRenderModel.setFiles(args.files);
});
scope.$on("archetypeRemoveFieldset", function (ev, args) {
var validationKey = "validation-f" + args.index;
ngModelCtrl.$setValidity(validationKey, true);
});
element.html(data).show();
$compile(element.contents())(scope);
@@ -265,4 +191,4 @@ angular.module("umbraco.directives").directive('archetypeProperty', function ($c
umbracoForm: '='
}
}
});
});
-41
View File
@@ -1,41 +0,0 @@
var ArchetypeLabels = (function() {
var isEntityLookupLoading = false;
var entityNameLookupCache = [];
function getEntityById(scope, id, type) {
for (var i in entityNameLookupCache) {
if (entityNameLookupCache[i].id == id) {
return entityNameLookupCache[i].value;
}
}
if (!isEntityLookupLoading) {
isEntityLookupLoading = true;
scope.resources.entityResource.getById(id, type).then(function(entity) {
entityNameLookupCache.push({id: id, value: entity.name});
isEntityLookupLoading = false;
return entity.name;
});
}
return "";
}
return {
GetFirstDocumentEntityName: function ($scope, value) {
if (value) {
var id = value.split(",")[0];
if (id) {
return getEntityById($scope, id, "Document");
}
}
return "";
}
}
})();
+70
View File
@@ -0,0 +1,70 @@
var ArchetypeSampleLabelTemplates = (function() {
//public functions
return {
Entity: function (value, scope, args) {
if(!args.entityType) {
args = {entityType: "Document", propertyName: "name"}
}
if (value) {
//if handed a csv list, take the first only
var id = value.split(",")[0];
if (id) {
var entity = scope.services.archetypeLabelService.getEntityById(scope, id, args.entityType);
if(entity) {
return entity[args.propertyName];
}
}
}
return "";
},
UrlPicker: function(value, scope, args) {
if(!args.propertyName) {
args = {propertyName: "name"}
}
var entity;
switch (value.type) {
case "content":
if(value.typeData.contentId) {
entity = scope.services.archetypeLabelService.getEntityById(scope, value.typeData.contentId, "Document");
}
break;
case "media":
if(value.typeData.mediaId) {
entity = scope.services.archetypeLabelService.getEntityById(scope, value.typeData.mediaId, "Media");
}
break;
case "url":
return value.typeData.url;
default:
break;
}
if(entity) {
return entity[args.propertyName];
}
return "";
},
Rte: function (value, scope, args) {
if(!args.contentLength) {
args = {contentLength: 50}
}
return $(value).text().substring(0, args.contentLength);
}
}
})();
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Tilføj et element",
"hideFieldsetControls": "Skjul fieldsets?",
"hideFieldsetControlsDescription": "Skjuler kontrollerne tilføj/fjern/sortér.",
"toggleAdvanced": "Slå avancerede muligheder til/fra",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Tilføj et element",
"hideFieldsetControls": "Skjul fieldsets?",
"hideFieldsetControlsDescription": "Skjuler kontrollerne tilføj/fjern/sortér.",
"toggleAdvanced": "Slå avancerede muligheder til/fra",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+1
View File
@@ -1,6 +1,7 @@
{
"label": "Label",
"alias": "Alias",
"addFieldset": "Add an item",
"hideFieldsetControls": "Hide Fieldset Controls?",
"hideFieldsetControlsDescription": "Hides the fieldset add/remove/sort controls.",
"toggleAdvanced": "Toggle Advanced Options",
+78 -33
View File
@@ -2,11 +2,11 @@
.archetypeEditor {
width: 98%;
.open {
position: relative;
}
.dropdown-menu {
top: auto;
> li > a {
@@ -23,14 +23,28 @@
}
.icon {
color: #333;
color: #ddd;
padding: 2px;
font-size: 16px;
&:hover {
color: #333;
}
&.icon-delete:hover {
color: #b94a48;
}
}
.fieldsetIcon {
float: left;
padding: 0 4px 0 0;
color: #333;
vertical-align: text-bottom;
}
fieldset {
border: 1px solid #dddddd;
border-bottom: 1px dashed #dddddd;
padding: 0;
margin-bottom: 5px;
background-color: #f1f1f1;
clear: both;
display: inline-block;
width: 100%;
@@ -56,13 +70,31 @@
.archetypeFieldsetLabel {
position: relative;
width: 100%;
min-height: 42px;
.label-sub {
padding: 8px 0;
padding: 8px 0 8px 0;
display: inline-block;
min-height: 20px; /* this makes the header clickable when there's no icon and no text */
&.module-label {
width: 100%;
}
&:hover {
cursor: pointer;
label {
span {
text-decoration: underline;
}
&:hover {
cursor: pointer;
}
}
.caret-right {
border-left-color: #333;
}
}
}
.dropdown-menu {
@@ -83,26 +115,14 @@
cursor: default;
}
&.enableCollapsing:hover {
cursor: pointer;
label {
span {
text-decoration: underline;
}
&:hover {
cursor: pointer;
}
}
}
.caret {
margin: 8px 1px 0 15px;
margin: 8px 1px 0 2px;
position: absolute;
&.caret-right {
border-bottom: 4px solid transparent;
border-top: 4px solid transparent;
border-left: 4px solid #000000;
border-left: 4px solid #ddd;
content: "";
margin-top: 6px;
}
@@ -110,13 +130,20 @@
label {
margin-bottom: 0;
margin-left: 5px;
margin-left: 18px;
white-space: nowrap;
padding: 0;
width: ~"calc(100% - 144px)";
position: absolute;
span {
display: inline-block;
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
&.dimmed {
color: #d9d9d9;
i, span {
color: #d9d9d9 !important;
}
}
}
@@ -126,6 +153,9 @@
right: 15px;
top: 0;
}
&:hover {
}
}
.archetypePropertyError {
@@ -145,9 +175,8 @@
.archetypeCollapser {
clear: both;
height: 100%;
padding-top: 12px;
padding: 12px 0 0 20px;
background-color: #fff;
border-top: 1px solid silver;
border-radius: 0 0 0 5px;
form {
@@ -179,13 +208,25 @@
border: 1px solid #b94a48;
}
.archetypeAddButton:hover {
text-decoration: none;
cursor: pointer;
.archetypeAddButtonText {
text-decoration: underline;
.archetypeAddButton {
color: #ddd;
.icon {
vertical-align: text-top;
}
&:hover {
color: #333;
text-decoration: none;
cursor: pointer;
.archetypeAddButtonText {
text-decoration: underline;
}
.icon {
color: #333;
}
}
}
.multiPropertyTextbox {
@@ -202,6 +243,10 @@
color: #343434;
}
}
.fieldsetPicker .icon {
color: #333;
}
}
.archetypeEditor, .archetypeConfig {
@@ -3,24 +3,24 @@ angular.module('umbraco.resources').factory('archetypePropertyEditorResource', f
getAllDataTypes: function() {
// Hack - grab DataTypes from Tree API, as `dataTypeService.getAll()` isn't implemented yet
return umbRequestHelper.resourcePromise(
$http.get("/umbraco/backoffice/ArchetypeApi/ArchetypeDataType/GetAll"), 'Failed to retrieve datatypes from tree service'
$http.get("backoffice/ArchetypeApi/ArchetypeDataType/GetAll"), 'Failed to retrieve datatypes from tree service'
);
},
getDataType: function (guid, useDeepDatatypeLookup, contentTypeAlias, propertyTypeAlias, archetypeAlias, nodeId) {
if(useDeepDatatypeLookup) {
return umbRequestHelper.resourcePromise(
$http.get("/umbraco/backoffice/ArchetypeApi/ArchetypeDataType/GetByGuid?guid=" + guid + "&contentTypeAlias=" + contentTypeAlias + "&propertyTypeAlias=" + propertyTypeAlias + "&archetypeAlias=" + archetypeAlias + "&nodeId=" + nodeId), 'Failed to retrieve datatype'
$http.get("backoffice/ArchetypeApi/ArchetypeDataType/GetByGuid?guid=" + guid + "&contentTypeAlias=" + contentTypeAlias + "&propertyTypeAlias=" + propertyTypeAlias + "&archetypeAlias=" + archetypeAlias + "&nodeId=" + nodeId), 'Failed to retrieve datatype'
);
}
else {
return umbRequestHelper.resourcePromise(
$http.get("/umbraco/backoffice/ArchetypeApi/ArchetypeDataType/GetByGuid?guid=" + guid , { cache: true }), 'Failed to retrieve datatype'
$http.get("backoffice/ArchetypeApi/ArchetypeDataType/GetByGuid?guid=" + guid , { cache: true }), 'Failed to retrieve datatype'
);
}
},
getPropertyEditorMapping: function(alias) {
return umbRequestHelper.resourcePromise(
$http.get("/umbraco/backoffice/ArchetypeApi/ArchetypeDataType/GetAllPropertyEditors", { cache: true }), 'Failed to retrieve datatype mappings'
$http.get("backoffice/ArchetypeApi/ArchetypeDataType/GetAllPropertyEditors", { cache: true }), 'Failed to retrieve datatype mappings'
).then(function (data) {
var result = _.find(data, function(d) {
return d.alias === alias;
+78
View File
@@ -0,0 +1,78 @@
angular.module('umbraco.services').factory('archetypeCacheService', function (archetypePropertyEditorResource) {
//private
var isEntityLookupLoading = false;
var entityCache = [];
var isDatatypeLookupLoading = false;
var datatypeCache = [];
return {
getDataTypeFromCache: function(guid) {
return _.find(datatypeCache, function (dt){
return dt.dataTypeGuid == guid;
});
},
addDatatypeToCache: function(datatype, dataTypeGuid) {
var cachedDatatype = this.getDataTypeFromCache(dataTypeGuid);
if(!cachedDatatype) {
datatype.dataTypeGuid = dataTypeGuid;
datatypeCache.push(datatype);
}
},
getDatatypeByGuid: function(guid) {
var cachedDatatype = this.getDataTypeFromCache(guid);
if(cachedDatatype) {
return cachedDatatype;
}
//go get it from server, but this should already be pre-populated from the directive, but I suppose I'll leave this in in case used ad-hoc
if (!isDatatypeLookupLoading) {
isDatatypeLookupLoading = true;
archetypePropertyEditorResource.getDataType(guid).then(function(datatype) {
datatype.dataTypeGuid = guid;
datatypeCache.push(datatype);
isDatatypeLookupLoading = false;
return datatype;
});
}
return null;
},
getEntityById: function(scope, id, type) {
var cachedEntity = _.find(entityCache, function (e){
return e.id == id;
});
if(cachedEntity) {
return cachedEntity;
}
//go get it from server
if (!isEntityLookupLoading) {
isEntityLookupLoading = true;
scope.resources.entityResource.getById(id, type).then(function(entity) {
entityCache.push(entity);
isEntityLookupLoading = false;
return entity;
});
}
return null;
}
}
});
+265
View File
@@ -0,0 +1,265 @@
angular.module('umbraco.services').factory('archetypeLabelService', function (archetypeCacheService) {
//private
function executeFunctionByName(functionName, context) {
var args = Array.prototype.slice.call(arguments).splice(2);
var namespaces = functionName.split(".");
var func = namespaces.pop();
for(var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
if(context && context[func]) {
return context[func].apply(this, args);
}
return "";
}
function getNativeLabel(datatype, value, scope) {
switch (datatype.selectedEditor) {
case "Imulus.UrlPicker":
return imulusUrlPicker(value, scope, {});
case "Umbraco.TinyMCEv3":
return coreTinyMce(value, scope, {});
case "Umbraco.MultiNodeTreePicker":
return coreMntp(value, scope, datatype);
case "Umbraco.MediaPicker":
return coreMediaPicker(value, scope, datatype);
case "Umbraco.DropDown":
return coreDropdown(value, scope, datatype);
default:
return "";
}
}
function coreDropdown(value, scope, args) {
if(!value)
return "";
var prevalue = args.preValues[0].value[value];
if(prevalue) {
return prevalue.value;
}
return "";
}
function coreMntp(value, scope, args) {
var ids = value.split(',');
var type = "Document";
switch(args.preValues[0].value.type) {
case 'content':
type = 'Document';
break;
case 'media':
type = 'media';
break;
case 'member':
type = 'member';
break;
default:
break;
}
var entityArray = [];
_.each(ids, function(id){
if(id) {
var entity = archetypeCacheService.getEntityById(scope, id, type);
if(entity) {
entityArray.push(entity.name);
}
}
});
return entityArray.join(', ');
}
function coreMediaPicker(value, scope, args) {
if(value) {
var entity = archetypeCacheService.getEntityById(scope, value, "media");
if(entity) {
return entity.name;
}
}
return "";
}
function imulusUrlPicker(value, scope, args) {
if(!args.propertyName) {
args = {propertyName: "name"}
}
var entity;
switch (value.type) {
case "content":
if(value.typeData.contentId) {
entity = archetypeCacheService.getEntityById(scope, value.typeData.contentId, "Document");
}
break;
case "media":
if(value.typeData.mediaId) {
entity = archetypeCacheService.getEntityById(scope, value.typeData.mediaId, "Media");
}
break;
case "url":
return value.typeData.url;
default:
break;
}
if(entity) {
return entity[args.propertyName];
}
return "";
}
function coreTinyMce(value, scope, args) {
if(!args.contentLength) {
args = {contentLength: 50}
}
var suffix = "";
var strippedText = $("<div/>").html(value).text();
if(strippedText.length > args.contentLength) {
suffix = "…";
}
return strippedText.substring(0, args.contentLength) + suffix;
}
return {
getFieldsetTitle: function(scope, fieldsetConfigModel, fieldsetIndex) {
//console.log(scope.model.config);
if(!fieldsetConfigModel)
return "";
var fieldset = scope.model.value.fieldsets[fieldsetIndex];
var fieldsetConfig = scope.getConfigFieldsetByAlias(fieldset.alias);
var template = fieldsetConfigModel.labelTemplate;
if (template.length < 1)
return fieldsetConfig.label;
var rgx = /({{(.*?)}})*/g;
var results;
var parsedTemplate = template;
var rawMatches = template.match(rgx);
var matches = [];
_.each(rawMatches, function(match){
if(match) {
matches.push(match);
}
});
_.each(matches, function (match) {
// split the template in case it consists of multiple property aliases and/or functions
var templates = match.replace("{{", '').replace("}}", '').split("|");
var templateLabelValue = "";
for(var i = 0; i < templates.length; i++) {
// stop looking for a template label value if a previous template part already yielded a value
if(templateLabelValue != "") {
break;
}
var template = templates[i];
//test for function
var beginParamsIndexOf = template.indexOf("(");
var endParamsIndexOf = template.indexOf(")");
//if passed a function
if(beginParamsIndexOf != -1 && endParamsIndexOf != -1)
{
var functionName = template.substring(0, beginParamsIndexOf);
var propertyAlias = template.substring(beginParamsIndexOf + 1, endParamsIndexOf).split(',')[0];
var args = {};
var beginArgsIndexOf = template.indexOf(',');
if(beginArgsIndexOf != -1) {
var argsString = template.substring(beginArgsIndexOf + 1, endParamsIndexOf).trim();
var normalizedJsonString = argsString.replace(/(\w+)\s*:/g, '"$1":');
args = JSON.parse(normalizedJsonString);
}
templateLabelValue = executeFunctionByName(functionName, window, scope.getPropertyValueByAlias(fieldset, propertyAlias), scope, args);
}
//normal {{foo}} syntax
else {
propertyAlias = template;
var rawValue = scope.getPropertyValueByAlias(fieldset, propertyAlias);
templateLabelValue = rawValue;
//determine the type of editor
var propertyConfig = _.find(fieldsetConfigModel.properties, function(property){
return property.alias == propertyAlias;
});
if(propertyConfig) {
var datatype = archetypeCacheService.getDatatypeByGuid(propertyConfig.dataTypeGuid);
if(datatype) {
//try to get built-in label
var label = getNativeLabel(datatype, templateLabelValue, scope);
if(label) {
templateLabelValue = label;
}
else {
templateLabelValue = templateLabelValue;
}
}
}
else {
return templateLabelValue;
}
}
}
if(!templateLabelValue) {
templateLabelValue = "";
}
parsedTemplate = parsedTemplate.replace(match, templateLabelValue);
});
return parsedTemplate;
}
}
});
@@ -24,7 +24,7 @@ angular.module('umbraco.services').factory('archetypeLocalizationService', funct
initLocalizedResources:function () {
var deferred = $q.defer();
userService.getCurrentUser().then(function(user){
$http.get("/App_plugins/Archetype/langs/" + user.locale + ".js", { cache: true })
$http.get("../App_plugins/Archetype/langs/" + user.locale + ".js", { cache: true })
.then(function(response){
service.resourceFileLoaded = true;
service.dictionary = response.data;
+104
View File
@@ -0,0 +1,104 @@
angular.module('umbraco.services').factory('archetypeService', function () {
//public
return {
//helper that returns a JS ojbect from 'value' string or the original string
jsonOrString: function (value, developerMode, debugLabel){
if(value && typeof value == 'string'){
try{
if(developerMode == '1'){
console.log("Trying to parse " + debugLabel + ": " + value);
}
value = JSON.parse(value);
}
catch(exception)
{
if(developerMode == '1'){
console.log("Failed to parse " + debugLabel + ".");
}
}
}
if(value && developerMode == '1'){
console.log(debugLabel + " post-parsing: ");
console.log(value);
}
return value;
},
getFieldsetByAlias: function (fieldsets, alias)
{
return _.find(fieldsets, function(fieldset){
return fieldset.alias == alias;
});
},
getPropertyIndexByAlias: function(properties, alias){
for (var i in properties)
{
if (properties[i].alias == alias) {
return i;
}
}
},
getPropertyByAlias: function (fieldset, alias){
return _.find(fieldset.properties, function(property){
return property.alias == alias;
});
},
getUniquePropertyAlias: function (currentScope, propertyAliasParts) {
if (currentScope.hasOwnProperty('fieldsetIndex') && currentScope.hasOwnProperty('property') && currentScope.hasOwnProperty('propertyConfigIndex'))
{
var currentPropertyAlias = "f" + currentScope.fieldsetIndex + "-" + currentScope.property.alias + "-p" + currentScope.propertyConfigIndex;
propertyAliasParts.push(currentPropertyAlias);
}
else if (currentScope.hasOwnProperty('isPreValue')) // Crappy way to identify this is the umbraco property scope
{
var umbracoPropertyAlias = currentScope.$parent.$parent.property.alias; // Crappy way to get the umbraco host alias once we identify its scope
propertyAliasParts.push(umbracoPropertyAlias);
}
if (currentScope.$parent)
this.getUniquePropertyAlias(currentScope.$parent, propertyAliasParts);
return _.unique(propertyAliasParts).reverse().join("-");
},
getFieldset: function(scope) {
return scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex];
},
getFieldsetProperty: function (scope) {
return this.getFieldset(scope).properties[scope.renderModelPropertyIndex];
},
setFieldsetValidity: function (fieldset) {
// mark the entire fieldset as invalid if there are any invalid properties in the fieldset, otherwise mark it as valid
fieldset.isValid =
_.find(fieldset.properties, function (property) {
return property.isValid == false
}) == null;
},
validateProperty: function (fieldset, property, configFieldsetModel) {
var propertyConfig = this.getPropertyByAlias(configFieldsetModel, property.alias);
if (propertyConfig) {
// use property.value !== property.value to check for NaN values on numeric inputs
if (propertyConfig.required && (property.value == null || property.value === "" || property.value !== property.value)) {
property.isValid = false;
}
// issue 116: RegEx validate property value
// Only validate the property value if anything has been entered - RegEx is considered a supplement to "required".
if (property.isValid == true && propertyConfig.regEx && property.value) {
var regEx = new RegExp(propertyConfig.regEx);
if (regEx.test(property.value) == false) {
property.isValid = false;
}
}
}
this.setFieldsetValidity(fieldset);
},
// called when the value of any property in a fieldset changes
propertyValueChanged: function (fieldset, property) {
// it's the Umbraco way to hide the invalid state when altering an invalid property, even if the new value isn't valid either
property.isValid = true;
this.setFieldsetValidity(fieldset);
}
}
});
+22 -23
View File
@@ -13,19 +13,11 @@
</label>
</div>
<div class="archetypeEditorControls label-sub" ng-hide="model.config.hideFieldsetControls">
<i class="icon icon-add dropdown-toggle" ng-show="canAdd()" data-toggle="{{ model.config.fieldsets.length > 1 ? 'dropdown' : ''}}" ng-click="model.config.fieldsets.length == 1 ? addRow(fieldset.alias, $index) : return"></i>
<ul class="dropdown-menu umb-actions">
<li ng-repeat="fieldsetModel in model.config.fieldsets" ng-click="addRow(fieldsetModel.alias, $parent.$index)">
<a>
<i class="fieldsetIcon icon ng-class:fieldsetModel.icon"></i>
<span class="menu-label">{{fieldsetModel.label}}</span>
</a>
</li>
</ul>
<i class="icon icon-documents" ng-click="cloneRow($index)" ng-show="canClone()"></i>
<i class="icon icon-remove" ng-click="removeRow($index)" ng-show="canRemove()"></i>
<i class="icon icon-power" ng-class="{dimmed: fieldset.disabled}" ng-click="enableDisable(fieldset)" ng-show="canDisable()"></i>
<i class="icon icon-add" ng-show="canAdd()" ng-click="openFieldsetPicker($index, $event)"></i>
<i class="icon icon-navigation handle" ng-show="canSort()"></i>
<i class="icon icon-documents" ng-click="cloneRow($index)" ng-show="canClone()"></i>
<i class="icon icon-power" ng-click="enableDisable(fieldset)" ng-show="canDisable()"></i>
<i class="icon icon-delete" ng-click="removeRow($index)" ng-show="canRemove()"></i>
</div>
</div>
<div class="archetypeCollapser animate-hide" ng-hide="model.config.enableCollapsing && isCollapsed(fieldset)">
@@ -49,17 +41,24 @@
</li>
</ul>
<div ng-show="showAddButton()">
<a class="archetypeAddButton" data-toggle="{{ model.config.fieldsets.length > 1 ? 'dropdown' : ''}}" ng-click="model.config.fieldsets.length == 1 ? addRow(model.config.fieldsets[0].alias, 0) : return" prevent-default>
<i class="icon icon-add dropdown-toggle"></i>
<localize key="general_add" class="archetypeAddButtonText">Add</localize>
<a class="archetypeAddButton" ng-click="openFieldsetPicker(0, $event)" prevent-default>
<i class="icon icon-add"></i>
<archetype-localize key="addFieldset">Add an item</archetype-localize>
</a>
<ul class="dropdown-menu umb-actions">
<li ng-repeat="fieldsetModel in model.config.fieldsets" ng-click="addRow(fieldsetModel.alias, 0)">
<a>
<i class="fieldsetIcon icon ng-class:fieldsetModel.icon"></i>
<span class="menu-label">{{fieldsetModel.label}}</span>
</a>
</li>
</ul>
</div>
<div class="usky-grid fieldsetPicker" ng-if="overlayMenu.show">
<div class="cell-tools-menu" ng-style="overlayMenu.style" delayed-mouseleave="closeFieldsetPicker()" archetype-click-outside="closeFieldsetPicker()">
<h5>
<archetype-localize key="addFieldset">Add an item</archetype-localize>
</h5>
<ul class="elements">
<li ng-repeat="fieldset in overlayMenu.fieldsets">
<a ng-click="pickFieldset(fieldset.alias, overlayMenu.index)" href>
<i class="icon {{fieldset.icon}}"></i>
{{fieldset.label}}
</a>
</li>
</ul>
</div>
</div>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Archetype",
"version": "1.7.4",
"version": "1.10-ui-rc1",
"url": "http://github.com/imulus/archetype/",
"author": "Imulus - Kevin Giszewski - Tom Fulton - Lee Kelleher - Matt Brailsford - Kenn Jacobsen - Et. Al.",
"authorUrl": "http://imulus.com/",
+15 -12
View File
@@ -1,15 +1,18 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Archetype</id>
<version><%= version %></version>
<title><%= name %></title>
<authors>imulus, kgiszewski, tomfulton</authors>
<owners>imulus, kgiszewski, tomfulton</owners>
<projectUrl>http://github.com/imulus/archetype</projectUrl>
<description><![CDATA[Archetype for Umbraco]]></description>
<tags>umbraco</tags>
<iconUrl>http://github.com/imulus/archetype/raw/master/assets/logo.png</iconUrl>
<licenseUrl><%= licenseUrl %></licenseUrl>
</metadata>
<metadata>
<id>Archetype</id>
<version><%= version %></version>
<title><%= name %></title>
<authors>imulus, kgiszewski, tomfulton</authors>
<owners>imulus, kgiszewski, tomfulton</owners>
<projectUrl>http://github.com/imulus/archetype</projectUrl>
<description><![CDATA[Archetype for Umbraco]]></description>
<tags>umbraco</tags>
<iconUrl>http://github.com/imulus/archetype/raw/master/assets/logo.png</iconUrl>
<licenseUrl><%= licenseUrl %></licenseUrl>
<dependencies>
<dependency id="UmbracoCms.Core" version="7.1.6" />
</dependencies>
</metadata>
</package>