Sitecore Insiders

Code Editor for custom field using SPEAK & CodeMirror– Part 4

Part 4 – Adding CodeMirror JavaScript library to the SPEAK dialog

This is the last part of this tutorial, the Sitecore only parts are done, now it’s time for JavaScript only… or not. Here you will learn how to integrate CodeMirror JavaScript library with SPEAK dialog.

I’s a bit more than import a script and bind it to an input… otherwise it wouldn’t be funny. There are some little details to care about.

Btw, check here the previous step.

What is CodeMirror and how it works?

Short story, “CodeMirror is a JavaScript library that can be used to create a relatively pleasant editor interface for code-like content“. It is an engine extended by addons for each language / capability and works as easy as import files and add some parameters to config object.

You choose an input or make it create a new one for you. Then it will hide it and draws the code editor using html elements. In this case, Sitecore renders the Textarea with bind to Backbone model and it will hide it.

I found a difference on the values being listened, without going much further, it seems that CodeMirror uses the “value” property and Backbone / Sitecore uses the “text” property of the Textarea. To solve it, I created the “save” function to update the Sitecore Textarea value.

Importing CodeMirror library

Remember the JS PageCode file created? Let’s get back to it.

The SPEAK framework uses Requires, JQuery, Underscore, Backbone and Knockout! And a Sitecore framework too. This is very important to be aware due to plugins compatibility, for example, last versions of CodeMirror are not compatible with Underscore, the last working version is the one used here, “5.59.2”.

All the inputs and buttons in the dialog are part of a Backbone model masked behind Sitecore framework. This eases the job, and the rules engine is in the shadows, so you can focus on doing what you need.

If you are not familiar with RequireJS, let me tell you that it doesn’t import CSS files. You need to import them dynamically or add them in dialog configuration. The same way we add the PageCode JS file, there is a place for stylesheets. In this case, I chose to keep CodeMirror separated and centralized in the JS file since you can configure here which addons you want.

Here I chose to add hint and lint for JavaScript addons.

$('head').append('<link rel="stylesheet" type="text/css" href="/sitecore/shell/Applications/Dialogs/CodeEditor/codemirror-5.59.2/lib/codemirror.css">');
$('head').append('<link rel="stylesheet" type="text/css" href="/sitecore/shell/Applications/Dialogs/CodeEditor/codemirror-5.59.2/addon/hint/show-hint.css">');
$('head').append('<link rel="stylesheet" type="text/css" href="/sitecore/shell/Applications/Dialogs/CodeEditor/codemirror-5.59.2/addon/lint/lint.css">');

require.config({
    baseUrl: "/sitecore/shell/Applications/Dialogs/CodeEditor",
    paths: {
        "jshint": "https://cdnjs.cloudflare.com/ajax/libs/jshint/2.6.3/jshint",
    },
    waitSeconds: 40,
    packages: [{
        name: "codemirror",
        location: "/sitecore/shell/Applications/Dialogs/CodeEditor/codemirror-5.59.2",
        main: "lib/codemirror"
    }],
});

define(["sitecore",
    "codemirror",
    "codemirror/mode/javascript/javascript",
    "codemirror/addon/hint/show-hint",
    "codemirror/addon/hint/anyword-hint",
    "codemirror/addon/hint/javascript-hint",
    "codemirror/addon/lint/lint",
    "codemirror/addon/lint/javascript-lint",
    "jshint",
], function (Sitecore, CodeMirror) {
    var CodeEditor = Sitecore.Definitions.App.extend({
        save: function () {
            if (!this.CodeText.get('text').trim())
                this.CodeText.set('text', "__#!$No value$!#__");
            console.log('What a save!');
        },
        initialized: function () {}
    });
    return CodeEditor;
});

Dissecting the code:

  • The first CSS is required for the basics of CodeMirror and the other 2 are related to addons.
  • Require configuration using “packages”

