Marvin JS Example - Structure Checker (using JChem Microservices)

Back to index

Here is an example of how to integrate Structure Checker web service into Marvin JS editor (Structure Checker license is required). 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 example requires molchange.js that defines MolChangeHandler class to aggregate molecule change events.

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

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 which it listens. 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 with atom and bond unique IDs 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", { hasUID: true}).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) {
    function check(source) {
    var json = {};
    json.filter={};
    json.filter.untriggered = true;
    json.structure = {};
    json.structure.uniqueId = "_mjs::uid";
    json.structure.source = source;
    json.settings = [];
    for(var i = 0; i < widgets.length; i++) {
        var widget = widgets[i];
        if(widget.isEnabled()) {
            json.settings.push({"checkerSettings":{"checkerClass": widget.getId() }});
        }
    }
    if(json.settings.length == 0) {
        return;
    }
    send(json);
}

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

/* Sends an async request to Structure checker web service. */
function send(json) {
    // arrange the input for the Structure Checker web service
    $.ajax({
        "url":  + "/jws-checker-fixer/rest-v1/checker-fixer/"
        ,"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 steps array contains all the performed checkers. Each checker result can be referred by its checkerName field. A checker result can contain many fields but only the following ones are relevant in this case:

The checkerName and the description are 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 = [];
    for(index in data.steps) {
        var checkerResult = data.steps[index];
        var widget = getWidget(checkerResult.checkerName);
        if(widget) {
            // display error message
            var message = "("+checkerResult.checkerName+"): "+checkerResult.description;
            $('#checkers-panel-result').append(widget.createErrorWidget(message));
            // calculate context to highlight
            if(typeof checkerResult == 'object') {
                var indexes = {};
                indexes.atoms = getAtoms(checkerResult.atomIds);
                indexes.bonds = getBonds(checkerResult.bondIds);
                if(indexes.atoms.length || indexes.bonds.length) { // add highlight unless context is empty
                    highlights.push({
                        'style': {
                            'color': widget.getColor(),
                            'opacity': 0.25
                        },
                        'uids': indexes
                    });
                }
            }
        }
    }
    // highlight atoms and bonds
    marvinSketcherInstance.setHighlight(highlights);
}

Changing of the configuration (checking or unchecking a checkbox or modifying a 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