Umbraco: Use the rich text editor (TinyMCE) in a custom section

By Markus Johansson
2013-12-04

One requirement of Enkel Medias Umbraco-package Newsletter Studio is to render and use the built in rich text editor from Umbraco (TinyMCE) in the custom Newsletter Studio-section.

 

Newsletter Studio -banner -580x 250

 

TinyMCE in a custom Umbraco 7-section

 

image

Since the backoffice of Umbraco 7 is rebuilt using AngularJS all property editors are built using JavaScript and they also rely on the current AngularJS-scope to contain some information on how to render the control. So in order to render our rich text editor (rte) we need to provide this information.

There are some different ways to render the control, some are more “hacky” than others and I’m going to walk you through some options. Just to be clear –this is not a best practice guide for JavaScript so I’ve added the JavaScript controllers in script-elements inside the views. This is something that you should not do, they should live in their own .js-files – but for simplicity I ignore that in this blog post.

Option 1 –Copy the original view

The first option is to just copy the code from the original rte-view (from the Umbraco source code). It looks something like this:

<div ng-controller="Umbraco.PropertyEditors.RTEController" class="umb-editor umb-rte">
    <textarea ng-model="model.value" rows="10" id="{{model.alias}}_rte"></textarea>
</div>

This is super simple. But it won’t work. Since Umbraco loads information about all the tabs, content, property types, values and so on for a content page when it’s loaded the RTEController assumes that the current scope has all this information. So we need to simulate that with our own controller. Let’s change the view so it looks like this:

<div ng-controller="CustomSectionEditController">    
    <ng-form>
        <div ng-controller="Umbraco.PropertyEditors.RTEController" class="umb-editor umb-rte">
            <textarea ng-model="model.value" rows="10" id="{{model.alias}}_rte"></textarea>
        </div>            
        <pre>
            Value from the RTE {{model.value}}
        </pre>
    </ng-form>
</div>

And for this code we need a controller called “CustomSectionEditController”, let’s just add a simple version of this controller:

<script type="text/javascript">
    function CustomSectionEditController($scope) {

        $scope.model = {
            alias: 'myRichtexteditor',
            config: {
                editor: {
                    toolbar: ["code", "undo", "redo", "cut", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbmacro", "table", "umbembeddialog"],
                    stylesheets: [],
                    dimensions: { height: 400, width: 250 }
                }

            }
        };
    }
</script>

With this code in place the rich text editor (rte) should be rendered and the HTML from the editor will be populated into the $scope.model.value-property.

While this works it’s not so very flexible and another problem is that it will be hard to “lazy load” properties, values and settings since the rendering of the rte-controller could start before all your data has been downloaded.

Option 2 – Use the umb-editor directive

In the views in Umbraco all the property editors are loaded dynamically the view for editing content only uses this simple tag:

<umb-editor model="myProperty"></umb-editor>

Umb-editor is a Umbraco-specific AngularJS directive that will look at the model attribute to find out which $scope-property to use for getting rendering information, in this case it’s $scope.myProperty – so let’s add that:

<script type="text/javascript">
    function CustomSectionEditController($scope) {
        $scope.myProperty= {
            label: 'bodyText',
            description: 'Load some stuff here',
            view: 'rte',
            config: {
                editor: {
                    toolbar: ["code", "undo", "redo", "cut", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbmacro", "table", "umbembeddialog"],
                    stylesheets: [],
                    dimensions: { height: 400, width: 250 }
                }
            }
        };
    }
</script>

As you can see with this code the $scope.myProperty.view property is called “rte” which tells AngularJS to load the rte (rich text editor)-view as the current view for this umb-editor. If we were to set $scope.myProperty.view to ‘textbox’, a textbox would be loaded. Updating the view to look like this would get this code to run:

<div ng-controller="CustomSectionEditController">    
    <ng-form>
        <umb-editor model="myProperty"></umb-editor>      
        <pre>
            Value from the RTE {{myProperty.value}}
        </pre>
    </ng-form>
</div>

This is a little bit cleaner than the approach in option 1 but we still suffer from the problem that the directive could be processed before we have loaded all our external data. For example – we would probably like to set the $scope.myProperty.value to some value that's loaded from the backend –but with this approach the directive could be processed before that data is downloaded and we’ll be left with a ugly error message.

Option 3 – Use ng-repeat and an array to “lazy load”

As far as I have seen this is the best way to load data from the backend and then render the wysiwyg-editor. In my use case I want to have a specific data type configured and when the page loads I want to get the configuration (pre values) for that data type and put that data in $scope.myProperty.config. I also want to download some content from the backend and put that in $scope.myProperty.value.

So – How do we avoid the directive from being processed before the data is loaded? A loop. Let’s put this in our view

<umb-property 
    property="property"
    ng-repeat="property in properties">
    <umb-editor model="property"></umb-editor>
</umb-property>

And then put an array of properties on the scope:

$scope.properties = [{
                        label: 'bodyText',
                        description: '',
                        view: 'rte',
                        config: {
                            editor: {}, // empty for now
                            hideLabel: true
                        }, hideLabel: true
                    }];

This would look almost the same as option 2 but. Since the ng-repeat attribute is there the directives will be reprocessed each time the $scope.properties-property change and since $scope.properties is null until after the data is downloaded the umb-editor directives will note be loaded.

The full view could look something like this:

<div ng-controller="CustomSectionEditController">    
    <ng-form>
        <umb-property 
            property="property"
            ng-repeat="property in properties">
            <umb-editor model="property"></umb-editor>
        </umb-property>
        <pre>
            Value from the RTE {{properties[0].value}}
        </pre>
    </ng-form>
</div>

And the controller like this:

<script type="text/javascript">
    function CustomSectionEditController($scope) {
        $scope.property = {
            label: 'bodyText',
            description: 'Load some stuff here',
            view: 'rte',
            config: {
                editor: {
                    toolbar: ["code", "undo", "redo", "cut", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbmacro", "table", "umbembeddialog"],
                    stylesheets: [],
                    dimensions: { height: 400, width: 250 }
                }
            }
        };
    }
</script>

Now the last step would be to make sure that the text-value for the editor is fetched from some backend service.

<script type="text/javascript">
    function CustomSectionEditController($scope, $http) {
        $http({ method: 'GET', url: '/url/to/our/cool/service' })
            .success(function (data) {
                $scope.property = {
                    label: 'bodyText',
                    description: 'Load some stuff here',
                    view: 'rte',
                    config: {
                        editor: {
                            toolbar: ["code", "undo", "redo", "cut", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbmacro", "table", "umbembeddialog"],
                            stylesheets: [],
                            dimensions: { height: 400, width: 250 }
                        }
                    },
                    value: data
                };

            })
            .error(function () {
                $scope.error = "An Error has occured while loading!";
            });
    }
</script>

As you can see we added the $http-parameter to the controller and uses it to fetch the data that we need before assigning the values to the scope. This will prevent the umb-editor directive from starting to load before all our data is loaded.

This is how I’ve solved the this issue at the moment and there may be other ways as well, please feel free to post questions or comments here below.

Cheers!






More blog posts



15 januari 2021

Umbraco and MiniProfiler