Browsed by
Tag: atto

Building An Atto Plugin (WIP)

Building An Atto Plugin (WIP)

My notes from working out how to build a plugin for Moodle’s Atto Editor.
(This post is not entirely finished – working out the dialogs)

Plugin Location

Atto Editor plugins can be found in /lib/editor/atto/plugins as defined here and listed here

Note an Atto Plugin is known as a sub-plugin rather than a full plugin although it follows a similar format.

Required Files

  • Version.php
  • /lang/en/atto_pluginname.php
  • /yui/src/button/

Version.php

defined('MOODLE_INTERNAL') || die();

$plugin->version   = 2022030700;        // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires  = 2020061511;        // Requires this Moodle version.
$plugin->component = 'atto_pluginname';  // Full name of the plugin (used for diagnostics). 

Current Moodle version can be found in Moodle’s own version.php file.

The component name is {plugin_type}_{plugin_name}
In this case the plugin type is “atto”

Language String File

This will contain all the translatable language strings for this plugin.
It should follow the frankenstyle naming convention of {plugin_type}_{plugin_name}.php i.e. the component name set in version.php

It should at least contain the name of the plugin in there.

$string['pluginname'] = 'Plugin Name';

YUI src button module

This is currently required but looking at the documentation it looks like Moodle are planning on replacing YUI with something else in the future. I worked through the documentation and experimented with it and have recorded more information below.

YUI Modules

Moodle used to use shifter to compile the YUI modules in the past. According to documentation it currently uses Grunt to compile the JS now but the documentation isn’t fully up to date on how this works in practise. I found much more information on how to use shifter so started off with this and will work on shifting to Grunt.

Basic File Structure

  • /build
    • This is where the compiled files get stored this is what Moodle actually loads.
  • /src
    • /button
      • /js
        • button.js
          (contents / working code for the module)
      • /meta
        • button.json
          (list of dependencies for the module)
      • build.json
        (essentially instructions for shifter or grunt on how to build the final js file)

Example build.json

{
  "name": "moodle-atto_pluginname-button",
  "builds": {
    "moodle-atto_pluginname-button": {
      "jsfiles": [
        "button.js"
      ]
    }
  }
}

Note the name “moodle-atto_pluginname-button” in “Builds” indicates the name of the folder it compiles to. The “Name” indicates the name of the javascript files after compiling.

Example /meta/button.json

{
    "moodle-atto_boostrap4moodle-button": {
        "requires": [
            "moodle-editor_atto-plugin"
        ]
    }
}

Example /js/button.js

Moodle Documentation has a list of examples for this file. Update the examples to use your component and plugin name. e.g. in the atto_strike example you would replace “atto_strike” with “atto_pluginname”

Compiling with Shifter

This is the old way of compiling the YUI module and reading through the threads on it, YUI will eventually be replaced but that may be sometime away yet due to the complexity. I’m going to take a look at compiling with Grunt as well but found more information on shifter so looked at it initially as a temporary way of compiling the YUI module.

To use shifter you need to have node.js installed then install shifter.

npm -g install shifter

Once installed navigate to your YUI /src folder and run the shifter watch command.

 cd ../yui/src/
 shifter --watch

Any change you make in button.js after that will cause the compiler to recompile all the files into the build folder.

Button Icon Locations

Icons need to be stored in /pix/filename within the plugin. SVG is supported 😊
They can then be referenced in button.js when creating a button – example below:

		this.addToolbarMenu({
			icon: 'e/bootstrap', 
			iconComponent: 'atto_pluginname',
			title: 'pluginname',
			globalItemConfig: {
				callback: this._changeStyle
			},
			items: items
		});

The icon name “e/bootstrap” is referring to an image stored at /pix/e/bootstrap.svg inside the plugin.
The iconComponent name is the plugin component name so it know to look in /lib/editor/atto/plugins/atto_pluginname
The title “pluginname” is reference to a language string in /lang/en/atto_pluginname.php

Experimenting with Button.js

I’ve added a few examples of what you can do in the YUI module. I learned a lot of this looking at the code for Core Moodle Atto plugins. A good one to check out is the atto_table plugin.

Using Language Strings in Button.js

You can reference language strings in your YUI module with the following reference:

M.util.get_string("stringReference", component)

Replace “stringReference” with the name of your string located in /lang/en/atto_pluginname.php

The variable component should be declared somewhere in the code and be the component name of your plugin e.g. atto_pluginname

The string should also be listed in the lib.php file for the plugin in the function atto_pluginname_strings_for_js (replace atto_pluginname with your plugin component name).

