Skip to main content

When creating an Umbraco website I not only want to build a great website for visitors, but also create a great experience for the editors. Since they will spend a considerable amount of time in the CMS editing content, it's an opportunity to help make their life a bit easier as well.

A great example of this is showing the number of characters in a text field, where content length is important. For example, you probably will have a field for each page to set its meta description that will be used when the page is being indexed by search engines. The current recommendation seems to be that this should be between 150 and 160 characters. So to show the text length while the editor is typing, helps them to determine what text length is most suitable.

Now, I'm obviously not the first person to think of this, and there are currently already a number of packages out there that support text/character counts for Umbraco:

And while these packages do exactly what I described above, there was one thing I didn't quite like about them: the packages would implement a new data type which created its own textbox or textarea input field, that would be different from the default text editor fields.
That in it self works for those simple data types, but I wondered whether there was a way to make use of the existing data types, and just extend them to add the text count functionality to them. 
Plus I wanted to be able to use text counts not just on simple textboxes and text areas, but on any text editor like the RTE for example.

The main function of showing the number of characters or words while you type is to assist the editor in the CMS. And since that's only useful at the editing stage, it shouldn't affect the underlying editor control or the data that gets stored. 
And that is why I started to look at a wrapper data type that could enhance the data type it wrapped around. 

Data type picker

In order to wrap a data type, I would need a picker that allows the user to choose which data type to wrap.
Since there is no built-in prevalue editor to do this, I created a new control that presents the user with a dropdown list where they can choose one of the other available data types in the current Umbraco site:

angular.module('umbraco').controller(
    'Codery.DataTypePickerController',
    function ($scope, editorState, dataTypeResource) {

        if ($scope.model.value !== undefined && $scope.model.value !== null) {
            $scope.model.value = parseInt($scope.model.value);
        }

        $scope.dataTypes = [];

        // load all data types, apart from current to avoid inception

        dataTypeResource.getAll()
            .then(function(data) {
                $scope.dataTypes = _.filter(data, function (dt) {
                    return dt.id !== editorState.current.id;
                });
            });

    }
);

Choosing one of the options will store the data type ID as the selected prevalue.

Once that was in place I could move on to the actual text count data type.

Wrapper

The idea for the wrapper would be to render a DIV element that wraps the chosen data type and applies a directive on it. This would then allow us to interact with the wrapped data type editor scope and elements in AngularJS.

<div class="codery__text-count umb-editor"
    ng-controller="Codery.TextCountController" 
    ng-model="model" 
    codery-textcount
    codery-textcount-count-type="{{model.config.countType}}"
    codery-textcount-limit="{{model.config.limit}}"
    codery-textcount-limit-type="{{model.config.limitType}}">
    
    <umb-property-editor
        ng-if="wrappedProperty !== undefined"
        model="wrappedProperty">
    </umb-property-editor>
	
</div>

The AngularJS controller will use the chosen data type ID to create an 'instance' of that data type which then acts as the model of the property editor directive inside our DIV: 

angular.module('umbraco').controller('Codery.TextCountController',
    function ($scope, contentTypeResource, assetsService) {

    if ($scope.model.value === undefined || $scope.model.value === null) {
        $scope.model.value = '';
    }

    contentTypeResource.getPropertyTypeScaffold($scope.model.config.dataType)
        .then(function (dataType) {
            // set some missing values
            dataType.id = $scope.model.id;
            dataType.alias = $scope.model.alias;
            dataType.value = $scope.model.value;

            // hide the label of the wrapped data type
            dataType.hideLabel = true;

            $scope.wrappedProperty = dataType;

            $scope.$watch('wrappedProperty.value',
                function (newVal) {
                    // update property value with new value of wrapped editor
                    $scope.model.value = newVal;
                });
        });

    assetsService.loadCss('/App_Plugins/Codery.TextCount/textcount.css');

});

By watching the value of the wrapped property it's easy to keep the stored value of our wrapper data type up-to-date.

Then it was just a case of creating the directive that will handle the text counting, displaying the number of characters/words and warning the user when they've reached the limit (based on the options chosen).
When the control view is loaded it will look for any compatible text controls (currently Textstring, Textarea, RTE, Markdown and Repeatable Textstring editors are supported), append a DIV to them for displaying the current count, and hooking up the events that will refresh the counters when the editor value gets updated.

On the server-side I've include a PropertyValueConverter that will retrieve the wrapped property type and use its property value converter to deal with any value conversions. So there should be no difference between this and the original data type when using it in strongly typed models generated by the ModelsBuilder.

The Text Count package is now available as both a NuGet package and a traditional Umbraco package on Our. It's compatible with Umbraco v7.4+.

Configure the Text Count data type settings
Configure the Text Count data type settings
Warning when the user has entered too many characters
Warning when the user has entered too many characters

Creating this package has been fun and interesting, especially since the idea of wrapping a data type editor worked out quite well.
I imagine there might be other future package ideas that can use the same principle, so I have created a separate NuGet package for the data type picker to allow it to be reused elsewhere.

Nested Content is a popular package for Umbraco to create repeatable content for pages, but this article shows how it can also easily be used to store data for website members.

Read more

I noticed that a website hosted on Azure Web Apps was generating multiple Umbraco trace log files for the same day. I went to investigate what caused it, and also came up with a suggested change to resolve this.

Read more