diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs
index f30a53c8b6..23df9e485f 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs
@@ -94,12 +94,17 @@
/// A fallback strategy.
/// An optional default value.
/// The fallback value.
+ /// The property that does not have a value.
/// A value indicating whether a fallback value could be provided.
///
/// This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.
+ /// In an , because walking up the tree is possible, the content itself may not even
+ /// have a property with the specified alias, but such a property may exist up in the tree. The
+ /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the
+ /// converter's interpretation of "no value".
///
- bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
+ bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value, out IPublishedProperty noValueProperty);
///
/// Tries to get a fallback value for a published content property.
@@ -112,11 +117,16 @@
/// A fallback strategy.
/// An optional default value.
/// The fallback value.
+ /// The property that does not have a value.
/// A value indicating whether a fallback value could be provided.
///
/// This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.
+ /// In an , because walking up the tree is possible, the content itself may not even
+ /// have a property with the specified alias, but such a property may exist up in the tree. The
+ /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the
+ /// converter's interpretation of "no value".
///
- bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
+ bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty);
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs
index cd7b063d44..245bbd1d39 100644
--- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs
@@ -37,16 +37,18 @@
}
///
- public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
+ public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value, out IPublishedProperty noValueProperty)
{
value = default;
+ noValueProperty = default;
return false;
}
///
- public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
+ public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty)
{
value = default;
+ noValueProperty = default;
return false;
}
}
diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs
index cfc45b8f53..bd4f1610cd 100644
--- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs
+++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs
@@ -207,7 +207,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance());
var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result));
- DoAssert(doc, 1234, key, templateIdVal: null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2);
+ DoAssert(doc, 1234, key, null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2);
Assert.AreEqual(null, doc.Parent);
}
@@ -223,7 +223,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetInstance());
var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true));
- DoAssert(doc, 2000, key, templateIdVal: null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1);
+ DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1);
Assert.AreEqual(null, doc.Parent);
Assert.AreEqual(2, doc.Children.Count());
Assert.AreEqual(2001, doc.Children.ElementAt(0).Id);
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs
index 7a96f670e6..42703f9bae 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using Moq;
using NUnit.Framework;
+using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
@@ -59,17 +60,23 @@ namespace Umbraco.Tests.PublishedContent
internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache)
{
- var prop1Type = factory.CreatePropertyType("prop1", 1);
- var welcomeType = factory.CreatePropertyType("welcomeText", 1);
- var welcome2Type = factory.CreatePropertyType("welcomeText2", 1);
+ var prop1Type = factory.CreatePropertyType("prop1", 1, variations: ContentVariation.Culture);
+ var welcomeType = factory.CreatePropertyType("welcomeText", 1, variations: ContentVariation.Culture);
+ var welcome2Type = factory.CreatePropertyType("welcomeText2", 1, variations: ContentVariation.Culture);
+ var nopropType = factory.CreatePropertyType("noprop", 1, variations: ContentVariation.Culture);
+
var props = new[]
{
prop1Type,
welcomeType,
welcome2Type,
+ nopropType
};
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props);
+ var prop3Type = factory.CreatePropertyType("prop3", 1, variations: ContentVariation.Culture);
+ var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), new[] { prop3Type });
+
var prop1 = new SolidPublishedPropertyWithLanguageVariants
{
Alias = "welcomeText",
@@ -98,6 +105,14 @@ namespace Umbraco.Tests.PublishedContent
prop3.SetSourceValue("en-US", "Welcome", true);
prop3.SetValue("en-US", "Welcome", true);
+ var noprop = new SolidPublishedProperty
+ {
+ Alias = "noprop",
+ PropertyType = nopropType
+ };
+ noprop.SolidHasValue = false; // has no value
+ noprop.SolidValue = "xxx"; // but returns something
+
var item1 = new SolidPublishedContent(contentType1)
{
Id = 1,
@@ -111,7 +126,7 @@ namespace Umbraco.Tests.PublishedContent
ChildIds = new[] { 2 },
Properties = new Collection
{
- prop1, prop2
+ prop1, prop2, noprop
}
};
@@ -125,18 +140,47 @@ namespace Umbraco.Tests.PublishedContent
Level = 2,
Url = "/content-1/content-2",
ParentId = 1,
- ChildIds = new int[] { },
+ ChildIds = new int[] { 3 },
Properties = new Collection
{
prop3
}
};
+ var prop4 = new SolidPublishedPropertyWithLanguageVariants
+ {
+ Alias = "prop3",
+ PropertyType = prop3Type
+ };
+ prop4.SetSourceValue("en-US", "Oxxo", true);
+ prop4.SetValue("en-US", "Oxxo", true);
+
+ var item3 = new SolidPublishedContent(contentType2)
+ {
+ Id = 3,
+ SortOrder = 0,
+ Name = "Content 3",
+ UrlSegment = "content-3",
+ Path = "/1/2/3",
+ Level = 3,
+ Url = "/content-1/content-2/content-3",
+ ParentId = 2,
+ ChildIds = new int[] { },
+ Properties = new Collection
+ {
+ prop4
+ }
+ };
+
item1.Children = new List { item2 };
item2.Parent = item1;
+ item2.Children = new List { item3 };
+ item3.Parent = item2;
+
cache.Add(item1);
cache.Add(item2);
+ cache.Add(item3);
}
[Test]
@@ -211,10 +255,40 @@ namespace Umbraco.Tests.PublishedContent
Assert.AreEqual("Welcome", value);
}
+ [Test]
+ public void Do_Not_Get_Content_Recursively_Unless_Requested2()
+ {
+ var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First().Children().First();
+ Assert.IsNull(content.GetProperty("welcomeText2"));
+ var value = content.Value("welcomeText2");
+ Assert.IsNull(value);
+ }
+
+ [Test]
+ public void Can_Get_Content_Recursively2()
+ {
+ var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First().Children().First();
+ Assert.IsNull(content.GetProperty("welcomeText2"));
+ var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors);
+ Assert.AreEqual("Welcome", value);
+ }
+
+ [Test]
+ public void Can_Get_Content_Recursively3()
+ {
+ var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First().Children().First();
+ Assert.IsNull(content.GetProperty("noprop"));
+ var value = content.Value("noprop", fallback: Fallback.ToAncestors);
+ // property has no value but we still get the value (ie, the converter would do something)
+ Assert.AreEqual("xxx", value);
+ }
+
[Test]
public void Can_Get_Content_With_Recursive_Priority()
{
+ Current.VariationContextAccessor.VariationContext = new VariationContext("nl");
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
+
var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language));
// No Dutch value is directly assigned. Check has fallen back to Dutch value from parent.
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
index 705b2fd826..ab576171f4 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs
@@ -792,29 +792,6 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsTrue(customDoc3.IsDescendantOrSelf(customDoc3));
}
- [Test]
- public void Up()
- {
- var doc = GetNode(1173);
-
- var result = doc.Up();
-
- Assert.IsNotNull(result);
-
- Assert.AreEqual(1046, result.Id);
- }
-
- [Test]
- public void Down()
- {
- var doc = GetNode(1173);
-
- var result = doc.Down();
-
- Assert.IsNotNull(result);
-
- Assert.AreEqual(1174, result.Id);
- }
[Test]
public void FragmentProperty()
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
index eecdd7eadc..d54f479f0a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
@@ -4,10 +4,10 @@
*
* @description
* Added in Umbraco 8.0. Application-wide service for handling infinite editing.
- *
*
- *
- *
+ *
+ *
+ *
Open a build-in infinite editor (media picker)
Markup example
@@ -97,7 +97,7 @@ When building a custom infinite editor view you can use the same components as a
hide-icon="true"
hide-description="true">
-
+
@@ -166,7 +166,7 @@ When building a custom infinite editor view you can use the same components as a
let editorsKeyboardShorcuts = [];
var editors = [];
-
+
/**
* @ngdoc method
* @name umbraco.services.editorService#getEditors
@@ -220,7 +220,7 @@ When building a custom infinite editor view you can use the same components as a
editors: editors,
editor: editor
};
-
+
eventsService.emit("appState.editors.open", args);
}
@@ -245,7 +245,7 @@ When building a custom infinite editor view you can use the same components as a
// emit event to let components know an editor has been removed
eventsService.emit("appState.editors.close", args);
-
+
// delay required to map the properties to the correct editor due
// to another delay in the closing animation of the editor
$timeout(function() {
@@ -287,7 +287,7 @@ When building a custom infinite editor view you can use the same components as a
* @param {Boolean} editor.create Create new content item
* @param {Function} editor.submit Callback function when the publish and close button is clicked. Returns the editor model object
* @param {Function} editor.close Callback function when the close button is clicked.
- *
+ *
* @returns {Object} editor object
*/
function contentEditor(editor) {
@@ -302,12 +302,12 @@ When building a custom infinite editor view you can use the same components as a
*
* @description
* Opens a content picker in infinite editing, the submit callback returns an array of selected items
- *
+ *
* @param {Object} editor rendering options
* @param {Boolean} editor.multiPicker Pick one or multiple items
* @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object
* @param {Function} editor.close Callback function when the close button is clicked.
- *
+ *
* @returns {Object} editor object
*/
function contentPicker(editor) {
@@ -523,7 +523,6 @@ When building a custom infinite editor view you can use the same components as a
*/
function queryBuilder(editor) {
editor.view = "views/common/infiniteeditors/querybuilder/querybuilder.html";
- editor.size = "small";
open(editor);
}
@@ -699,7 +698,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @description
* Opens the section picker in infinite editing, the submit callback returns an array of the selected items
- *
+ *
* @param {Object} editor rendering options
* @param {Array} editor.availableItems Array of available items.
* @param {Array} editor.selectedItems Array of selected items. When passed in the selected items will be filtered from the available items.
@@ -721,7 +720,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @description
* Opens a macro picker in infinite editing, the submit callback returns an array of the selected items
- *
+ *
* @param {Callback} editor.submit Submits the editor.
* @param {Callback} editor.close Closes the editor.
* @returns {Object} editor object
@@ -739,7 +738,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @description
* Opens a member group picker in infinite editing.
- *
+ *
* @param {Object} editor rendering options
* @param {Object} editor.multiPicker Pick one or multiple items.
* @param {Callback} editor.submit Submits the editor.
@@ -776,22 +775,22 @@ When building a custom infinite editor view you can use the same components as a
}
///////////////////////
-
+
/**
* @ngdoc method
* @name umbraco.services.editorService#storeKeyboardShortcuts
* @methodOf umbraco.services.editorService
*
* @description
- * Internal method to keep track of keyboard shortcuts registered
+ * Internal method to keep track of keyboard shortcuts registered
* to each editor so they can be rebound when an editor closes
- *
+ *
*/
function unbindKeyboardShortcuts() {
const shortcuts = angular.copy(keyboardService.keyboardEvent);
editorsKeyboardShorcuts.push(shortcuts);
- // unbind the current shortcuts because we only want to
+ // unbind the current shortcuts because we only want to
// shortcuts from the newly opened editor working
for (let [key, value] of Object.entries(shortcuts)) {
keyboardService.unbind(key);
@@ -805,7 +804,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @description
* Internal method to rebind keyboard shortcuts for the editor in focus
- *
+ *
*/
function rebindKeyboardShortcuts() {
// find the shortcuts from the previous editor
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js
index db3e16f8b6..f2cc0dbecb 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js
@@ -2,32 +2,14 @@
"use strict";
function InsertFieldController($scope, contentTypeResource, localizationService) {
-
+
var vm = this;
vm.field;
- vm.altField;
- vm.altText;
- vm.insertBefore;
- vm.insertAfter;
+ vm.defaultValue;
vm.recursive = false;
- vm.properties = [];
- vm.standardFields = [];
- vm.date = false;
- vm.dateTime = false;
- vm.dateTimeSeparator = "";
- vm.casingUpper = false;
- vm.casingLower = false;
- vm.encodeHtml = false;
- vm.encodeUrl = false;
- vm.convertLinebreaks = false;
+ vm.showDefaultValue = false;
- vm.showAltField = false;
- vm.showAltText = false;
-
- vm.setDateOption = setDateOption;
- vm.setCasingOption = setCasingOption;
- vm.setEncodingOption = setEncodingOption;
vm.generateOutputSample = generateOutputSample;
vm.submit = submit;
vm.close = close;
@@ -50,93 +32,29 @@
contentTypeResource.getAllStandardFields().then(function (array) {
vm.standardFields = array;
});
-
- }
- // date formatting
- function setDateOption(option) {
-
- if (option === 'date') {
- if(vm.date) {
- vm.date = false;
- } else {
- vm.date = true;
- vm.dateTime = false;
- }
- }
-
- if (option === 'dateWithTime') {
- if(vm.dateTime) {
- vm.dateTime = false;
- } else {
- vm.date = false;
- vm.dateTime = true;
- }
- }
-
- }
-
- // casing formatting
- function setCasingOption(option) {
- if (option === 'uppercase') {
- if(vm.casingUpper) {
- vm.casingUpper = false;
- } else {
- vm.casingUpper = true;
- vm.casingLower = false;
- }
- }
-
- if (option === 'lowercase') {
- if(vm.casingLower) {
- vm.casingLower = false;
- } else {
- vm.casingUpper = false;
- vm.casingLower = true;
- }
- }
- }
-
- // encoding formatting
- function setEncodingOption(option) {
- if (option === 'html') {
- if(vm.encodeHtml) {
- vm.encodeHtml = false;
- } else {
- vm.encodeHtml = true;
- vm.encodeUrl = false;
- }
- }
-
- if (option === 'url') {
- if (vm.encodeUrl) {
- vm.encodeUrl = false;
- } else {
- vm.encodeHtml = false;
- vm.encodeUrl = true;
- }
- }
}
function generateOutputSample() {
- var pageField = (vm.field !== undefined ? '@Umbraco.Field("' + vm.field + '"' : "")
- + (vm.altField !== undefined ? ', altFieldAlias:"' + vm.altField + '"' : "")
- + (vm.altText !== undefined ? ', altText:"' + vm.altText + '"' : "")
- + (vm.insertBefore !== undefined ? ', insertBefore:"' + vm.insertBefore + '"' : "")
- + (vm.insertAfter !== undefined ? ', insertAfter:"' + vm.insertAfter + '"' : "")
- + (vm.recursive !== false ? ', recursive: ' + vm.recursive : "")
- + (vm.date !== false ? ', formatAsDate: ' + vm.date : "")
- + (vm.dateTime !== false ? ', formatAsDateWithTimeSeparator:"' + vm.dateTimeSeparator + '"' : "")
- + (vm.casingUpper !== false ? ', casing: ' + "RenderFieldCaseType.Upper" : "")
- + (vm.casingLower !== false ? ', casing: ' + "RenderFieldCaseType.Lower" : "")
- + (vm.encodeHtml !== false ? ', encoding: ' + "RenderFieldEncodingType.Html" : "")
- + (vm.encodeUrl !== false ? ', encoding: ' + "RenderFieldEncodingType.Url" : "")
- + (vm.convertLinebreaks !== false ? ', convertLineBreaks: ' + "true" : "")
+ var fallback;
+
+ if(vm.recursive !== false && vm.defaultValue !== undefined){
+ fallback = "Fallback.To(Fallback.Ancestors, Fallback.DefaultValue)";
+ }else if(vm.recursive !== false){
+ fallback = "Fallback.ToAncestors";
+ }else if(vm.defaultValue !== undefined){
+ fallback = "Fallback.ToDefaultValue";
+ }
+
+ var pageField = (vm.field !== undefined ? '@Model.Value("' + vm.field + '"' : "")
+ + (fallback !== undefined? ', fallback: ' + fallback : "")
+ + (vm.defaultValue !== undefined ? ', defaultValue: new HtmlString("' + vm.defaultValue + '")' : "")
+
+ (vm.field ? ')' : "");
$scope.model.umbracoField = pageField;
-
+
return pageField;
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html
index 16f4bfb919..b2c6382b98 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html
@@ -31,46 +31,18 @@
-