function atto_pluginname_strings_for_js() {
    global $PAGE;

    $PAGE->requires->strings_for_js(array('stringReference'),
                                    'atto_pluginname');
}

Reference : https://docs.moodle.org/dev/Atto#Atto_subplugin_Php_API

If using mustache templates you can also reference these strings using the following reference and passing the variable via javaScript

{{get_string "stringReference" component}}
var component = 'atto_pluginname',
EXAMPLETEMPLATE = '<div>{{get_string "stringReference" component}}</div>';

You can then use the below code to convert the mustache strings in the example template to the Language strings stored in your plugin language file ( /lang/en/atto_pluginname.php ). Remember to pass the component name (usually stored in a variable) and to reference the string in the plugin’s lib.php file otherwise it won’t work!

/* This example function can pass form content to a dialogue box in Atto */
_getDialogueContent: function() {
		
		// this will be the form template.
		var finaltemplate = Y.Handlebars.compile(EXAMPLETEMPLATE);
		
		// insert variables into the template
		var content = Y.Node.create(finaltemplate({
			component: component
		}));
		
		return content;
	},

Inserting a Template into Content Example

	 /**
	 * Change the title to the specified style.
	 *
	 * @method _insertTemplate
	 * @param {EventFacade} e
	 * @param {string} template
	 * @private
	 */
	_insertTemplate: function(e, template) {
		this._currentSelection = this.get('host').getSelection();
		var host = this.get('host');
		
		// Focus on the last point.
		host.setSelection(this._currentSelection);
		
		// And add the template. e.g. <p>test</p>
		host.insertContentAtFocusPoint(template);
		
		// Mark as updated
		this.markUpdated();
	}

A callback that can be added to globalItemConfig: { } to insert a HTML template into the main content editor. This can be extended to fill in information from a dialog form.

Note: I think there is probably a way to compile the template passed in like there is for the form dialogue but I ended up writing a custom one for inserting into Editor Content because if I use the same method as form dialogue it goes wrong – I’m suspecting I don’t have a setting right somewhere but I need to look deeper into this!

/**
	 * Replace Mustache Placeholders with content
	 *
	 * @method _replaceMustache
	 * @param {string} template (html)
	 * @param {object} variables
	 * @private
	 */
	_replaceMustache: function(template, variables) {
		
		// holder for the final output.
		var finalHtml = template;
		
		// this next bit is only if we have variables to play with.
		if(typeof variables == "object" && Object.keys(variables).length !== 0) {
			
			// Loop through the object
			for (const [key, value] of Object.entries(variables)) {
				
				// find each mustache key and replace with the value.
				var regex = new RegExp(`{{${key}}}`,`g`);
				finalHtml = finalHtml.replace(regex, value);
				
			}
			
		}
		
		return finalHtml;
	},

Troubleshooting

A few things that tripped me up while working out how to get this to work!

Button not appearing

Check it has been set in Site Administration > Plugins > Text Editors > Atto HTML Editor -> Atto Toolbar Settings ( ../admin/settings.php?section=editorsettingsatto )

Add “pluginname = pluginname” to the bottom of Toolbar Config to make it show at the end of the toolbar. You can also insert it inside a group of icons if needed.
Note you should change “pluginname” to match your component name

Question for later : is it possible to add it by default or is that a bad idea and if so why?

Missing Strings?

If a string is called in the YUI js files it must be specified in the language strings file in order to show. Otherwise you will get errors!

It also may need to be specified in lib.php in the atto_pluginname_strings_for_js() function.

function atto_pluginname_strings_for_js() {
    global $PAGE;

    $PAGE->requires->strings_for_js(array('accordion',
										  'pluginname',
										  'h3',
                                          'h4',
                                          'h5',
                                          'pre',
                                          'p'),
                                    'atto_pluginname');
}

Missing YUI component?

This seems very obvious in hindsight but although the documentation only mentions requiring yui/src/button.js you actually need javascript files in /yui/build/moodle-atto_pluginname-button/ to exist for example : moodle-atto_pluginname-button.js as well for this to work. Otherwise it just won’t load and you’ll get a missing module message in dev tools console. These files get automatically created when you compile with shifter or (presumably) Grunt.

YUI module not compiling

One of those silly obvious ones but make sure any custom functions you add are separated by a comma!

Useful Documentation

https://docs.moodle.org/dev/Atto
https://docs.moodle.org/dev/Frankenstyle
https://docs.moodle.org/dev/YUI/Modules
https://docs.moodle.org/dev/YUI/Shifter
https://docs.moodle.org/dev/Grunt