Setting BlockList SettingsData programatically
# help-with-umbraco
t
Hey All, Back onto a migration im doing (Finally got a chance to jump back to this project!). Trying to set the value of a property inside of a blocklist programatically across a site based off of another property inside the same blocklist. (The value to set is a link / UDI, the value that sets this is a Label from a Color picker). This has lead me to this thus far:
Copy code
csharp

foreach (var palette in palettes)
{
    var paletteName = palette.Name;
    var paletteId = palette.Key;
    umbDocDictionary.Add(paletteName, paletteId);
}

foreach (var node in allNodes)
{
    if (node.HasProperty("sectionPicker") && node.HasValue("sectionPicker"))
    {
        var sectionPicker = node.Value<IEnumerable<BlockListItem>>("sectionPicker");
        var contentNode = _contentService.GetById(node.Id);
        var jsonValue = contentNode.GetValue("sectionPicker").ToString();
        BlockValue blockValue = JsonConvert.DeserializeObject<BlockValue>(jsonValue);

        foreach (var settingObj in blockValue.SettingsData)
        {
            foreach (var settingColumnProperty in settingObj.RawPropertyValues)
            {
                var paletteValue = "";

                if (settingColumnProperty.Key == "backgroundColor")
                {
                    var colorObj = settingColumnProperty.Value.ToString();
                    var color = JsonConvert.DeserializeObject<ColorPickerValueConverter.PickedColor>(colorObj);
                    var neededPalette = (color.Label == "Default") ? umbDocDictionary["Palette Default"] : umbDocDictionary[color.Label];

                    paletteValue = JsonConvert.SerializeObject(new[] { Udi.Create(Constants.UdiEntityType.Document, neededPalette).UriValue });
                }
            }
        }
    }
}
Ultimately, this has given me the JSON of the BlockList, Allowed me to loop through the each of the blocklist settings nodes, detect whether they have a key with the name "backgroundColor" which is the label of a color picker. Then correctly create the UDI needed to "set" in the other property which is a link (palette). Now i've hit the roadblock of actually "setting" this. Reading the docs at : https://github.com/umbraco/UmbracoDocs/blob/e64ec0e5b28b4e5a37b7865691621e45dd82701f/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Block-List-Editor/index.md I see the best way is to create a custom model that is decorated with the Umbraco.BlockList Jsonproperty to match the Json structure. My question ultimately rounds out to: Do i need to mock up the entire settings model? Or can i somehow target just the individual property? (Simply due to having a number of different BlockList settings document types across the site.) Sorry that its a bit of a mouthful of a question 😅
m
If you have the json that's required for the settings, then you could simply manipulate the blocklist settings nodes directly via json manipulation?
t
Yeah thats what im thinking of doing, just wondered if thats the "safe" way ofcourse. But ultimately imagining something like:
Copy code
csharp

column.RawPropertyValues["palette"] = JsonConvert.SerializeObject(paletteValue);
Would do
m
would think so.. might help to use jpath to isolate the setting node you want to alter?
Copy code
csharp
var settingNodes = j.SelectToken($"$..settingsData[?(@.udi=={settingsUdi})]");
 if (settingNodes.FirstOrDefault() is JObject setting)
 {
    setting["palette"] = JsonConvert.SerializeObject(paletteValue);
 }
