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
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:
add
function of the Checkers
object, selected structure checkers are initialized.onSketchLoaded
function.$(document).ready(function handleDocumentReady (e) {
var config = [
{id: "bondlength", color: "#ffe211", title: "Bond length"},
{id: "bondangle", color: "#ef6671", title: "Bond angle"},
{id: "atommap", 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 CheckerWidget
s 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:
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);
}
}