packages: [{
location: “/sitecore/shell/Applications/Dialogs/CodeEditor/codemirror-5.59.2”,
name: “codemirror”, //Alias for the location above
main: “lib/codemirror” //Default JS file to load when using the name above without any relative path concatenated
}]

On the “define” use the “name“+”relative path” to get other JS files in the “location“, like, “codemirror/addon/hint/show-hint“.

  • JSHint is a library necessary for the hint addon. When adding addons, check for the imports in the example.
  • Then on the “define” you import each addon.

Integrating CodeMirror

The topic was about the import part and the following is the real integration, the bind to Textarea.

var CodeEditorDialog = Sitecore.Definitions.App.extend({
	CodeEditor: {},
	save: function () {   
		this.CodeEditor.save();
             
		var text = this.CodeEditor.getTextArea().value;
		//if the text is empty replace it by a token to distinguish from cancel result
		if (!text.trim())
			text = "__#!$No value$!#__";

		this.CodeText.set("text", text);
	},
	initialized: function () {
		var self = this;
		var element = document.querySelectorAll('[data-sc-id="CodeText"]')[0];
		this.CodeEditor = CodeMirror.fromTextArea(element, {
			lineNumbers: true,
			mode: { name: "javascript" },
			indentWithTabs: true,
			lint: true,
			hint: CodeMirror.hint.javascript,
			autohint: true,
			extraKeys: { "Shift-Space": "autocomplete" },
			gutters: ["CodeMirror-lint-markers"],
			readOnly: false,
			matchBrackets: true,
			autoCloseBrackets: true
		});

		var self = this;
		var updateTextArea = function () {

			if (self.CodeEditor.doc.getValue() != self.CodeText.get("text"))
				self.SaveButton.set("isEnabled", true);
			else
				self.SaveButton.set("isEnabled", false);
		};
		this.CodeEditor.on('change', updateTextArea, this);

		this.SaveButton.set("isEnabled", false);
	}
});
return CodeEditorDialog;

Dissecting the code:

  • Save: make the code editor “commit” the value, transform it and update the Sitecore CodeText model value with it.
  • Initialized:
    • As Sitecore say, “SPEAK assigns the component ID you specify for components to the rendered HTML components as the data-sc-id attribute”, so, to get Textarea, document.querySelectorAll(‘[data-sc-id=”CodeText”]’)[0].
    • The CodeMirror.fromTextArea will make the magic on the Textarea using a config object. I will let you read the docs to understand how much you can do.
    • On text change, call the “updateTextArea” to do the fancy enable / disable.
    • Set SaveButton as disabled by default, this will only change if code is different.

Cosmetic

If you compare your custom Multi-line Text with the original one, you can’t resize yours, why? Whyyy?

Simple… you need to add one more configuration to your CodeEditor Sitecore config file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>

    ...

    <fieldTypes>
      <!-- Custom Types -->
      <fieldType name="Code" type="Sitecore.Data.Fields.TextField,Sitecore.Kernel" resizable="true"/>
    </fieldTypes>
  </sitecore>
</configuration>

Conclusion

Very simple isn’t it? 🙂

Source Code

Still in doubt with something? Check here. Have in mind that this is the final product.

https://github.com/sitecoreinsiders/SitecoreRepo/blob/v1.0/SitecoreInsiders.Shell.CodeEditor/sitecore/shell/Applications/Dialogs/CodeEditor/CodeEditor.js

Bruno Nunes

Sitecore developer since 2015, passed by 7.5, 8.2, 9.1, next step will be the 10!
Self-motivated to bring new capabilities to Sitecore and to adapt it for every business case.
Currently working on Noesis as Sitecore Architect / Tech Lead.

Add comment

Bruno Nunes

Sitecore developer since 2015, passed by 7.5, 8.2, 9.1, next step will be the 10!
Self-motivated to bring new capabilities to Sitecore and to adapt it for every business case.
Currently working on Noesis as Sitecore Architect / Tech Lead.