eg from the blockgrid example
Copy code
json
{
    "layout": {
        "Umbraco.BlockGrid": [{
                "contentUdi": "umb://element/bb23fe28160941efa506da7aa314172d",
                "settingsUdi": "umb://element/9b832ee528464456a8e9a658b47a9801",
                "areas": [],
                "columnSpan": 12,
                "rowSpan": 1
            }, {
                "contentUdi": "umb://element/a11e06ca155d40b78189be0bdaf11c6d",
                "settingsUdi": "umb://element/d182a0d807fc4518b741b77c18aa73a1",
                "areas": [],
                "columnSpan": 6,
                "rowSpan": 2
            }
        ]
    },
    "contentData": [{
            "contentTypeKey": "0e9f8609-1904-4fd1-9801-ad1880825ff3",
            "udi": "umb://element/bb23fe28160941efa506da7aa314172d",
            "title": "Item one",
            "text": "This is item one"
        }, {
            "contentTypeKey": "0e9f8609-1904-4fd1-9801-ad1880825ff3",
            "udi": "umb://element/a11e06ca155d40b78189be0bdaf11c6d",
            "title": "Item two",
            "text": "This is item two"
        }
    ],
    "settingsData": [{
            "contentTypeKey": "22be457c-8249-42b8-8685-d33262f7ce2a",
            "udi": "umb://element/9b832ee528464456a8e9a658b47a9801",
            "featured": "0"
        }, {
            "contentTypeKey": "22be457c-8249-42b8-8685-d33262f7ce2a",
            "udi": "umb://element/d182a0d807fc4518b741b77c18aa73a1",
            "featured": "1"
        }
    ]
}
$..settingsData[?(@.udi=='umb://element/9b832ee528464456a8e9a658b47a9801')]
array of 1 for first contentdata. I find https://jsonpath.com/ really useful for testing.
d
I have grabbed the colour label to use for background colours on layout blocks in https://umbootstrap.com/ like so:
Copy code
@{
    if (Model?.Areas.Any() != true) { return; }

    var hasSettings = Model.Settings != null;
    var backgroundColour = hasSettings ? Model.Settings.Value<ColorPickerValueConverter.PickedColor>("layoutSettingsColourPicker") : null;
    var colorLabel = backgroundColour?.Label;

    var colorShades = hasSettings ? Model.Settings.Value<decimal>("layoutSettingsColourShades") : (decimal?)null;

    var backgroundImage = hasSettings ? Model.Settings.Value<Umbraco.Cms.Core.Models.MediaWithCrops>("layoutSettingsBackgroundImagePicker") : null;
}

@{
    string styleAttribute = "";
    if (hasSettings && backgroundColour != null)
    {
        styleAttribute = $"background-color: var({colorLabel}";
        if (colorShades.HasValue)
        {
            styleAttribute += $"-{colorShades});";
        }
    }
    else if (backgroundImage != null)
    {
        styleAttribute = $"background-image: url({backgroundImage.MediaUrl()});";
    }
}
<div class="layout py-3" data-bgimage="@backgroundImage.MediaUrl()" style="@styleAttribute background-size: contain;" data-block-alias="@Model.Content.ContentType.Alias">
    @RenderBody()
</div>
As you can see I concatenate a shade from a slider to the end of the css cstom property as well to alter the shade
t
Cheers @Mike Chambers Ill take a look at that cheers. And thanks for your example @Dean Leigh - Its similar to what we used to do, We are actually migrating to using an entire node of css properties now via a palette picker so you end up: creating a palette underneath a themes node. Then selecting the palette on a blocklist item via a Contentment DataList pointing at the Themes folder as its parent. In our scenario we are creating an internal baseline which each of the child projects needs to have control over its own colors from the backoffice. And the "hookup" of a Color Picker meant that our current 10 baseline children all see colors that dont relate to them. I ended up creating an image generating service to then override the Contentment Datalist thumbnail with an image formed from the palette colors on the palette node
Now its the fun part of migrating them all to this new picker 😅
d
This sounds very useful. I was trying to stick with Bootstraps pallette out of the box but also have plans to allow users to create their own themes. I have been thinking about how I can run Dart Sass on the server on save to generate the themes and run my shades mixin as well
t
Yeah we are using tailwind as our base framework, but due to the nature of how picky some brands can be, (And we have the limitation that whatever we do, needs to not include HTML changes). We needed to create some form of Design "norm" So we hooked up our tailwind config to look at css variables instead of hard set values. Those css variables are ultimately what get populated from the backoffice
m
@Dean Leigh would it be better to migrate away from background-image now? to a srcset of imgs in a picture with object-fit? (for serving responsive sized images) Also not a fan of inline styles from a content security policy perspective.. 😉
d
Inline styles - Agree, I prefer to use utility classes bit BS are moving more to css custom properties which tbf are very flexible to change on the fly background images - Hope full support for container queries should address this. There are proposals for a backfround version of srcset as well.
m
interesting.. so how would you serve a 480px for mobile vs a 1920px at desktop version say of any unknown url (as it's coming from the CMS via @backgroundImage.MediaUrl()) with container queries?
t
Little side note: Anyone know how to access an UmbracoHelper in a MigrationBase inherited class? "InvalidOperationException: Cannot resolve scoped service 'Umbraco.Cms.Web.Common.UmbracoHelper' from root provider." Im assuming I can spin up a singleton of it for the lifetime of the migration?
s
Not sure if it works within a migration, but I assume it should: https://docs.umbraco.com/umbraco-cms/reference/querying/umbraco-context
Either that, or this one, using
IUmbracoContextFactory
https://cultiv.nl/blog/using-hangfire-to-update-umbraco-content/
d
It would have to be inline
t
Just to come back to this: Thank you for all your help all! Migration working successfully and baseline children updated as expected. No longer bound by the color picker finally 😅
10 Views