JavaScript

SharePoint Rich Content Editor with CKEditor Widgets

0

In this article, we build a CKEditor widget representing a SharePoint document link. This introduces a SharePoint Rich Content Editor alternative, which provides features to build a variety custom widgets. At the same time, we demonstrate the ability to leverage SharePoint data and the 2013 REST API.

The following image displays a standard CKEditor control, which includes a toolbar containing various features to deliver rich content without requiring the author to understand HTML or CSS.

The first step is create the CKEditor widget, which is the JavaScript plugin.js located in the widget folder. 

 Javascript |  copy code |? 
01
CKEDITOR.plugins.add('documentlinkwidget', {
02
    requires: 'widget',
03
    icons: 'documentlinkwidget',
04
    init: function(editor) {
05
       editor.widgets.add('documentlinkwidget', {
06
           button: 'Add document link',
07
           template:
08
               '<div class="documentlinkwidget" title="">' +
09
               '<a href="" class="doclinkwidget" style="width: 200px;"></a>' +
10
               '</div>',
11
           requiredContent: 'div(documentlinkwidget); a(doclinkwidget)',
12

In the init, we add the widget to the CKEditor. The template defines the widget markup structure. The requiredContent represents the mandatory widget content, which is a div and a element.

 Javascript |  copy code |? 
01
            init: function() {
02
                var width = this.element.getStyle('width');
03
                if (width)
04
                    this.setData('width', width);
05
                else
06
                    this.setData('width', '200px'); //default
07
 
08
                var id = this.element.getAttribute('title');
09
                if (id)
10
                    this.setData('docid', id);
11
            },
12

Next, add an init function to set the data. The width value is retrieved from the div element style attribute. If the style does exist then width is set to “200px” as the default. The id is retrieved from the title attribute, which is the docid value.

 Javascript |  copy code |? 
1
           data: function() {
2
                var width = this.data.width;
3
                if (width == '')
4
                    this.element.removeStyle('width');
5
                else
6
                    this.element.setStyle('width', width);
7

In the data function above, we build the widget markup. The div element style attribute is set to the appropriate width value. Next, we will retrieve the SharePoint document information using a REST API call.

 Javascript |  copy code |? 
01
               var spQuery = {
02
                    element: '',
03
                    url: '',
04
                    init: function(element) {
05
                        spQuery.element = element;
06
                        spQuery.url = _spPageContextInfo.webAbsoluteUrl + "/_api/lists/getByTitle('" + encodeURIComponent(spQuery.element.data.sourcelistname) + "')/items(" + spQuery.element.data.docid + ")/File?$select=Title,Name,Exists,Length,Level,ServerRelativeUrl";
07
                    },
08
                    load: function() {
09
                        $.ajax(
10
                            {
11
                                url: spQuery.url,
12
                                method: "GET",
13
                                headers: { "accept": "application/json;odata=verbose" },
14
                                success: spQuery.onSuccess,
15
                                error: spQuery.onError
16
                            }
17
                        );
18
                    },
19
                    onSuccess: function(data) {
20
                        spQuery.element.element.setAttribute('title', spQuery.element.data.docid);
21
 
22
                        var link = spQuery.element.element.findOne('a');
23
                        if (link) {
24
                            link.setText(data.d.Title || data.d.Name); //title is not required so set to name if missing
25
                            link.setAttribute('href', data.d.ServerRelativeUrl);
26
                            link.setAttribute('class', 'doclinkwidget');
27
 }
28
                    },
29
                   onError: function(err) {
30
                        api.openMsgDialog('', JSON.stringify(err));
31
                    }
32
                };
33
 
34
                //get data if docid is set
35
                if (this.data.docid) {
36
                    spQuery.init(this);
37
                    spQuery.load();
38
                }
39
            }
40

SPQuery contains our initialize and JQuery logic, so we can retrieve the SharePoint document file information to build the widget markup. The init sets the SPQuery.element and SPQuery.url values, which will be used in the load function. The SPQuery.onSuccess and SPQuery.onError are the callbacks to handle the logic based on the response. The link.setText will set the value to Title or Name from the JSON result. The ServerRelativeUrl  result value is the href attribute. You can retrieve the icon and file size, which can also be included in the markup.

The final section will conditionally make the SPQuery call when a docid value is present. This is accomplished with SPQuery.init(this) and SPQuery.load() calls.

We also add logic to support a widget configuration dialog, which  allows the user to select a document and set the width. The following is the additional plugin.js code required to define a dialog.

 Javascript |  copy code |? 
1
init: function(editor) {
2
        CKEDITOR.dialog.add('documentlinkwidget', this.path + 'dialogs/documentlinkwidget.js');
3
 
4

We will cover the CKEditor widget dialog in the next post. The following is the complete widget plugin.js.

 Javascript |  copy code |? 
01
CKEDITOR.plugins.add('documentlinkwidget', {
02
    requires: 'widget',
03
 
04
    icons: 'documentlinkwidget',
05
 
06
    init: function(editor) {
07
        CKEDITOR.dialog.add('documentlinkwidget', this.path + 'dialogs/documentlinkwidget.js');
08
 
09
        editor.widgets.add('documentlinkwidget', {
10
            button: 'Add document link',
11
 
12
            template:
13
                '<div class="documentlinkwidget" title="">' +
14
                '<a href="" class="doclinkwidget" style="width: 200px;"></a>' +
15
                '</div>',
16
 
17
            allowedContent:
18
                'div(!documentlinkwidget); a(!doclinkwidget)',
19
 
20
            requiredContent: 'div(documentlinkwidget); a(doclinkwidget)',
21
 
22
            dialog: 'documentlinkwidget',
23
 
24
            upcast: function(element) {
25
                return element.name == 'div' && element.hasClass('documentlinkwidget');
26
            },
27
 
28
            init: function() {
29
                var width = this.element.getStyle('width');
30
 
31
                if (width)
32
                    this.setData('width', width);
33
                else
34
                    this.setData('width', '200px'); //default
35
 
36
                var id = this.element.getAttribute('title');
37
                if (id)
38
                    this.setData('docid', id);
39
            },
40
 
41
            data: function() {
42
                var width = this.data.width;
43
 
44
                if (width == '')
45
                    this.element.removeStyle('width');
46
                else
47
                    this.element.setStyle('width', width);
48
 
49
                var SPQuery = {
50
                    element: '',
51
                    url: '',
52
                    init: function(element) {
53
                        SPQuery.element = element;
54
                        SPQuery.url = _spPageContextInfo.webAbsoluteUrl + "/_api/lists/getByTitle('" + encodeURIComponent(SPQuery.element.data.sourcelistname) + "')/items(" + SPQuery.element.data.docid + ")/File?$select=Title,Name,Exists,Length,Level,ServerRelativeUrl";
55
                    },
56
                    load: function() {
57
                        $.ajax(
58
                            {
59
                                url: SPQuery.url,
60
                                method: "GET",
61
                                headers: { "accept": "application/json;odata=verbose" },
62
                                success: SPQuery.onSuccess,
63
                                error: SPQuery.onError
64
                            }
65
                        );
66
                    },
67
                    onSuccess: function(data) {
68
                        SPQuery.element.element.setAttribute('title', SPQuery.element.data.docid);
69
                        var link = SPQuery.element.element.findOne('a');
70
                        if (link) {
71
                            link.setText(data.d.Title || data.d.Name); //title is not required so set to name if missing
72
                            link.setAttribute('href', data.d.ServerRelativeUrl);
73
                            link.setAttribute('class','doclinkwidget');
74
                        }
75
                    },
76
                   onError: function(err) {
77
                        api.openMsgDialog('', JSON.stringify(err));
78
                    }
79
                };
80
 
81
                //get data if docid is set
82
                if (this.data.docid) {
83
                    SPQuery.init(this);
84
                    SPQuery.load();
85
                }
86
            }
87
        });
88
    }
89
});
90
 
91

Improve UX with SharePoint Client-side Rendering

0

The standard SharePoint rendered views generally score low for originality with limited points for user-experience. The following is an example of a standard list view, which is a table structure with text-based field values.

What if we add a little style to represent the Approval Status column? We can override the standard field control rendering and inject our own client-side logic. This can be accomplished using HTML and JavaScript technologies with the SharePoint 2013 client-side rendering feature. So…let’s look at a simple style enhancement to the above list view and Approval Status column.

This is a very simple change, which utilizes color (red, yellow and green) and a minor value change (Approved is now Live) to improve the view. At a glance, the user can associate the color with the status using the common traffic stop light palette. So…let’s look at the changes required to implement the rendering logic.

 First, we can create the rendering logic.

 Javascript |  copy code |? 
01
(function () {
02
    var fieldContext = {}; 
03
    fieldContext.Templates = {}; 
04
    fieldContext.Templates.Fields = { 
05
        "_ModerationStatus": { "View": approvalStatusFieldTemplate, "ViewForm": approvalStatusFieldTemplate&nbsp;}
06
    };
07
 
08
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(fieldContext); 
09
 
10
})(); 
11

This function contains the field context and registers the templates. The key is fields and _ModerationStatus, which is the internal field name for Approval Status. You can include templates for View, DisplayForm, EditForm and NewForm. In this example, the View and ViewForm templates are defined to call the approvalStatusFieldTemplate function.

 Javascript |  copy code |? 
01
function approvalStatusFieldTemplate(ctx) { 
02
 var approvalStatus = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
03
 
04
    switch (approvalStatus) { 
05
        case "Draft": 
06
            return "<span style='color:red'><strong>" + approvalStatus + "</strong></span>"; 
07
            break; 
08
        case "Scheduled": 
09
            return "<span style='color:orange'><strong>" + approvalStatus + "</strong></span>"; 
10
            break; 
11
        case "Approved": 
12
            return "<span style='color:green'><strong>Live</strong></span>";
13
 }
14
 
15
 return approvalStatus; 
16
}
17

This function will return the injected mark-up for the target field. The first statement assigns the current field value to approvalStatus. Next, a switch statement returns a span element with the appropriate style color. The result is a colorized value based on the approval status.

The next example replaces the text value with an image, which also represents the approval status. The result is mark-up to render an image with the approval status value as a tooltip.

The following is the modified approvalStatusFieldTemplate function. In this example, we are borrowing a few SharePoint provided images.

 Javascript |  copy code |? 
01
function approvalStatusFieldTemplate(ctx) { 
02
 var approvalStatus = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
03
 var icon;
04
 
05
    switch (approvalStatus) { 
06
        case "Draft": 
07
            icon = "hlthwrn.png"; 
08
            break; 
09
        case "Scheduled": 
10
            icon =  "calendar.gif"; 
11
            break; 
12
        case "Approved":
13
            icon = "hlthsucc.png";
14
            break;
15
        default:
16
            icon = "hltherr.png";
17
    }
18
 
19
    return "<img src='/_layouts/15/images/" + icon + "' title='" + approvalStatus + "'/>";
20
}
21

The client-side rendering is also referred to as JSLink, since this is the new property that instructs the field rendering process to replace the result using the JavaScript contained in the JSLink resource. The following PowerShell script will assign the JSLink value to the JavaScript file containing our rendering logic for the Approval Status field.

 DOS |  copy code |? 
1
$web = Get-SPWeb http://myserver
2
$field = $web.Fields["Approval Status"]
3
$field.JSLink = "~layouts/15/ApprovalStatusViewDecorate.js"
4
$field.Update($true)
5

If you would like to explore client-side rendering for your sites then I would recommend additional tutorials. This should include handling multiple instances of the field on a page, which would cause issues without additional configuration. The following are a few helpful resources:

Good Examples

Good Overview

This new powerful feature will provide another tool to improve the user experience with endless possibilities. I hope this information provides a quick start for SharePoint 2013 Client-side rendering.

Go to Top