Click here to Skip to main content
Click here to Skip to main content

Online Rich Text Editor with Download as Word Option

By , 7 Sep 2010
Rate this:
Please Sign up or sign in to vote.

Introduction

This article describes how you can create online rich text editor which provides the following functionality:

  • Online Rich Text Editing
  • Saving the rich text
  • Downloading it as Word document
  • Mail merge features

screenshot.png

Background

I have been using Google Doc for a while now. I was wondering if I could create something similar using the coding knowledge that I have. After searching online, I did not find anything (open source) that could provide Google like functionality and mail merge. I did not want to write the richtext editor from scratch. After searching the internet, I found that FCKEditor) should serve my needs. Also the OpenXML SDK from Microsoft can be used for mail merge and conversion to docx file.

You will need the FCKEditor as online editor. (You can download it here.)

You can download OpenXML SDK here.)

Using the Code

Project Setup

Find below the steps to setup the project in Visual Studio (I am using Visual Studio 2008).

vs.png

  1. Download and unzip the source in the folder of your choice
  2. To simply use it, configure this folder as a virtual folder in IIS (Here is a link from MSDN explaining how you can do it)
  3. Open Visual Studio
    1. Click on open project of type Web Site
    2. Open this folder as website
    3. Add references to the "DocumentFormat.OpenXml" and "WindowsBase". You can get those when you install the OpenXML SDK 2.0. Alternatively, I have included these two DLLs in the lib folder.
    4. Run the project

Code Explained

The project contains one folder called "scripts". This folder has the FCKEditor files. It also has jquery file and one of our script files called "myAjax.js" (I will explain them later). You will also find a folder called "docs". This is the folder that will hold our generated Word documents.

In the root folder, we have three files:

  1. Default.aspx - The default web page
  2. downloader.ashx - This file handles our download logic
  3. web.config - Normal config file

Default.aspx

Let's first open Default.aspx file. In the <head> section, include the following references to the JavaScript files. These files will allow us to use the FCKEditor and also provide reference to JQuery and our JavaScript file:

<script src="scripts/ckeditor/ckeditor.js" type="text/javascript"></script>
<script src="scripts/ckeditor/adapters/jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>
<script src="scripts/myAjax.js" type="text/javascript"></script>
<script language="javascript" type="text/javascript">	

Let's skip to the HTML now. We will come back to other scripts in the head later. On the form, we simply add a header for our page:

Online Richtext editor with Mail Merge		

Then we take care of those browsers that may not have JavaScript turned on.

<noscript>
  
    Online Richtext editor requires JavaScript to run. 
    In a browser with no JavaScript support, like yours, you should still see 
    the contents (HTML data) and you should be able to edit it normally, 
    without a rich editor interface.	    

</noscript>		

Next, we add some instructions and a textarea. The textarea will serve as our richtext editor. We use the inbuilt FCKEditor method "CKEditor.Replace" to convert the textarea into FCKEditor richText editor. We also add a div tag to display our messages

   <p>
	This is online rich text editor sample with mail merge.<br />
	Use the text area below to type in your document. <br />
	Use the dropdowns below that have mail merge fields. 
   </p>
<textarea cols="50" id="editor1" name="editor1" rows="10"></textarea>
<!-- instantiate a new instance of CKEDITOR -->
<script type="text/javascript">
	//<![CDATA[
	// Replace the <textarea id="editor1"> with an CKEditor instance.
	var editor = CKEDITOR.replace('editor1',
	{
	    toolbar: 'myToolBar', skin: 'office2003', width: '60%'
	});
	//]]>
</script>
<div id="eMessage" />		

The last section is where we add the dropdown for mailmerge fields and a button to let user generate the Word document. As you can see, we have an onclick handler for the button. The ajaxDownloadDoc() function is in our JavaScript file "myAjax.js". Here we use the JQuery's AJAX method to post the data to our handler and generate the Word document. More about handler in the next section.

 Insert Merge Fields from here
    <select id="MergeFields" >
        <option value="0" selected="selected">Select Merge Fields</option>
        <option value="1">{^Title^}</option>
        <option value="2">{^FirstName^}</option>
        <option value="3">{^LastName^}</option>
    </select>    
