In this article, we will learn how to implement CodeMirror and use ColdFusion and Lucee's file methods to create a browser-based interface for editing server-side code. This interface was designed to implement a browser-based content management system (CMS) for an upcoming version of Galaxie Blog



CodeMirror Working Example

The following example uses CodeMirror 5 and ColdFusion/Lucee. It allows users to interact with a web-based code editor to edit and save server-side ColdFusion/Lucee code. This was developed as a CMS feature to enable blog users to edit the navigation script at the top of this page. 

The code first reads data from the existing menu script and allows you to edit it inside a CodeMirror editor. Once the submit button is clicked at the bottom of the page, the application will write a new file with your changes.

This example uses the code below and was tested using Adobe ColdFusion and Lucee.


What is CodeMirror?

CodeMirror is a free, open-source software that allows you to implement a full-fledged code editor within a traditional HTML text area field. It is extensible and allows various customized options for multiple languages, such as syntax highlighting, auto-completion, and code hinting. In this example, we are using CodeMirror to implement a code editor that allows users to edit server-side code for content management purposes.


Differences Between CodeMirror Version 5 and 6

While the newest version of CodeMirror offers improved extensibility and customization, CodeMirror 6 is much more complex and requires a package manager and build process. CodeMirror 5, on the other hand, can be incorporated into a web application using scripts or standard JavaScript modules. 


How to Incorporate CodeMirror 5 into a ColdFusion Application


Download CodeMirror 5

CodeMirror 5 can be downloaded from the official repository at https://github.com/codemirror/codemirror5. For this example, I downloaded the zip file, extracted its contents, and uploaded it to my web server. 


Including the Required JavaScript Files

After CodeMirror has been uploaded to the server, the required JavaScript files—codemirror.css and codemirror.js—are used to load CodeMirror 5.

The autorefresh.js add-on script is often necessary to prevent a common CodeMirror 5 bug that causes the input container to be empty until it is clicked on, even though a value was set. This is often due to a rendering issue that requires the editor layout to be reloaded after the content value has been set.

It should be noted that even though this example also loads the jQuery library via CDN, jQuery is not needed for CodeMirror. I only load it to handle the form, the DOM-ready state, and AJAX.


<link rel="stylesheet" href="/galaxie/common/libs/codemirror5/lib/codemirror.css">
<!-- Note: I imported this file manually as the import process failed in the blog environment -->
<script src="/galaxie/common/libs/codemirror5/lib/codemirror.js"></script>
<!-- Include the auto refresh script- otherwise the content will not load unless you click on the editors div -->
<script src="/galaxie/common/libs/codemirror5/addon/display/autorefresh.js"></script>

Initialize CodeMirror

The following code is necessary to initialize CodeMirror. It is recommended that you initialize CodeMirror after the page has been loaded.

I place the initialization script inside the document onload function; more on that below. I also include autoRefresh: true to ensure that the editor is refreshed as soon as changes are made. Many other configuration options are available; some are shown in the full code below. 


<script>
	// Note: for the syntax highlighting to work, among other addons, this must be within the window onload event!
	window.onload = function() {
		// Create the text editor
		var cmEditor = CodeMirror.fromTextArea(document.getElementById("cmEditor"), {
			mode: "text/html",
			autoRefresh: true
		});
	};//window.onload
</script>

Put the CodeMirror Initialization Script Within a window.onload Function

It is critical to place the initialization code within a window.onload function if you want add-on functionality to work- such as syntax highlighting.

Typically, developers place initialization code in a document-ready block when using jQuery, as we are here. However, the code inside a document-ready block will run when the DOM is ready, even if the required scripts have not been completely loaded. The difference between the window.onload and document-ready events is the window.onload method will only run once the external resources have been completely loaded.

Also, if you want syntax highlighting or any CodeMirror add-on functionality, you must load all required resources to work. For example, syntax highlighting in HTML mixed mode requires the CSS, Javascript, XML, and HTML mixed CodeMirror libraries. I typically include all modes I may need when using CodeMirror—see the complete code below. As always, consult the CodeMirror 5 documentation if you are unsure about the required dependencies.


