wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

CKEditor and Mustache become friends


In the beginning there was WordStar and CSV. The possibility of (then printed) personalized mass-communication had arrived in the form of mail-merge. For Notes eMails that is still a challenge (the latest version of OpenOffice now seems to have a reasonable eMail-Merge, but that's off topic here) since creating the template message with variables to be filled is kind of fuzzy (a.k.a usually out of the reach of mere mortal users).
XPages, Mustache and CKEditor to the rescue! The CKEditor shipping with XPages can be easily customized, so adding a dropdown that inserts Mustache markup shouldn't be too hard. To allow easy reuse of the code, I created a bean than contains all needed components. Add it to a page and simply call the bean functions. The first sample, using only uses static therefore is quite simple:

 
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
 <xp:scriptBlock id="scriptBlock1" value="#{mustache.sampleData}">
 </xp:scriptBlock>
 <h1>Mustache and CKEdit demo</h1>
 <xp:inputRichText id="inputRichText1">
  <xp:this.dojoAttributes>
   <xp:dojoAttribute name="extraPlugins" value="mustache">
   </xp:dojoAttribute>  
  </xp:this.dojoAttributes>
 </xp:inputRichText>
</xp:view>

The variable mustache is configured as a managed bean in faces-config.xml:

<managed-bean>
 <managed-bean-name>mustache</managed-bean-name>
 <managed-bean-class>com.notessensei.xpages.MustacheWrapper</managed-bean-class>
 <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

While it seems a little overkill to move the JavaScript into a Java function, we will see that the bean, once completed, is quite useful and keeps what we need in one place.The code so far:

package com.notessensei.xpages;

import java.io.Serializable;

/**
 * @author Stephan H. Wissel
 */
public class MustacheWrapper implements Serializable {

 private static final long serialVersionUID = 1L;

 private final String   preText;
 private final String   postText;

 public MustacheWrapper() {
  this.preText = this.populatePreText();
  this.postText = this.populatePostText();
 }

 public String getSampleData() {
  StringBuilder sample = new StringBuilder();
  sample.append(this.preText);
  sample.append("this.add('{{Subject}}','Subject','Subject');\n");
  sample.append("this.add('{{From}}','From','From');\n");
  sample.append("this.add('{{To}}','To','To');\n");
  sample.append("this.add('{{Date}}','Date','Date');\n");
  sample.append("this.add('{{Form}}','Form','Form');\n");
  sample.append(this.postText);
  return sample.toString();
 }

 /**
  * Client JavaScript code before the actual values
  *
  * @return
  */
 private String populatePreText() {

  StringBuilder result = new StringBuilder();

  result.append("CKEDITOR.plugins.add( 'mustache',\n");
  result.append("  {  requires : ['richcombo'],\n");
  result.append("     init : function( editor ) {\n");
  result.append("            var config = editor.config\n");
  result.append("            var lang = editor.lang.format\n");
  result.append("            editor.ui.addRichCombo( 'mustache',\n");
  result.append("            { label : \"Variables\",\n");
  result.append("              title : \"Variables\",\n");
  result.append("              voiceLabel : \"Variables\",\n");
  result.append("              className : 'cke_format',\n");
  result.append("              multiSelect : false,\n");
  result.append("              panel : {\n");
  result.append("                        css : [ config.contentsCss, CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ],\n");
  result.append("                  voiceLabel : lang.panelVoiceLabel },\n");
  result.append("              init : function() {\n");
  result.append("                     this.startGroup(\"Variables\");\n");
  // The rest is in the PostText
  return result.toString();
 }

 /**
  * Client JavaScript code AFTER the actual values
  *
  * @return
  */
 private String populatePostText() {

  StringBuilder result = new StringBuilder();
  result.append("          },\n"); // End of the init function!
  result.append("              onClick : function( value ) {\n");
  result.append("                        editor.focus();\n");
  result.append("                        editor.fire('saveSnapshot');\n");
  result.append("                        editor.insertHtml(value);\n");
  result.append("                        editor.fire('saveSnapshot');\n");
  result.append("          } });\n");
  result.append("            var toolbarName = config.toolbar;\n");
  result.append("            config[\"toolbar_\"+toolbarName].push(");
  result.append("{\"name\" : \"mustache\", \"items\" : [\"mustache\"] });\n");
  result.append("  }});\n");

  return result.toString();
 }
}

Next stop: generate the list of variables from a form or a document (or supply a collection)

Posted by on 09 April 2014 | Comments (2) | categories: XPages

Comments

  1. posted by Sean Cull on Wednesday 18 November 2015 AD:
    It looks as though this may not work with CKEditor 4 introduced in 9.01 FP2 (?)

    "Thus the proper way for a plugin to style it's editable content is to call CKEDITOR.addCss inside of the plugin's onLoad function, rather than it's init function in v3."

    { Link }

    still trying to work out what this means but with FP4 I get a 404 error on an undefinededitor.css
  2. posted by Sean Cull on Wednesday 18 November 2015 AD:
    changing line 53 to
    result.append(" css : [ config.contentsCss, CKEDITOR.getUrl(CKEDITOR.skin.getPath('editor') + 'editor.css') ],\n");

    seems to work