The state of WYSIWYG grid editing in Umbraco - a prototype

Books I read
See all recommendations

The state of the grid

Since the grid was launched at the CG talk "The sky is the limit" back in 2015 (i think), it has been a joy to use for editors, designers and developers alike. Granted, there's been the other feature that could've gotten more love (configuration and settings), but generally it does the job swell.
However, it's still just a white canvas with "settings applied".
Antoine made LePainter back in 2015, but likely didn't get much traction.
I was just told about it today by Callum. Shame on me for not watching the package space enough, how embarrasing. 😳

Anyway...

Why should we care

Recently, I've been in lots of discussions; near battles IMO; about which CMS to sell to our customers.
The feedback is that editors wonder why what they see while editing does not match what they see in the front-end. We do work with another CMS where the backoffice experience has a near 1:1 layout and design with the front-end. As developers, we tend to think structured data is the better solution. Think nested- and stacked content. I tend to agree. But in a sales meeting, those arguments are total losing arguments. A competing agency will bury you when they oversell the fancy editing experience. Even though the not-so-apparent technical features of the competing CMS are totally useless.
And I do agree - the editing experience is what Umbraco likes to sell as well!

What can we do?

The current HQ efforts are working on the "block based" editor. It's been going on for the better part of a year, but the publicly available information is mostly just an RFC with screenshots looking like stacked / nested content, and that's it. The technical aspects seem super, but there's nothing much (but text) about the general editing experience. Also, and I know this is a hot take, editors DO want to see three columns in the editor when it ends up three columns in the front-end.

Hopefully, as mentioned in the RFC, it can act as a stepping stone towards the "Grid 2.0" (which we started work on at the retreat two years ago).

But as far as I'm concerned, this could just as well end up never happening. The effort is not very visible, Project UniCore πŸ¦„ has (rightly) high priority, and in the mean time we're stuck with Grid 1.0, or maybe, hopefully, a rumored commercial package called Bento by the eminent Peter Gregory and his colleagues at KØBEN Digital.

A prototype

So back using Grid 1.0 for projects to be delivered imminently, I've started doing some prototypes for something similar to what LePainter did for early v7 projects.

I asked the community if it was madness or worth pursuing, and the general feedback is that it's both. Hence this blog post to explore it some more, and hopefully gather some more feedback or at least provide feedback to the block editor team. (Hope they receive it. 😁)

Here's a quick little preview of what I've been doing so far:

The issues

Actually getting the styling into the grid is "just" a matter of including a few pagefuls of JavaScript and a limited and scoped CSS file in a package.manifest file for the site. Both classes and styles are applied as expected, and with some due dilligence it doesn't look half bad.

The biggest issue I have is that the cells and controls are styled white, and while styling one have to mind not messing up the GUI text like "Settings applied".

I attempted to lessen the opacity of the cells and controls, and it works to a degree. The minute one adds a background image however, the entire thing starts to crumble. How to keep the WYSIWYG experience while still seeing the GUI? One could fantasize about doing an average color sample of the image and toggling between white and black text, maybe? Could be done in a short hacking sprint...
Could just keep it fairly white like I've done in the prototype. Could keep just the active rows white. This is where I start struggling. 😳

To be frank, the biggest problem lies in the fact that the "Add content" button is in the grid. Other CMSes have drag drop functionality from a menu over to the side. It could possibly still just be white or slightly transparent. The last problem then is all those "settings applied" and empty cells with the other type of "Add content" button.

I can't help but think this is all within grasp, albeit just out of my limited back-ender reach. πŸ˜†

The settings dialog issues

The settings dialog is a chapter of itself. It's a mystery to me why we still have a concept of individual "prevalue editors" in Umbraco. Property editors have the same model, and can be sneaked in there by just providing a full path.

However, as experienced when working on our ContentList package, nesting property editors using built-in directives wreaks total havok on the styling. There's some grey background and drop shadows - that I haven't seen being actively used anywhere. It's generally just a mystery, and a huge candidate for backoffice re-work.

Also, for instance the slider property editor does look a bit weird when used as a setting editor. I do hope that those things will get an overhaul and are made more uniform sooner or later.

I'll just leave that hanging, since I know (and appreciate) there are several HQ and community efforts going on to clean things up.

The code