<!-- The following libraries are required when using syntax highlighting in HTMLMixed mode -->
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/css/css.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/javascript/javascript.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/xml/xml.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/htmlmixed/htmlmixed.js"></script>

Create the HTML Input Element

The editor can be placed inside an HTML text area or the document body. To embed the editor within the document body, use:


var codeMirrorEditor = CodeMirror(document.body)

 However, using a text area allows the developer to have better placement control:


<textarea id="cmEditor" style="width: 66%; height: 275px;">
</textarea> 

Using ColdFusion/Lucee File Methods to Populate the CodeMirror Editor

ColdFusion/Lucee has quite a few file management functions. This article will focus on the fileOpen, fileClose, fileReadLine, and fileWrite methods. We will explain each step below.

To populate our code editor, we will create an empty string variable to store the line data and use the fileOpen method to open the menu.cfm ColdFusion/Lucee template:


<!--- Set the file path --->
<cfset filePath = expandPath( "/blog/demo/codeMirror/menu.cfm" ) />
<!--- Create a var to store the line data --->
<cfset FileLines = "">
<!--- Open the file --->
<cfset fileTemplate = fileOpen( filePath, "read" ) />

Once the file is opened, I loop through its contents until the end using the fileIsEOF ColdFusion/Lucee method. This method returns a when it reaches the end of the file.