<input type="button" name="saveAsWord" id="saveAsWord"   
	önclick="javascript:ajaxDownloadDoc()" value="Download as word" />	 

As promised earlier, here is the JavaScript that actually inserts the mailmerge fields in the richtext editor. We use JQuery to acertain that our form is loaded. Then we tap on to the onSelectChange event of our "MergeFields" dropdown. Here we call our function and insert the selected values in the FCKEditor.

oEditor.insertHtml(valueToInsert);

Here is the code:

//select the merge fields dropdown and implement the onchange event 
//to insert the selected value in the text area
$(document).ready(function() {
	$("#MergeFields").val('0');
	$("#MergeFields").change(onSelectChange);
});

function onSelectChange() {
	var selected = $("#MergeFields option:selected");
	var oEditor = CKEDITOR.instances.editor1;

	if (selected.val() != 0) {
		var valueToInsert = selected.text();
		// Check the active editing mode.
		if (oEditor.mode == 'wysiwyg') {
			// Insert the desired HTML.
			oEditor.insertHtml(valueToInsert);
		}
		else {
			alert('You must be on WYSIWYG mode!');
		}
	}
	$("#MergeFields").val('0');
}		

Default.aspx.cs

Let's look at the code behind for our default page. There isn't much we do here. We simply provide a place holder to save the contents of the richtext editor to your place of choice. I have not implemented the save to database or other places. You can add the code as you see fit.

protected void Page_Load(object sender, EventArgs e)
{
	string richText = Request["editor1"];
	if (!string.IsNullOrEmpty(richText))
	{
		string a = richText;
		///TODO - save this HTML values somewhere
	}
}		

downloader.ashx

As you would have noticed, we also have a handler file here "downloader.ashx". For more information about how handers are used, click here. We use this file to handle the Ajax post back event. In the ProcessRequest method, we create a new file name by using GUID. This will ensure that our file names are unique. We then call the SaveAsWord method which does the heavy lifting.

public void ProcessRequest (HttpContext context) 
{
	try
	{
		string fileName = Guid.NewGuid().ToString() + ".docx";
		string path = context.Server.MapPath("~/docs/") + fileName;
		if (!(string.IsNullOrEmpty(context.Request["what"])) 
		&& (context.Request["what"].ToLower() == "saveasword")
			&& !(string.IsNullOrEmpty(context.Request.Form[0])))
		{
			SaveAsWord(context.Request.Form[0], path, fileName);
		}
	}
	catch (Exception ex)
	{
		context.Response.ContentType = "text/plain";
		context.Response.Write(ex.ToString());
		System.Diagnostics.Trace.WriteLine(ex.ToString());
	}	
}		

SaveAsWord is a wrapper function which first creates a Word document for us. Then, it calls a function to replace the mailmerge fields with actual values. Finally, it calls a function that generates the final Word document.

private void SaveAsWord(string input, string fullFilePath, string fileNameOnly)
{
	CreateDocument(fullFilePath);
	input = ReplaceMailMerge(input);
	generateWordDocument(input, fullFilePath, fileNameOnly);
}		

Creating Word document using the OpenXML SDK is pretty straight forward. I took this code from MSDN sample and tweaked it a bit to suit our needs.

private void CreateDocument(string path)
{
	// Create a Wordprocessing document. 
	using (WordprocessingDocument myDoc = 
	WordprocessingDocument.Create(path, WordprocessingDocumentType.Document))
	{
		// Add a new main document part. 
		MainDocumentPart mainPart = myDoc.AddMainDocumentPart();
		//Create DOM tree for simple document. 
		mainPart.Document = new Document();
		Body body = new Body();
		Paragraph p = new Paragraph();
		Run r = new Run();
		Text t = new Text("");
		//Append elements appropriately. 
		r.Append(t);
		p.Append(r);
		body.Append(p);
		mainPart.Document.Append(body);
		// Save changes to the main document part. 
		mainPart.Document.Save();
	}
}		

