Marvin JS Example - Structure Checker

Back to index

Here is an example how to integrate Structure Checker web service into Marvin JS editor (In this case JChem WS requires the Structure Checker license). If the molecule changes, its source will be sent to the Structure Checker to evaluate the structure with the selected types of checkers. If any structure error is detected, it is highlighted on the structure and a detailed error message appears below the editor.

In this example, only Bond Length, Bond Angle and Atom Map checkers are used. The full list of checkers are available here: Checker List

Structure Checker configuration

Structure Errors

Code comments

This examples requires molchange.js that defines MolChangeHandler class to aggregate molecule change events.

First of all, CheckersWS checks which services are availables. It provides a Promise to notify when the list of the available services arrived (or notify if services are unreachables).

After that, the following steps will be executed:

$(document).ready(function handleDocumentReady (e) {
	var config = [ 
		{id: "BondLengthChecker", color: "#ffe211", title: "Bond length"},
		{id: "BondAngleChecker", color: "#ef6671", title: "Bond angle"},
		{id: "AtomMapChecker", color: "#d26ff1", title: "Atom map"}
	];
	CheckersWS.then(function(services) {
		for(var i = 0; i < config.length; i++) {
			if($.inArray(config[i].id, services)) {
				Checkers.add(config[i].id, config[i].color, config[i].title);
			}
		}
		MarvinJSUtil.getEditor("#sketch").then(onSketchLoaded, function () {
			alert("Cannot retrieve sketcher instance from iframe");
		});
	}, function(error) {
		$('#checkers-panel-result').html('structure checker is not available');
	});
});
});

The onSketchLoaded function instantiates a MolChangeHandler. Its first parameter is the reference of the editor to listen. The second one is a callback function that is performed at each mol change event.

function onSketchLoaded(sketcherInstance) {
	marvinSketcherInstance = sketcherInstance;
	// aggregator for molecule change events
	new MolChangeHandler(sketcherInstance, onMolChange);
}

If you take a look at the add function of Checkers, you can see that it creates a CheckerWidget object for each checker.

	/** Appends a new checker to the document.
	* @param id - the name of the checker as referred in JChem WS (see JChem WS documentation)
	* @param color - colorizes the result (atoms/bonds) in the editor with this color
	* @param title - the short description of the checker that displays in the config panel below.
	*/
	function add(id, color, title) {
		var widget = new CheckerWidget(id, color, title);
		$("#checkers-panel-config").append(widget.asWidget());
		widgets.push(widget);
	}

Returning to the onMolChange function, you can see that it resets the current highlight on the sketcher, gets the source of the current structure and performs a structure check on it.

function reset() {
	// reset current highlight
	marvinSketcherInstance.setHighlight({});
	$('#checkers-panel-result').empty();
}

var last = null;

function onMolChange(e) {
	last = e;
	reset();
	e.target.exportStructure("mrv").then(function(source) {
		if(!e.isDeprecated) { // unless a newer molchange event deprecate this event
			e.source = source;
			Checkers.check(source);
		}
	},alert);
}

If you take a closer look at the Checkers.check function, you can see those CheckerWidgets are enabled where the checkboxes were previously checked. It creates the input for the Structure Checker web service. Finally, it calls the send method with the created object.

	/**
	* Performs the structure checking on the given molecule source.
	* @param source - the molecule source in MRV format.
	*/
	function check(source) {
		var json = {};
		json.structure = source;
		json.parameters = {};
		json.parameters.checkers = [];
		for(var i = 0; i < widgets.length; i++) {
			var widget = widgets[i];
			if(widget.isEnabled()) {
				json.parameters.checkers.push({"checkerId": widget.getId() });
			}
		}
		if(json.parameters.checkers.length == 0) {
			return;
		}
		send(json);
	}

The send invokes the web service, then call onSuccess function when the response of web service is received or onFail if any error occured.

	/* Sends an async request to Structure checker web service. */
	function send(json) {
		var wsBase = getDefaultServicesPrefix();
		// arrange the input for the Structure Checker web service
		$.ajax({
			"url": wsBase + "/rest-v0/util/calculate/structureChecker"
			,"type": "POST"
			,"dataType": "json"
			,"contentType": "application/json"
			,"data": JSON.stringify(json)
		}).done(function (data, textStatus, jqXHR) {
			onSuccess(data);
		}).fail(onFail);
	}

The onSuccess function gets the response of the web service as a parameter (data). Its header field contains the id of the performed checkers. Each checker result can be referred with its own id. E.g. data.bondlength describes the result of bondlength checker. A checker result can contain many fields but only the following one are relevant in this case:

The checkerName and the decription is displayed on the current page in the checkers-panel-result div. Meanwhile the affected atoms and bonds are highlighted in the sketcher.
/* Callback to process web service result. */
	function onSuccess(data) {
		var highlights = [];
		if(typeof data.headers == 'object') {
			for(checkerId in data.headers) {
				if(data.headers.hasOwnProperty(checkerId) && isCheckerResult(data[checkerId])) {
					var widget = getWidget(checkerId);
					if(widget) {
						// display error message
						var message = "("+data[checkerId].checkerName+"): "+data[checkerId].description;
						$('#checkers-panel-result').append(widget.createErrorWidget(message));
						// calculate context to highlight
						if(typeof data[checkerId].errors == 'object') {
							var indexes = {};
							indexes.atoms = getAtoms(data[checkerId].errors.atoms);
							indexes.bonds = getBonds(data[checkerId].errors.bonds);
							if(indexes.atoms.length || indexes.bonds.length) { // add highlight unless context is empty
								highlights.push({
									'style': {
										'color': widget.getColor(),
										'opacity': 0.25
									},
									'indexes': indexes
								});
							}
						}
					}
				}
			}	
		}
		// highlight atoms and bonds
		marvinSketcherInstance.setHighlight(highlights);
	}

Changing of the configuration (check/uncheck checkbox or modify checker color) also triggers the checking of the structure (whose source was already stored in the source property of the last molchange event).

function onConfigChange(e) {
	// reevaluate last consumed molchange event when congfiguration is changed
	if(last && !last.isDeprecated && (typeof last.source == 'string')) {
		reset();
		Checkers.check(last.source);
	}
}

Back to index