Since I managed to sneak in a few new events in the grid, making it work was a matter of hooking into the "grid.initialized" event and start watching the model. There might be some performance issues with large grids since it's a deep watch, but with todays computers that might be irrational over engineering to think as well.

It adds the configured styles as expected.
For the configuration, it just adds the value of the configuration setting called "class". If there's more it concats the setting key with the setting value, delimited by a dash.

You can scrutinize the JavaScript here, or in this gist.

angular.module("umbraco").run(["eventsService", function(eventsService) {
    function findModelScope(scope) {
        if (!scope) {
            return null;
        }
        if (scope.model) {
            return scope;
        }
        return findModelScope(scope.$parent);
    }

    function isProtectedClass(className) {
        return className === "umb-row-inner" ||
            className === "umb-cell-inner" ||
            className.substr(0, 2) === "ng-" ||
            className.substr(0, 2) === "ui-";
    }

    function addClasses(element, gridItem) {
        function classNameFromConfig(e) {
            if (e === "class") {
                return gridItem.config[e];
            } else {
                return e + "-" + gridItem.config[e];
            }
        }

        var classes = (element.className || "").split(/\s+/);
        var newClasses = classes.filter(isProtectedClass);
        var nameClass = (gridItem.name || "").toLowerCase().replace(" ", "-");
        var configClasses = Object.keys(gridItem.config || {}).map(classNameFromConfig);
        newClasses.push(nameClass);
        newClasses = newClasses.concat(configClasses);
        element.className = newClasses.join(" ");
    }

    function addStyles(element, gridItem) {
        function styleFromKeyPair(e) {
            return e + ":" + gridItem.styles[e];
        }

        var stylesValues = Object.keys(gridItem.styles || {}).map(styleFromKeyPair);
        element.style = stylesValues.join(";");
    }

    eventsService.on("grid.initialized",
        function(evt, data) {
            var modelScope = findModelScope(data.scope);
            var model = modelScope.model;
            var jqEl = data.element;
            var el = data.element.get(0);
            jqEl.addClass("stylized-grid");

            modelScope.$watch(
                "model",
                function () {
                    var areaElements = el.getElementsByClassName("umb-column");
                    if (areaElements.length === 0) {
                        return;
                    }
                    model.value.sections.forEach(function (area, ai) {
                        var rowElements = areaElements[ai].getElementsByClassName("umb-row-inner");
                        area.rows.forEach(function (row, ri) {
                            var rowElement = rowElements[ri];
                            addClasses(rowElement, row);
                            addStyles(rowElement, row);

                            var cellElements = rowElement.getElementsByClassName("umb-cell-inner");
                            row.areas.forEach(function(cell, ci) {
                                addClasses(cellElements[ci], cell);
                                addStyles(cellElements[ci], cell);
                            });
                        });
                    });
                },
                true
            );
        });

}]);

The CSS

To not interfere with any other backoffice styling, the script adds the class "stylized-grid" to the grid container. Here's the (S)CSS I used to make things more transparent:

.umb-grid.stylized-grid { 

    .umb-cell-content {
        background-color: rgba(255, 255, 255, .8);
    }

    .umb-cell-content:hover {
        background-color: rgba(255, 255, 255, 1);
    }

    .umb-cell-content.-has-editors {
        background-color: rgba(255, 255, 255, .8);
    }

    .umb-cell-content.-has-editors:hover {
        background-color: rgba(255, 255, 255, 1);
    }

    .umb-grid-add-more-content {
        background-color: rgba(255, 255, 255, 1);
    }

    .umb-control-bar, .umb-grid-has-config {
        font-family: Lato, Helvetica Neue, Helvetica, Arial, sans-serif !important;
    }

    iframe {
        background-color: transparent;
    }

    @import "../../../Assets/grid";
}

The import at the bottom is the sass that styles our individual components in the front-end, and that should be reflected in the backoffice.

Conclusion

We'll be diving into deep water and testing this out on a real customer. I'm a bit weary, but we need to get the experience.

I do hope I've been able to provide some inspiration, and that WYSIWYG editing in Umbraco might get the renaissance (I believe) it deserves.

Feel free to drop your comments below or ping me on Twitter or Slack!
Also happy to continue the discussion on our or github, but not sure the best way forward, if any.

Author

comments powered by Disqus