I use the fileReadLine during the loop to read each line and stuff the data into our fileLines string variable. I also append a carriage line (chr(10) to the string variable. 

At the end of the loop, I use the fileClose method to close the file. This is a crucial step to avoid file corruption and memory leaks.


<!--- Loop through the contents of the file. --->
<cfloop condition="!fileIsEOF( fileTemplate )">
	<!--- Read the line and append the data. --->
	<cfset fileLines = fileLines & chr(10) & fileReadLine( fileTemplate ) >
</cfloop>
<!--- Close the file. --->
<cfset fileClose( fileTemplate ) />

After reading and capturing each line, we populate the CodeMirror editor. I will explain the hidden form later in this article.


<!-- Hidden form to capture the code -->
<input type="hidden" id="cmEditorCode" name="cmEditorCode" value="">
<!-- Editor -->
<textarea id="cmEditor" style="width: 66%; height: 275px;">
	<cfoutput>#fileLines#</cfoutput>
</textarea>

Getting CodeMirror Editor Content and Writing it to a ColdFusion Page

Typically, when using forms, we can get form values by simply accessing the element ID or name of the form. However, this is not possible with CodeMirror or many other HTML5 controls. To get the editor value, we need to use editor.getValue().

In our example, we are using the following to access the editor value:


// Get the contents of the editor
var coldFusionCode = cmEditor.getValue();

Once we get the editor's value, I use the file write method to write the editor's contents to a ColdFusion/Lucee file. The following code will either overwrite an existing ColdFusion file if it exists or create a new file. Be careful using this approach, as this code will overwrite existing files and code!


<cfset filePath = expandPath( "/blog/demo/codeMirror/menu.cfm" ) />	
<!--- Save the file. Note: you may want to prettify the code with a formatter as all formatting such as line breaks, are lost. Note: the demo uses encodeForHtml for safety --->
<cfset fileWrite( file=filePath, data=trim(encodeForHtml ( Form.cmEditorCode ) ), charset="UTF-8")>

Full Code


Creating and Populating the CodeMirror Editor


<!doctype html>
<title>CodeMirror Demo</title>
<meta charset="utf-8"/>

<cfsilent>
	<!--- Set a root directory path. I am using several environments (for both ACF and Lucee) and the file locations are different depending upon the environment --->
	<cfset rootDirectory = "/blog">
	<!--- Preset vars --->
	<cfset filePath = expandPath( rootDirectory & "/demo/codeMirror/menu.cfm" ) />
	<cfset FileLines = "">

	<!--- Open and read the file. --->
	<cfset fileTemplate = fileOpen( filePath, "read" ) />

	<!--- Loop through the contents of the file. --->
	<cfloop condition="!fileIsEOF( fileTemplate )">
		<!--- Read the line and append the data. --->
		<cfset fileLines = fileLines & chr(10) & fileReadLine( fileTemplate ) >
	</cfloop>

	<!--- Close the file. --->
	<cfset fileClose( fileTemplate ) />
	
</cfsilent>

<link rel="stylesheet" href="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/lib/codemirror.css">
<link rel="stylesheet" href="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/show-hint.css">
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/lib/codemirror.js"></script>
<!-- Addons -->
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/edit/matchtags.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/edit/closebrackets.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/fold/xml-fold.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/html-hint.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/show-hint.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/xml-hint.js"></script>
<!-- Modes -->
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/css/css.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/javascript/javascript.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/htmlmixed/htmlmixed.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/markdown/markdown.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/sql/sql.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/xml/xml.js"></script>

<!--- Include jQuery via CDN. jQuery is not needed for CodeMirror to work, however, I use it to post the data to the server, typically using AJAX --->
<script
	src="https://code.jquery.com/jquery-3.5.1.min.js"
	integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
	crossorigin="anonymous"></script>

<script>
	// Note: for the syntax highlighting to work, among other addons, this must be within the window onload event!
	window.onload = function() {
		// Instantiate code mirror
		cmEditor = CodeMirror.fromTextArea(document.getElementById("code"), {
			mode: "text/html",
			autoRefresh: true,
			styleActiveLine: true,
			matchBrackets: true,
			autoCloseBrackets: true,
			smartIndent: true,
			tabSize: 3,
			indentWithTabs: true,
			lineWrapping: true,
			lineNumbers: true,
			readOnly: false,
			autofocus: true
		});
		// setSize( width, height ). An empty string will set the width to 100% of the container
		cmEditor.setSize('', 900); 
		
		// Submit button handler
		var codeSubmit = $('#codeSubmit');
		codeSubmit.on('click', function(e){ 
			// Get the contents of the editor
			var coldFusionCode = cmEditor.getValue();
			// Stuff the editor content into a hidden form
			$("#cmEditorCode").val( coldFusionCode );
			/* Note: carriage returns will be ignored. If you want to replace carriage returns with line breaks in the code use:
			$("#cmEditorCode").val( coldFusionCode.replace(/
/g, "<br/>") ); */
			setTimeout(function() {
				// Submit the form
				$('#codeForm').submit();
			}, 250);
		})

	};
</script>
	
<table align="center" width="100%" cellpadding="2" cellspacing="0">
 <form id="codeForm" name="codeForm" action="fileSave.cfm" method="post" enctype="multipart/form-data">
  <tr>
	<td align="right" style="width: 5%"> 
	</td>
	<td>
		<!-- Hidden form to capture the code -->
		<input type="hidden" id="cmEditorCode" name="cmEditorCode" value="">
		<!-- Editor -->
		<textarea id="code">
			<cfoutput>#fileLines#</cfoutput>
		</textarea> 
	</td>
	<td align="right" style="width: 5%"> 
	</td>
  </tr>
  <tr>
	<td></td>
	<td><hr noshade></td>
	<td></td>
  </tr>
  <tr>
	<td></td>
	<td>
	  <button id="codeSubmit" name="codeSubmit" type="button">Submit</button> 
	</td>
	<td></td>
  </tr>
  <tr>
	<td></td>
	<td><hr noshade></td>
	<td></td>
  </tr>
</form>
</table>
	  
</body>

Saving the Code


<!doctype html>
<title>CodeMirror Demo/File Saved</title>
<meta charset="utf-8"/>

<cfsilent>
	<!--- ---------------------------------   Save the file   --------------------------------- --->
	<!--- Set a root directory path. I am using several environments (for both ACF and Lucee) and the file locations are different depending upon the environment --->
	<cfset rootDirectory = "/blog">
	<!--- Set the path to new file that is to be created/overwritten --->
	<cfset filePath = expandPath( rootDirectory & "/demo/codeMirror/newMenu.cfm" ) />	
	<!--- Save the file. Note: you may want to prettify the code with a formatter as all formatting such as line breaks, are lost --->
	<cfset fileWrite( file=filePath, data=trim(encodeForHtml ( Form.cmEditorCode ) ), charset="UTF-8")>
	
	<!--- --------------------------------- Read the new File --------------------------------- --->
	<!--- Preset vars --->
	<cfset FileLines = "">

	<!--- Open and read the file. --->
	<cfset fileTemplate = fileOpen( filePath, "read" ) />

	<!--- Loop through the contents of the file. --->
	<cfloop condition="!fileIsEOF( fileTemplate )">
		<!--- Read the line and append the data. --->
		<cfset fileLines = fileLines & chr(10) & fileReadLine( fileTemplate ) >
	</cfloop>

	<!--- Close the file. --->
	<cfset fileClose( fileTemplate ) />
	
</cfsilent>

<link rel="stylesheet" href="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/lib/codemirror.css">
<link rel="stylesheet" href="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/show-hint.css">
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/lib/codemirror.js"></script>
<!-- Addons -->
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/edit/matchtags.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/edit/closebrackets.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/fold/xml-fold.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/html-hint.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/show-hint.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/addon/hint/xml-hint.js"></script>
<!-- Modes -->
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/css/css.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/javascript/javascript.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/htmlmixed/htmlmixed.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/markdown/markdown.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/sql/sql.js"></script>
<script src="<cfoutput>#rootDirectory#</cfoutput>/common/libs/codemirror5/mode/xml/xml.js"></script>

<!--- Include jQuery via CDN. jQuery is not needed for CodeMirror to work, however, I use it to post the data to the server, typically using AJAX --->
<script
	src="https://code.jquery.com/jquery-3.5.1.min.js"
	integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
	crossorigin="anonymous"></script>

<script>
	// Note: for the syntax highlighting to work, among other addons, this must be within the window onload event!
	window.onload = function() {
		// Instantiate code mirror
		cmEditor = CodeMirror.fromTextArea(document.getElementById("code"), {
			mode: "text/html",
			autoRefresh: true,
			styleActiveLine: true,
			matchBrackets: true,
			autoCloseBrackets: true,
			smartIndent: true,
			tabSize: 3,
			indentWithTabs: true,
			lineWrapping: true,
			lineNumbers: true,
			readOnly: false,
			autofocus: true
		});
		// setSize( width, height ). An empty string will set the width to 100% of the container
		cmEditor.setSize('', 900); 
		
		// Submit button handler
		var codeSubmit = $('#codeSubmit');
		codeSubmit.on('click', function(e){ 
			// Get the contents of the editor
			var coldFusionCode = cmEditor.getValue();
			// Stuff the editor content into a hidden form
			$("#cmEditorCode").val( coldFusionCode );
			/* Note: carriage returns will be ignored. If you want to replace carriage returns with line breaks in the code use:
			$("#cmEditorCode").val( coldFusionCode.replace(/
/g, "<br/>") ); */
			setTimeout(function() {
				// Submit the form
				$('#codeForm').submit();
			}, 250);
		})

	};
</script>
	
<table align="center" width="100%" cellpadding="2" cellspacing="0">
 <form id="codeForm" name="codeForm" action="fileSave.cfm" method="post" enctype="multipart/form-data">
  <tr>
	<td align="right" style="width: 5%"></td>
	<td>
		The file has been saved. New code is shown below. For safety, this code has been encoded for HTML.
	</td>
	<td align="right" style="width: 5%"></td>
  </tr>
  <tr>
	<td align="right" style="width: 5%"></td>
	<td>
		<!-- Hidden form to capture the code -->
		<input type="hidden" id="cmEditorCode" name="cmEditorCode" value="">
		<!-- Editor -->
		<textarea id="code">
			<cfoutput>#fileLines#</cfoutput>
		</textarea> 
	</td>
	<td align="right" style="width: 5%"></td>
  </tr>
  <tr>
	<td></td>
	<td><hr noshade></td>
	<td></td>
  </tr>
  <tr>
	<td></td>
	<td>
	  <button id="codeSubmit" name="codeSubmit" type="button">Submit</button> 
	</td>
	<td></td>
  </tr>
  <tr>
	<td></td>
	<td><hr noshade></td>
	<td></td>
  </tr>
</form>
</table>
	  
</body>

Further Reading