diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs
index ba9f22d945..5ee609b148 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs
@@ -1,74 +1,44 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
-using Umbraco.Core.Serialization;
namespace Umbraco.Core.Models.Blocks
{
///
- /// Converted block data from json
+ /// Convertable block data from json
///
public class BlockEditorData
{
+ private readonly string _propertyEditorAlias;
+
public static BlockEditorData Empty { get; } = new BlockEditorData();
private BlockEditorData()
{
}
- public BlockEditorData(JToken layout,
- IReadOnlyList references,
- IReadOnlyList contentData,
- IReadOnlyList settingsData)
+ public BlockEditorData(string propertyEditorAlias,
+ IEnumerable references,
+ BlockValue blockValue)
{
- Layout = layout ?? throw new ArgumentNullException(nameof(layout));
- References = references ?? throw new ArgumentNullException(nameof(references));
- ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData));
- SettingsData = settingsData ?? throw new ArgumentNullException(nameof(settingsData));
- }
-
- public JToken Layout { get; }
- public IReadOnlyList References { get; } = new List();
- public IReadOnlyList ContentData { get; } = new List();
- public IReadOnlyList SettingsData { get; } = new List();
-
- internal class BlockValue
- {
- [JsonProperty("layout")]
- public IDictionary Layout { get; set; }
-
- [JsonProperty("contentData")]
- public IEnumerable ContentData { get; set; } = new List();
-
- [JsonProperty("settingsData")]
- public IEnumerable SettingsData { get; set; } = new List();
+ if (string.IsNullOrWhiteSpace(propertyEditorAlias))
+ throw new ArgumentException($"'{nameof(propertyEditorAlias)}' cannot be null or whitespace", nameof(propertyEditorAlias));
+ _propertyEditorAlias = propertyEditorAlias;
+ BlockValue = blockValue ?? throw new ArgumentNullException(nameof(blockValue));
+ References = references != null ? new List(references) : throw new ArgumentNullException(nameof(references));
}
///
- /// Represents a single block's data in raw form
+ /// Returns the layout for this specific property editor
///
- public class BlockItemData
- {
- [JsonProperty("contentTypeKey")]
- public Guid ContentTypeKey { get; set; }
+ public JToken Layout => BlockValue.Layout.TryGetValue(_propertyEditorAlias, out var layout) ? layout : null;
- [JsonProperty("udi")]
- [JsonConverter(typeof(UdiJsonConverter))]
- public Udi Udi { get; set; }
+ ///
+ /// Returns the reference to the original BlockValue
+ ///
+ public BlockValue BlockValue { get; }
- ///
- /// The remaining properties will be serialized to a dictionary
- ///
- ///
- /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket
- /// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm
- /// NestedContent serializes to string, int, whatever eg
- /// "stringValue":"Some String","numericValue":125,"otherNumeric":null
- ///
- [JsonExtensionData]
- public Dictionary RawPropertyValues { get; set; } = new Dictionary();
- }
+ public List References { get; } = new List();
}
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
index f045a00401..22e364c0f8 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
@@ -1,9 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
-using System;
using System.Collections.Generic;
-using static Umbraco.Core.Models.Blocks.BlockEditorData;
namespace Umbraco.Core.Models.Blocks
{
@@ -20,21 +18,18 @@ namespace Umbraco.Core.Models.Blocks
_propertyEditorAlias = propertyEditorAlias;
}
- public BlockEditorData Convert(string json)
+ public BlockEditorData Deserialize(string json)
{
var value = JsonConvert.DeserializeObject(json);
if (value.Layout == null)
return BlockEditorData.Empty;
- if (!value.Layout.TryGetValue(_propertyEditorAlias, out var layout))
- return BlockEditorData.Empty;
+ var references = value.Layout.TryGetValue(_propertyEditorAlias, out var layout)
+ ? GetBlockReferences(layout)
+ : Enumerable.Empty();
- var references = GetBlockReferences(layout);
- var contentData = value.ContentData.ToList();
- var settingsData = value.SettingsData.ToList();
-
- return new BlockEditorData(layout, references, contentData, settingsData);
+ return new BlockEditorData(_propertyEditorAlias, references, value);
}
///
@@ -42,7 +37,7 @@ namespace Umbraco.Core.Models.Blocks
///
///
///
- protected abstract IReadOnlyList GetBlockReferences(JToken jsonLayout);
+ protected abstract IEnumerable GetBlockReferences(JToken jsonLayout);
}
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockItemData.cs b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs
new file mode 100644
index 0000000000..12a636771e
--- /dev/null
+++ b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs
@@ -0,0 +1,56 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Serialization;
+
+namespace Umbraco.Core.Models.Blocks
+{
+ ///
+ /// Represents a single block's data in raw form
+ ///
+ public class BlockItemData
+ {
+ [JsonProperty("contentTypeKey")]
+ public Guid ContentTypeKey { get; set; }
+
+ ///
+ /// not serialized, manually set and used during internally
+ ///
+ [JsonIgnore]
+ public string ContentTypeAlias { get; set; }
+
+ [JsonProperty("udi")]
+ [JsonConverter(typeof(UdiJsonConverter))]
+ public Udi Udi { get; set; }
+
+ [JsonIgnore]
+ public Guid Key => Udi != null ? ((GuidUdi)Udi).Guid : throw new InvalidOperationException("No Udi assigned");
+
+ ///
+ /// The remaining properties will be serialized to a dictionary
+ ///
+ ///
+ /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket
+ /// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm
+ /// NestedContent serializes to string, int, whatever eg
+ /// "stringValue":"Some String","numericValue":125,"otherNumeric":null
+ ///
+ [JsonExtensionData]
+ public Dictionary RawPropertyValues { get; set; } = new Dictionary();
+
+ ///
+ /// Used during deserialization to convert the raw property data into data with a property type context
+ ///
+ [JsonIgnore]
+ public IDictionary PropertyValues { get; set; } = new Dictionary();
+
+ ///
+ /// Used during deserialization to populate the property value/property type of a block item content property
+ ///
+ public class BlockPropertyValue
+ {
+ public object Value { get; set; }
+ public PropertyType PropertyType { get; set; }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs
index 0dec05bc54..23f69922d9 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Models.Blocks
{
}
- protected override IReadOnlyList GetBlockReferences(JToken jsonLayout)
+ protected override IEnumerable GetBlockReferences(JToken jsonLayout)
{
var blockListLayout = jsonLayout.ToObject>();
return blockListLayout.Select(x => new ContentAndSettingsReference(x.ContentUdi, x.SettingsUdi)).ToList();
diff --git a/src/Umbraco.Core/Models/Blocks/BlockValue.cs b/src/Umbraco.Core/Models/Blocks/BlockValue.cs
new file mode 100644
index 0000000000..4700ddfd3b
--- /dev/null
+++ b/src/Umbraco.Core/Models/Blocks/BlockValue.cs
@@ -0,0 +1,18 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Models.Blocks
+{
+ public class BlockValue
+ {
+ [JsonProperty("layout")]
+ public IDictionary Layout { get; set; }
+
+ [JsonProperty("contentData")]
+ public List ContentData { get; set; } = new List();
+
+ [JsonProperty("settingsData")]
+ public List SettingsData { get; set; } = new List();
+ }
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index d7a1251f20..73af567cbc 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -134,8 +134,10 @@
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
index 68b19abf08..e3c2913e4e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
@@ -517,7 +517,7 @@
}
function handleHttpException(err) {
- if (!err.status) {
+ if (err && !err.status) {
$exceptionHandler(err);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
index 7496c15b52..e3e1e31a53 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js
@@ -25,7 +25,7 @@
if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; }
var variant = elementModel.variants[0];
-
+
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
@@ -36,7 +36,7 @@
}
}
}
-
+
}
/**
@@ -44,11 +44,11 @@
* needs to stay simple to avoid deep watching.
*/
function mapToPropertyModel(elementModel, dataModel) {
-
+
if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; }
var variant = elementModel.variants[0];
-
+
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
@@ -59,7 +59,7 @@
}
}
}
-
+
}
/**
@@ -98,13 +98,13 @@
}
}
-
+
/**
* Generate label for Block, uses either the labelInterpolator or falls back to the contentTypeName.
* @param {Object} blockObject BlockObject to recive data values from.
*/
function getBlockLabel(blockObject) {
- if(blockObject.labelInterpolator !== undefined) {
+ if (blockObject.labelInterpolator !== undefined) {
// We are just using the data model, since its a key/value object that is live synced. (if we need to add additional vars, we could make a shallow copy and apply those.)
return blockObject.labelInterpolator(blockObject.data);
}
@@ -133,7 +133,7 @@
// But we like to sync non-primitive values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript.
// Non-primitive values act as references to the same data and are therefor synced.
blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + field + ".variants[0].tabs[" + t + "].properties[" + p + "].value", watcherCreator(blockObject, prop)));
-
+
// We also like to watch our data model to be able to capture changes coming from other places.
if (forSettings === true) {
blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "settingsData" + "." + prop.alias, createLayoutSettingsModelWatcher(blockObject, prop)));
@@ -151,8 +151,8 @@
/**
* Used to create a prop watcher for the data in the property editor data model.
*/
- function createDataModelWatcher(blockObject, prop) {
- return function() {
+ function createDataModelWatcher(blockObject, prop) {
+ return function () {
if (prop.value !== blockObject.data[prop.alias]) {
// sync data:
@@ -165,8 +165,8 @@
/**
* Used to create a prop watcher for the settings in the property editor data model.
*/
- function createLayoutSettingsModelWatcher(blockObject, prop) {
- return function() {
+ function createLayoutSettingsModelWatcher(blockObject, prop) {
+ return function () {
if (prop.value !== blockObject.settingsData[prop.alias]) {
// sync data:
prop.value = blockObject.settingsData[prop.alias];
@@ -177,8 +177,8 @@
/**
* Used to create a scoped watcher for a content property on a blockObject.
*/
- function createContentModelPropWatcher(blockObject, prop) {
- return function() {
+ function createContentModelPropWatcher(blockObject, prop) {
+ return function () {
if (blockObject.data[prop.alias] !== prop.value) {
// sync data:
blockObject.data[prop.alias] = prop.value;
@@ -191,8 +191,8 @@
/**
* Used to create a scoped watcher for a settings property on a blockObject.
*/
- function createSettingsModelPropWatcher(blockObject, prop) {
- return function() {
+ function createSettingsModelPropWatcher(blockObject, prop) {
+ return function () {
if (blockObject.settingsData[prop.alias] !== prop.value) {
// sync data:
blockObject.settingsData[prop.alias] = prop.value;
@@ -255,17 +255,17 @@
this.isolatedScope = scopeOfExistance.$new(true);
this.isolatedScope.blockObjects = {};
-
+
this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this)));
this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this)));
};
-
+
BlockEditorModelObject.prototype = {
update: function (propertyModelValue, propertyEditorScope) {
// clear watchers
- this.__watchers.forEach(w => { w(); });
+ this.__watchers.forEach(w => { w(); });
delete this.__watchers;
// clear block objects
@@ -293,7 +293,7 @@
* @param {string} key contentTypeKey to recive the configuration model for.
* @returns {Object | null} Configuration model for the that specific block. Or ´null´ if the contentTypeKey isnt available in the current block configurations.
*/
- getBlockConfiguration: function(key) {
+ getBlockConfiguration: function (key) {
return this.blockConfigurations.find(bc => bc.contentTypeKey === key) || null;
},
@@ -305,7 +305,7 @@
* @param {Object} blockObject BlockObject to receive data values from.
* @returns {Promise} A Promise object which resolves when all scaffold models are loaded.
*/
- load: function() {
+ load: function () {
var tasks = [];
var scaffoldKeys = [];
@@ -339,7 +339,7 @@
* @description Retrive a list of aliases that are available for content of blocks in this property editor, does not contain aliases of block settings.
* @return {Array} array of strings representing alias.
*/
- getAvailableAliasesForBlockContent: function() {
+ getAvailableAliasesForBlockContent: function () {
return this.blockConfigurations.map(blockConfiguration => this.getScaffoldFromKey(blockConfiguration.contentTypeKey).contentTypeAlias);
},
@@ -351,13 +351,13 @@
* The purpose of this data is to provide it for the Block Picker.
* @return {Array} array of objects representing available blocks, each object containing properties blockConfigModel and elementTypeModel.
*/
- getAvailableBlocksForBlockPicker: function() {
+ getAvailableBlocksForBlockPicker: function () {
var blocks = [];
this.blockConfigurations.forEach(blockConfiguration => {
var scaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey);
- if(scaffold) {
+ if (scaffold) {
blocks.push({
blockConfigModel: blockConfiguration,
elementTypeModel: scaffold.documentType
@@ -376,7 +376,7 @@
* @param {string} key contentTypeKey to recive the scaffold model for.
* @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context.
*/
- getScaffoldFromKey: function(contentTypeKey) {
+ getScaffoldFromKey: function (contentTypeKey) {
return this.scaffolds.find(o => o.contentTypeKey === contentTypeKey);
},
@@ -388,7 +388,7 @@
* @param {string} alias contentTypeAlias to recive the scaffold model for.
* @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context.
*/
- getScaffoldFromAlias: function(contentTypeAlias) {
+ getScaffoldFromAlias: function (contentTypeAlias) {
return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias);
},
@@ -413,14 +413,14 @@
* @param {Object} layoutEntry the layout entry object to build the block model from.
* @return {Object | null} The BlockObject for the given layout entry. Or null if data or configuration wasnt found for this block.
*/
- getBlockObject: function(layoutEntry) {
+ getBlockObject: function (layoutEntry) {
- var udi = layoutEntry.udi;
+ var udi = layoutEntry.contentUdi;
var dataModel = this._getDataByUdi(udi);
if (dataModel === null) {
- console.error("Couldnt find content model of " + udi)
+ console.error("Couldn't find content model of " + udi)
return null;
}
@@ -428,11 +428,12 @@
var contentScaffold;
if (blockConfiguration === null) {
- console.error("The block entry of "+udi+" is not begin initialized cause its contentTypeKey is not allowed for this PropertyEditor");
- } else {
+ console.error("The block entry of " + udi + " is not being initialized because its contentTypeKey is not allowed for this PropertyEditor");
+ }
+ else {
contentScaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey);
- if(contentScaffold === null) {
- console.error("The block entry of "+udi+" is not begin initialized cause its Element Type was not loaded.");
+ if (contentScaffold === null) {
+ console.error("The block entry of " + udi + " is not begin initialized cause its Element Type was not loaded.");
}
}
@@ -443,12 +444,12 @@
unsupported: true
};
contentScaffold = {};
-
+
}
var blockObject = {};
// Set an angularJS cloneNode method, to avoid this object begin cloned.
- blockObject.cloneNode = function() {
+ blockObject.cloneNode = function () {
return null;// angularJS accept this as a cloned value as long as the
}
blockObject.key = String.CreateGuid().replace(/-/g, "");
@@ -465,7 +466,7 @@
this.__scope.$evalAsync();
}
}.bind(blockObject)
- , 10);
+ , 10);
// make basics from scaffold
blockObject.content = Utilities.copy(contentScaffold);
@@ -505,7 +506,7 @@
}
}
- blockObject.retrieveValuesFrom = function(content, settings) {
+ blockObject.retrieveValuesFrom = function (content, settings) {
if (this.content !== null) {
mapElementValues(content, this.content);
}
@@ -545,7 +546,7 @@
delete this.settingsData;
delete this.content;
delete this.settings;
-
+
// remove model from isolatedScope.
delete this.__scope.blockObjects["_" + this.key];
// NOTE: It seems like we should call this.__scope.$destroy(); since that is the only way to remove a scope from it's parent,
@@ -555,7 +556,7 @@
// removes this method, making it impossible to destroy again.
delete this.destroy;
-
+
// lets remove the key to make things blow up if this is still referenced:
delete this.key;
}
@@ -580,7 +581,7 @@
}
this.destroyBlockObject(blockObject);
this.removeDataByUdi(udi);
- if(settingsUdi) {
+ if (settingsUdi) {
this.removeSettingsByUdi(settingsUdi);
}
},
@@ -592,7 +593,7 @@
* @description Destroys the Block Model, but all data is kept.
* @param {Object} blockObject The BlockObject to be destroyed.
*/
- destroyBlockObject: function(blockObject) {
+ destroyBlockObject: function (blockObject) {
blockObject.destroy();
},
@@ -604,13 +605,13 @@
* @param {object} defaultStructure if no data exist the layout of your poerty editor will be set to this object.
* @return {Object} Layout object, structure depends on the model of your property editor.
*/
- getLayout: function(defaultStructure) {
+ getLayout: function (defaultStructure) {
if (!this.value.layout[this.propertyEditorAlias]) {
this.value.layout[this.propertyEditorAlias] = defaultStructure;
}
return this.value.layout[this.propertyEditorAlias];
},
-
+
/**
* @ngdoc method
* @name create
@@ -619,21 +620,21 @@
* @param {string} contentTypeKey the contentTypeKey of the block you wish to create, if contentTypeKey is not avaiable in the block configuration then ´null´ will be returned.
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or null if contentTypeKey is unavaiaible.
*/
- create: function(contentTypeKey) {
-
+ create: function (contentTypeKey) {
+
var blockConfiguration = this.getBlockConfiguration(contentTypeKey);
- if(blockConfiguration === null) {
+ if (blockConfiguration === null) {
return null;
}
var entry = {
- udi: this._createDataEntry(contentTypeKey)
+ contentUdi: this._createDataEntry(contentTypeKey)
}
if (blockConfiguration.settingsElementTypeKey != null) {
entry.settingsUdi = this._createSettingsEntry(blockConfiguration.settingsElementTypeKey)
}
-
+
return entry;
},
@@ -644,19 +645,19 @@
* @description Insert data from ElementType Model
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or ´null´ if the given ElementType isnt supported by the block configuration.
*/
- createFromElementType: function(elementTypeDataModel) {
+ createFromElementType: function (elementTypeDataModel) {
elementTypeDataModel = Utilities.copy(elementTypeDataModel);
var contentTypeKey = elementTypeDataModel.contentTypeKey;
var layoutEntry = this.create(contentTypeKey);
- if(layoutEntry === null) {
+ if (layoutEntry === null) {
return null;
}
var dataModel = this._getDataByUdi(layoutEntry.udi);
- if(dataModel === null) {
+ if (dataModel === null) {
return null;
}
@@ -672,7 +673,7 @@
* @methodOf umbraco.services.blockEditorModelObject
* @description Force immidiate update of the blockobject models to the property model.
*/
- sync: function() {
+ sync: function () {
for (const key in this.isolatedScope.blockObjects) {
this.isolatedScope.blockObjects[key].sync();
}
@@ -680,7 +681,7 @@
// private
// TODO: Then this can just be a method in the outer scope
- _createDataEntry: function(elementTypeKey) {
+ _createDataEntry: function (elementTypeKey) {
var content = {
contentTypeKey: elementTypeKey,
udi: udiService.create("element")
@@ -690,7 +691,7 @@
},
// private
// TODO: Then this can just be a method in the outer scope
- _getDataByUdi: function(udi) {
+ _getDataByUdi: function (udi) {
return this.value.contentData.find(entry => entry.udi === udi) || null;
},
@@ -699,10 +700,10 @@
* @name removeDataByUdi
* @methodOf umbraco.services.blockEditorModelObject
* @description Removes the content data of a given UDI.
- * Notice this method does not remove the block from your layout, this will need to be handlede by the Property Editor since this services donst know about your layout structure.
+ * Notice this method does not remove the block from your layout, this will need to be handled by the Property Editor since this services don't know about your layout structure.
* @param {string} udi The UDI of the content data to be removed.
*/
- removeDataByUdi: function(udi) {
+ removeDataByUdi: function (udi) {
const index = this.value.contentData.findIndex(o => o.udi === udi);
if (index !== -1) {
this.value.contentData.splice(index, 1);
@@ -711,7 +712,7 @@
// private
// TODO: Then this can just be a method in the outer scope
- _createSettingsEntry: function(elementTypeKey) {
+ _createSettingsEntry: function (elementTypeKey) {
var settings = {
contentTypeKey: elementTypeKey,
udi: udiService.create("element")
@@ -722,7 +723,7 @@
// private
// TODO: Then this can just be a method in the outer scope
- _getSettingsByUdi: function(udi) {
+ _getSettingsByUdi: function (udi) {
return this.value.settingsData.find(entry => entry.udi === udi) || null;
},
@@ -731,10 +732,10 @@
* @name removeSettingsByUdi
* @methodOf umbraco.services.blockEditorModelObject
* @description Removes the settings data of a given UDI.
- * Notice this method does not remove the settingsUdi from your layout, this will need to be handlede by the Property Editor since this services donst know about your layout structure.
+ * Notice this method does not remove the settingsUdi from your layout, this will need to be handled by the Property Editor since this services don't know about your layout structure.
* @param {string} udi The UDI of the settings data to be removed.
*/
- removeSettingsByUdi: function(udi) {
+ removeSettingsByUdi: function (udi) {
const index = this.value.settingsData.findIndex(o => o.udi === udi);
if (index !== -1) {
this.value.settingsData.splice(index, 1);
@@ -747,13 +748,13 @@
* @methodOf umbraco.services.blockEditorModelObject
* @description Notice you should not need to destroy the BlockEditorModelObject since it will automaticly be destroyed when the scope of existance gets destroyed.
*/
- destroy: function() {
+ destroy: function () {
this.__watchers.forEach(w => { w(); });
for (const key in this.isolatedScope.blockObjects) {
this.destroyBlockObject(this.isolatedScope.blockObjects[key]);
}
-
+
delete this.__watchers;
delete this.value;
delete this.propertyEditorAlias;
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js
index b2fdf37aa4..7f8212f2c6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js
@@ -504,7 +504,6 @@ function serverValidationManager($timeout) {
// add a generic error for the property
addPropertyError(propertyValidationKey, culture, htmlFieldReference, value && Array.isArray(value) && value.length > 0 ? value[0] : null, segment);
- hasPropertyErrors = true;
}
else {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
index aa66472778..45ba6ddaa7 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
@@ -132,7 +132,13 @@
// Called when we save the value, the server may return an updated data and our value is re-synced
// we need to deal with that here so that our model values are all in sync so we basically re-initialize.
- function onServerValueChanged(newVal, oldVal) {
+ function onServerValueChanged(newVal, oldVal) {
+
+ // We need to ensure that the property model value is an object, this is needed for modelObject to recive a reference and keep that updated.
+ if (typeof newVal !== 'object' || newVal === null) {// testing if we have null or undefined value or if the value is set to another type than Object.
+ newVal = {};
+ }
+
modelObject.update(newVal, $scope);
onLoaded();
}
@@ -148,6 +154,8 @@
// Store a reference to the layout model, because we need to maintain this model.
vm.layout = modelObject.getLayout([]);
+ var invalidLayoutItems = [];
+
// Append the blockObjects to our layout.
vm.layout.forEach(entry => {
// $block must have the data property to be a valid BlockObject, if not its considered as a destroyed blockObject.
@@ -155,9 +163,22 @@
var block = getBlockObject(entry);
// If this entry was not supported by our property-editor it would return 'null'.
- if(block !== null) {
+ if (block !== null) {
entry.$block = block;
}
+ else {
+ // then we need to filter this out and also update the underlying model. This could happen if the data
+ // is invalid for some reason or the data structure has changed.
+ invalidLayoutItems.push(entry);
+ }
+ }
+ });
+
+ // remove the ones that are invalid
+ invalidLayoutItems.forEach(entry => {
+ var index = vm.layout.findIndex(x => x === entry);
+ if (index >= 0) {
+ vm.layout.splice(index, 1);
}
});
@@ -240,7 +261,7 @@
function deleteBlock(block) {
- var layoutIndex = vm.layout.findIndex(entry => entry.udi === block.content.udi);
+ var layoutIndex = vm.layout.findIndex(entry => entry.contentUdi === block.content.udi);
if (layoutIndex === -1) {
throw new Error("Could not find layout entry of block with udi: "+block.content.udi)
}
diff --git a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs
index 866bd38e05..c20bd08204 100644
--- a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using static Umbraco.Core.Models.Blocks.BlockEditorData;
+using static Umbraco.Core.Models.Blocks.BlockItemData;
namespace Umbraco.Web.PropertyEditors
{
@@ -63,8 +64,12 @@ namespace Umbraco.Web.PropertyEditors
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
var result = new List();
+ var blockEditorData = _blockEditorValues.DeserializeAndClean(rawJson);
+ if (blockEditorData == null)
+ return Enumerable.Empty();
- foreach (var row in _blockEditorValues.GetPropertyValues(rawJson))
+ // TODO: What about Settings?
+ foreach (var row in blockEditorData.BlockValue.ContentData)
{
foreach (var prop in row.PropertyValues)
{
@@ -83,6 +88,132 @@ namespace Umbraco.Web.PropertyEditors
return result;
}
+
+ #region Convert database // editor
+
+ // note: there is NO variant support here
+
+ ///
+ /// Ensure that sub-editor values are translated through their ToEditor methods
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null)
+ {
+ var val = property.GetValue(culture, segment);
+
+ BlockEditorData blockEditorData;
+ try
+ {
+ blockEditorData = _blockEditorValues.DeserializeAndClean(val);
+ }
+ catch (JsonSerializationException)
+ {
+ // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format.
+ return string.Empty;
+ }
+
+ if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0)
+ return string.Empty;
+
+ foreach (var row in blockEditorData.BlockValue.ContentData)
+ {
+ foreach (var prop in row.PropertyValues)
+ {
+ try
+ {
+ // create a temp property with the value
+ // - force it to be culture invariant as the block editor can't handle culture variant element properties
+ prop.Value.PropertyType.Variations = ContentVariation.Nothing;
+ var tempProp = new Property(prop.Value.PropertyType);
+
+ tempProp.SetValue(prop.Value.Value);
+
+ // convert that temp property, and store the converted value
+ var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
+ if (propEditor == null)
+ {
+ // NOTE: This logic was borrowed from Nested Content and I'm unsure why it exists.
+ // if the property editor doesn't exist I think everything will break anyways?
+ // update the raw value since this is what will get serialized out
+ row.RawPropertyValues[prop.Key] = tempProp.GetValue()?.ToString();
+ continue;
+ }
+
+ var tempConfig = dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration;
+ var valEditor = propEditor.GetValueEditor(tempConfig);
+ var convValue = valEditor.ToEditor(tempProp, dataTypeService);
+
+ // update the raw value since this is what will get serialized out
+ row.RawPropertyValues[prop.Key] = convValue;
+ }
+ catch (InvalidOperationException)
+ {
+ // deal with weird situations by ignoring them (no comment)
+ row.PropertyValues.Remove(prop.Key);
+ }
+ }
+ }
+
+ // return json convertable object
+ return blockEditorData.BlockValue;
+ }
+
+ ///
+ /// Ensure that sub-editor values are translated through their FromEditor methods
+ ///
+ ///
+ ///
+ ///
+ public override object FromEditor(ContentPropertyData editorValue, object currentValue)
+ {
+ if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
+ return null;
+
+ BlockEditorData blockEditorData;
+ try
+ {
+ blockEditorData = _blockEditorValues.DeserializeAndClean(editorValue.Value);
+ }
+ catch (JsonSerializationException)
+ {
+ // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format.
+ return string.Empty;
+ }
+
+ if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0)
+ return string.Empty;
+
+ foreach (var row in blockEditorData.BlockValue.ContentData)
+ {
+ foreach (var prop in row.PropertyValues)
+ {
+ // Fetch the property types prevalue
+ var propConfiguration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration;
+
+ // Lookup the property editor
+ var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
+ if (propEditor == null) continue;
+
+ // Create a fake content property data object
+ var contentPropData = new ContentPropertyData(prop.Value.Value, propConfiguration);
+
+ // Get the property editor to do it's conversion
+ var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, prop.Value.Value);
+
+ // update the raw value since this is what will get serialized out
+ row.RawPropertyValues[prop.Key] = newValue;
+ }
+ }
+
+ // return json
+ return JsonConvert.SerializeObject(blockEditorData.BlockValue);
+ }
+
+ #endregion
}
internal class BlockEditorValidator : ComplexEditorValidator
@@ -96,19 +227,26 @@ namespace Umbraco.Web.PropertyEditors
protected override IEnumerable GetElementTypeValidation(object value)
{
- foreach (var row in _blockEditorValues.GetPropertyValues(value))
+ var blockEditorData = _blockEditorValues.DeserializeAndClean(value);
+ if (blockEditorData != null)
{
- var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Id);
- foreach (var prop in row.PropertyValues)
+ foreach (var row in blockEditorData.BlockValue.ContentData)
{
- elementValidation.AddPropertyTypeValidation(
- new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value));
+ var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Key);
+ foreach (var prop in row.PropertyValues)
+ {
+ elementValidation.AddPropertyTypeValidation(
+ new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value));
+ }
+ yield return elementValidation;
}
- yield return elementValidation;
}
}
}
+ ///
+ /// Used to deserialize json values and clean up any values based on the existence of element types and layout structure
+ ///
internal class BlockEditorValues
{
private readonly Lazy> _contentTypes;
@@ -126,81 +264,77 @@ namespace Umbraco.Web.PropertyEditors
return contentType;
}
- public IReadOnlyList GetPropertyValues(object propertyValue)
+ public BlockEditorData DeserializeAndClean(object propertyValue)
{
if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString()))
- return new List();
+ return null;
- var converted = _dataConverter.Convert(propertyValue.ToString());
+ var blockEditorData = _dataConverter.Deserialize(propertyValue.ToString());
- if (converted.ContentData.Count == 0)
- return new List();
-
- var contentTypePropertyTypes = new Dictionary>();
- var result = new List();
-
- foreach(var block in converted.ContentData)
+ if (blockEditorData.BlockValue.ContentData.Count == 0)
{
- var contentType = GetElementType(block);
- if (contentType == null)
- continue;
-
- // get the prop types for this content type but keep a dictionary of found ones so we don't have to keep re-looking and re-creating
- // objects on each iteration.
- if (!contentTypePropertyTypes.TryGetValue(contentType.Alias, out var propertyTypes))
- propertyTypes = contentTypePropertyTypes[contentType.Alias] = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x);
-
- var propValues = new Dictionary();
-
- // find any keys that are not real property types and remove them
- foreach (var prop in block.RawPropertyValues.ToList())
- {
- // doesn't exist so remove it
- if (!propertyTypes.TryGetValue(prop.Key, out var propType))
- {
- block.RawPropertyValues.Remove(prop.Key);
- }
- else
- {
- // set the value to include the resolved property type
- propValues[prop.Key] = new BlockPropertyValue
- {
- PropertyType = propType,
- Value = prop.Value
- };
- }
- }
-
- result.Add(new BlockValue
- {
- ContentTypeAlias = contentType.Alias,
- PropertyValues = propValues,
- Id = ((GuidUdi)block.Udi).Guid
- });
+ // if there's no content ensure there's no settings too
+ blockEditorData.BlockValue.SettingsData.Clear();
+ return null;
}
- return result;
+ var contentTypePropertyTypes = new Dictionary>();
+
+ // filter out any content that isn't referenced in the layout references
+ foreach(var block in blockEditorData.BlockValue.ContentData.Where(x => blockEditorData.References.Any(r => r.ContentUdi == x.Udi)))
+ {
+ ResolveBlockItemData(block, contentTypePropertyTypes);
+ }
+ // filter out any settings that isn't referenced in the layout references
+ foreach (var block in blockEditorData.BlockValue.SettingsData.Where(x => blockEditorData.References.Any(r => r.SettingsUdi == x.Udi)))
+ {
+ ResolveBlockItemData(block, contentTypePropertyTypes);
+ }
+
+ // remove blocks that couldn't be resolved
+ blockEditorData.BlockValue.ContentData.RemoveAll(x => x.ContentTypeAlias.IsNullOrWhiteSpace());
+ blockEditorData.BlockValue.SettingsData.RemoveAll(x => x.ContentTypeAlias.IsNullOrWhiteSpace());
+
+ return blockEditorData;
}
- ///
- /// Used during deserialization to populate the property value/property type of a nested content row property
- ///
- internal class BlockPropertyValue
+ private bool ResolveBlockItemData(BlockItemData block, Dictionary> contentTypePropertyTypes)
{
- public object Value { get; set; }
- public PropertyType PropertyType { get; set; }
- }
+ var contentType = GetElementType(block);
+ if (contentType == null)
+ return false;
- ///
- /// Used during deserialization to populate the content type alias and property values of a block
- ///
- internal class BlockValue
- {
- public Guid Id { get; set; }
- public string ContentTypeAlias { get; set; }
- public IDictionary PropertyValues { get; set; } = new Dictionary();
- }
+ // get the prop types for this content type but keep a dictionary of found ones so we don't have to keep re-looking and re-creating
+ // objects on each iteration.
+ if (!contentTypePropertyTypes.TryGetValue(contentType.Alias, out var propertyTypes))
+ propertyTypes = contentTypePropertyTypes[contentType.Alias] = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x);
+ var propValues = new Dictionary();
+
+ // find any keys that are not real property types and remove them
+ foreach (var prop in block.RawPropertyValues.ToList())
+ {
+ // doesn't exist so remove it
+ if (!propertyTypes.TryGetValue(prop.Key, out var propType))
+ {
+ block.RawPropertyValues.Remove(prop.Key);
+ }
+ else
+ {
+ // set the value to include the resolved property type
+ propValues[prop.Key] = new BlockPropertyValue
+ {
+ PropertyType = propType,
+ Value = prop.Value
+ };
+ }
+ }
+
+ block.ContentTypeAlias = contentType.Alias;
+ block.PropertyValues = propValues;
+
+ return true;
+ }
}
#endregion
diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
index 4767dc19cd..ac9e0624bb 100644
--- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
@@ -133,13 +133,20 @@ namespace Umbraco.Web.PropertyEditors
#endregion
-
+
#region Convert database // editor
// note: there is NO variant support here
- // TODO: What does this do?
+ ///
+ /// Ensure that sub-editor values are translated through their ToEditor methods
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null)
{
var val = property.GetValue(culture, segment);
@@ -186,11 +193,16 @@ namespace Umbraco.Web.PropertyEditors
}
}
- // return json
+ // return the object, there's a native json converter for this so it will serialize correctly
return rows;
}
- // TODO: What does this do?
+ ///
+ /// Ensure that sub-editor values are translated through their FromEditor methods
+ ///
+ ///
+ ///
+ ///
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
{
if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs
index 83c612e9f7..f043c8e66e 100644
--- a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs
+++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
}
public IPublishedElement ConvertToElement(
- BlockEditorData.BlockItemData data,
+ BlockItemData data,
PropertyCacheLevel referenceCacheLevel, bool preview)
{
// hack! we need to cast, we have no choice beacuse we cannot make breaking changes.
diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
index 2b04106288..4d972f7a33 100644
--- a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
+++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
@@ -62,20 +62,20 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
var converter = new BlockListEditorDataConverter();
- var converted = converter.Convert(value);
- if (converted.ContentData.Count == 0) return BlockListModel.Empty;
+ var converted = converter.Deserialize(value);
+ if (converted.BlockValue.ContentData.Count == 0) return BlockListModel.Empty;
var blockListLayout = converted.Layout.ToObject>();
// convert the content data
- foreach (var data in converted.ContentData)
+ foreach (var data in converted.BlockValue.ContentData)
{
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
contentPublishedElements[element.Key] = element;
}
// convert the settings data
- foreach (var data in converted.SettingsData)
+ foreach (var data in converted.BlockValue.SettingsData)
{
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;