The ReplaceMailMerge function simply finds and replaces the mailmerge fields with whatever values we seem fit. Currently, I have hardcoded these values. However, you can wire it up so that these values come from some datasource instead.

private string ReplaceMailMerge(string input)
{
	input = input.Replace("{^FirstName^}", "Billy");
	input = input.Replace("{^LastName^}", "Bob");
	input = input.Replace("{^Title^}", "Dr.");
	return input;
}		

generateWordDocument is our work horse. It has two sections, the first section actually fills in the content in the document we created in step 1. It then adds some skeletal body to the document. We are relying on the AddAlternativeFormatImportPart method to simply add our richtext as XHTML to the document so that we do not need to do all the parsing. We will let the SDK handle the parsing and creating the document. The second section sends back the path of the new document to AJAX caller.

public void generateWordDocument
	(string htmlMarkup, string fullFilePath, string fileNameOnly)
{
	try
	{
		/*----------- Generate the Document -----------------------*/
		//put some title
		string pageTitle = Guid.NewGuid().ToString();
		//open the document
		using (WordprocessingDocument wordDoc = 
			WordprocessingDocument.Open(fullFilePath, true))
		{
			//get the document
			MainDocumentPart mainPart = wordDoc.MainDocumentPart;
			int altChunkIdCounter = 1;
			int blockLevelCounter = 1;

			string mainhtml = "<html><head><style type='text/css'>
			.catalogGeneralTable{border-collapse: 
			collapse;text-align: left;} .catalogGeneralTable 
			td, th{ padding: 5px; border: 1px solid #999999; }
			</style></head>
			<body style='font-family:Trebuchet MS;font-size:.9em;'>" 
			+ htmlMarkup 
			+ "</body></html>";
			string altChunkId = String.Format("AltChunkId{0}", 
			altChunkIdCounter++);

			//Import data as html content using Altchunk
			AlternativeFormatImportPart chunk = 
			mainPart.AddAlternativeFormatImportPart
			(AlternativeFormatImportPartType.Html, altChunkId);

			//add the chunk to the doc
			using (Stream chunkStream = chunk.GetStream
			(FileMode.Create, FileAccess.Write))
			{
				//Encoding.UTF8 is important to remove 
				//special characters
				using (StreamWriter stringWriter 
				= new StreamWriter(chunkStream, Encoding.UTF8)) 
				{
					stringWriter.Write(mainhtml);
				}
			}

			AltChunk altChunk = new AltChunk();
			altChunk.Id = altChunkId;
			//insert the text in the doc
			mainPart.Document.Body.InsertAt(altChunk, blockLevelCounter++);
			//save the document
			mainPart.Document.Save();
		}
		/*----------- End Generate the Document -----------------------*/
		
		/* ------- Send the response -----------*/
		//clear the response object
		HttpContext.Current.Response.ClearContent();
		//add the demilited string to the response object and write it. 
		string url = HttpContext.Current.Request.ApplicationPath 
				+ "/docs/" + fileNameOnly;
		HttpContext.Current.Response.Write(url);
		HttpContext.Current.Response.End();

		/* -------End Send the response -----------*/
	}
	catch (Exception ex)
	{
		HttpContext.Current.Response.Write(ex.Message.ToString());
	}
}		

Other Files of Interest

I have modified the config.js file (found under root-->scripts-->ckeditor) to create my own custom toolbar for the FCKeditor. You do not need this if you are going to use the out of the box editor.

CKEDITOR.editorConfig = function(config) {
	config.toolbar = 'myToolBar';
	config.toolbar_myToolBar =
	[
		['Source', '-', 'Save', 'NewPage', 'Preview', '-', 'Templates'],
		['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 
			'Print', 'SpellChecker', 'Scayt'],
		['Undo', 'Redo', '-', 'Find', 'Replace', '-', 
			'SelectAll', 'RemoveFormat'],
		'/',
		['Bold', 'Italic', 'Underline', 'Strike', '-', 
			'Subscript', 'Superscript'],
		['NumberedList', 'BulletedList', '-', 'Outdent', 
			'Indent', 'Blockquote', 'CreateDiv'],
		['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
		['Link', 'Unlink', 'Anchor'],
		['Image', 'Table', 'HorizontalRule', 'SpecialChar', 'PageBreak'],
		'/',
		['Styles', 'Format', 'Font', 'FontSize'],
		['TextColor', 'BGColor'],
		['Maximize', 'ShowBlocks']
	];
};		

Points of Interest

As you would have noticed, I have kept it to the bare minimum. Here are some of the things we can improve upon:

  1. Currently, I do not know of a way for JQuery to send in binary data back on a AJAX call. Hence, we are sending the document path. You will notice that I also have a method called DownloadFile. If you do not want to use AJAX, you can modify the generateWordDocument method to call this method instead. It will then write the document as binary data to the response object.
  2. I have added the mail merge dropdown outside our editor. It will be nice if we could include it in the toolbar.
  3. The "Save" button on the editor toolbar does a postback. It would be nice we could do an AJAX call instead.
  4. I had to turn of ValidateRequest="false" in the default.aspx page to avoid ASP.NET throwing exception as we are modifying control content on fly. There must be a better way of handling this.

History

  • 7th September, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nitin Kunte

United States United States

Comments and Discussions

 
QuestionOnline Richtext editor with Mail Merge - Setting Default Value in Format Pinmemberendrum1815-Feb-12 3:18 
QuestionHow shall I fix this error PinmemberMember 850362529-Dec-11 23:18 
GeneralMy vote of 5 PinmemberMonjurul Habib3-Nov-11 9:07 
QuestionThanks Pinmembersagar r.10-Aug-11 21:58 
AnswerRe: Thanks PinmemberJohn Spar24-Dec-11 4:32 
QuestionONLINE EDIT IN OPEN EXISTING DOCUMENT HOW??????? Pinmemberjamil shah28-Nov-10 20:09 
GeneralMy vote of 5 Pinmemberjamil shah28-Nov-10 20:08 
GeneralMy vote is 5 PinmemberNabin Kumar Jha21-Sep-10 0:02 
GeneralRe: My vote is 5 PinmemberNitin Kunte18-Oct-10 10:48 
GeneralVery excellent job PinmemberRaed Barouki20-Sep-10 20:49 
GeneralRe: Very excellent job PinmemberNitin Kunte19-Oct-10 7:00 
GeneralMy vote of 5 PinmemberMarcelo Ricardo de Oliveira20-Sep-10 3:02 
GeneralRe: My vote of 5 PinmemberNitin Kunte18-Oct-10 10:49 
GeneralMy vote of 5 Pinmemberfredchan52114-Sep-10 4:17 
GeneralRe: My vote of 5 PinmemberNitin Kunte19-Oct-10 7:00 
GeneralMy vote of 5 PinmemberRahulKhadikar8-Sep-10 1:23 
GeneralRe: My vote of 5 PinmemberNitin Kunte8-Sep-10 5:38 
GeneralMy vote of 5 PinmemberEric Xue (brokensnow)7-Sep-10 18:25 
GeneralRe: My vote of 5 PinmemberNitin Kunte8-Sep-10 5:37 
GeneralRe: My vote of 5 Pinmembertusharpat16-Sep-10 16:59 
GeneralMy vote of 5 PinmemberJohn Spar7-Sep-10 9:08 
GeneralMy vote of 5 PinmemberAnindya Chatterjee7-Sep-10 6:30 
GeneralRe: My vote of 5 PinmemberNitin Kunte7-Sep-10 6:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 7 Sep 2010
Article Copyright 2010 by Nitin Kunte
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid