XPages & Angular.js: Accessing Rich Text (1)

If you want to access Rich Text with Angular.js, an easy way to get the content is to use a XPage as handler and grab the content of a XspInputRichText component. The component does all required steps automatically (f.e. it detaches all embedded images to disc and cleans up the temporary files later) and returns the complete HTML of the Rich Text item.

For the conversion I have created a small java helper class:

package ch.hasselba.xpages.util;

import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import com.ibm.xsp.component.xp.XspInputRichText;
import com.ibm.xsp.http.IMimeMultipart;

/**
 * Utility class for accessing RichText items
 * 
 * @author Sven Hasselbach
 * @version 1.3
 */
public class RTItemUtil {

    /**
     * 
     * @param dsName
     *            the name of the datasource to use
     * @param rtFieldName
     *            the name of the richtext item
     * @return String with the HTML
     */
    public static String getHTML(final String dsName, final String rtFieldName) {

        // get current FacesContext
        FacesContext fc = FacesContext.getCurrentInstance();

        // create a value binding for the datasource
        String expr = "#{" + dsName + "." + rtFieldName + "}";
        ValueBinding vb = fc.getApplication().createValueBinding(expr);

        // create a rich text components
        XspInputRichText uiCmp = new XspInputRichText();
        uiCmp.setValueBinding("value", vb);
        Object value = uiCmp.getValue();

        // get the value of the component as HTML
        String strHtml = null;
        if (value != null) {
            if (value instanceof IMimeMultipart) {
                IMimeMultipart mime = (IMimeMultipart) value;
                strHtml = mime.getHTML();
            } else {
                strHtml = value.toString();
            }
        }

        return strHtml;
    }

What does the method getHTML do? First, the datasource is accessed by creating a new value binding. Then a new XspInputRichText component is created on the fly which does the magic: All attachments are detachted into the temporary xsppers folder, and linked into the generated HTML code of the Rich Text item.

This is the XPage with the name „getrtdata“ which is our handle:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">

    <xp:this.data>
        <xp:dominoDocument var="documentRTItem" />
    </xp:this.data>
    
    <xp:this.afterRenderResponse>
        <![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util );
        
            var res = facesContext.getExternalContext().getResponse();
            res.setContentType("text/html");
            res.setCharacterEncoding( "UTF-8" );
            var writer = res.getWriter();
            
            var rtUtil = new ch.hasselba.xpages.util.RTItemUtil();
            var strHtml = rtUtil.getHTML( "documentRTItem", "Body" );
            
            writer.write( strHtml );
            writer.flush();
            facesContext.responseComplete();
       }]]>
       </xp:this.afterRenderResponse>
       
</xp:view>

The datasource on the this XPage is accessed by URL parameters from our Angular.js application. The method getHTML from the utility class is called with the name of the datasource and the name of the rich text item we want to access.

Now let’s have a look at our Angular.js application. To make it a litlle bit more complicated, the data from the Rich Text item will be loaded into a CKEditor:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
        <script type='text/javascript' src="jquery-1.8.3.js"></script>
        <script type='text/javascript' src="http://ckeditor.com/apps/ckeditor/4.4.1/ckeditor.js"></script>  
    </head>
    
    <body data-ng-app="myApp" data-ng-controller="CKEditiorCtrl">
    
        <textarea data-ck-editor data-ng-model="ckEditor.value"></textarea>
        <br>
       
        <button ng-click="loadData( '49573FE44017DA9FC1257CEC0035CBCC' )">load data</button>
    
        <script type='text/javascript' src="angular.js"></script>
        <script type='text/javascript' src="app.js"></script>
    </body>
    
</html>

The resources of the previous article are used in this example. The lastest version of CKEditor is used, and the whole application logic is contained in the file app.js.

When opening the application in the browser, a fresh and empty CKEditor instance will be instantiated:

When clicking the button „load data„, the XPage is loaded in the background. In the Firebug console the response of the request contains the HTML markup, including the pathes to the detached attachments:

Then the CKEditor is updated with the HTML code, and displays the content of the Rich Text item:

How does the app.js looks like?

var myApp = angular.module('myApp',[]);

/**
 * the data service to load the RTItem data
 */
myApp.service('dataService', function($http) {
    delete $http.defaults.headers.common['X-Requested-With'];
    this.getData = function( id ) {
        return $http({
            method: 'GET',
            url: 'getrtdata.xsp',
            params: {
                documentId: id,
                action: 'openDocument'}
            });
    }
});

/**
 * the directive
 */
myApp.directive('ckEditor', [function () {
    return {
        require: '?ngModel',
        link: function ($scope, elm, attr, ngModel) {
            var ck = CKEDITOR.replace(elm[0]);
            ngModel.$render = function () {
                ck.setData(ngModel.$modelValue);
            };
        }
    };
}])

/**
 * the controller
 */
myApp.controller('CKEditiorCtrl', function($scope, dataService) {
    $scope.dataservice = dataService;
    $scope.ckEditor = { value: null };
    $scope.loadData = function( unid ){
        $scope.dataservice.getData( unid ).then(
            function(dataResponse) {
                   $scope.ckEditor.value = dataResponse.data;
            });
    }
    
});

The service dataService is the interface to the XPages backend: The method getData makes the HTTP request with the $http service of Angular.js. The two parameters are the document UNID and the action to perform, and are used to control the datasource of the XPage.

This service is bound to the controller, and is called by the loadData method as soon as the button is clicked. It sets the value of the ckEditor binding with the resulting data from the request. (Btw. if you have a look at the HTML of the button you will see that the used document UNID is hardcoded in this example).

But what is the directive for?

 <textarea data-ck-editor data-ng-model="ckEditor.value"></textarea>

The directive is a „link“ between Angular.js and the code of the CKEditor: The data-ck-editor attribute of the textarea is the key to our directive and is searched by Angular.js when the application is initialized. When a matching directive is found, the method link is called, with the parent DOM element as parameter. This DOM element is now replaced by a CKEditor instance. The data-ng-model attribute binds the data of the CKEditor to the defined value of our controller, the $scope.ckEditor.value. This allows to update the content of the CKEditor instance.

That’s it!

Dieser Beitrag wurde unter Allgemein, Angular.js abgelegt und mit , , , , , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

3 Antworten zu XPages & Angular.js: Accessing Rich Text (1)

  1. Mark Barton sagt:

    Very nice Sven.

    Could you get the Rich Text via DDS?

Schreibe einen Kommentar zu Sven Hasselbach Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.