Scripting hooks

    About scripting hooks

    The scripting hooks are functions that can be added to a widget/view definition. In this way they can be used to extend /customize the behavior of the widget/view. In principle, the hook is called when certain event occurs in the application and before the corresponding action is executed. It is possible to cancel the relevant action by returning false from a hook, for example when a condition is not met.

    The list of available scripting hooks and their description

    onDeleteRows

    This scripting hook is available for views. The event is called when ‘delete row’ action is invoked. If the scripts associated with this event signal that they have handled the event the actions would stop there. If there is no script or the script decides not to consume the event the actions would proceed with their default behavior.

    An example of the onDeleteRows scripting hook use is shown below.

    onDeleteRows = { event ->
        // use java popup dialog
        def readln = javax.swing.JOptionPane.&showInputDialog
        def pass = readln 'Enter password:'
        // unless the user responds enters correct password (psswd), the event is directly consumed and the row is not deleted
        if (pass == 'psswd'){
           return false
        } else {
           println "INCORRECT PASSWORD!"
           return true
        }
    }

    beforeInsertWindow

    The scripting hook is called before the “New Row” dialog is invoked. It can be used for instance to validate whether certain user can insert rows at all. The following sample script asks user a password. If it is correctly entered, the “New Row” dialog is rendered. Otherwise, the hook is not consumed and nothing happens.

    beforeInsertWindow = { event ->
        // use java popup dialog
        def readln = javax.swing.JOptionPane.&showInputDialog
        def pass = readln 'Enter password:'
        // unless the user responds enters correct password (psswd), the event is directly consumed and the row is not deleted
        if (pass == 'psswd'){
            return false
        } else {
            println "INCORRECT PASSWORD!"
            return true
        }
    }

    beforeInsert

    This scripting hook is available for views. The event is called when new row is inserted. It means all the options about inserting new row are filled, the user clicks ‘OK’ and the action is invoked. We can for instance state some condition on substructure, formula or IUPAC name. The hook was formerly called onInsertRows

    An example of the beforeInsert scripting hook use is shown below. The script uses demo project and is run under “Pubchem grid view”. It allows the user to insert new row if and only if the DB Name field is “MOLI”. Otherwise the action is not invoked and the row is not inserted.

    beforeInsert = { event ->
        // use java popup dialog
        def dbname = 'DB name'
        def dbNameField = dataTree.getRootVertex().getEntity().getFields().getItems().find{it.name == dbname}
        def dbNameValue = event.values.getAt(dbNameField.id)
        if (dbNameValue == "MOLI"){
            return false
        } else {
            println "DB Name must be MOLI"
            return true
        }
    }

    afterInsert

    The script is invoked once new row is inserted. The following example fills two additional fields, username and textDate with the name of the user logged in and the date. It allows to track which user and when inserts some molecule.

    afterInsert = { event ->
        //Names of fields where username and date is stored
        def userFieldName="username"
        def dateFieldName="textDate"
        println "after edit operation"
    
        // at first, fields username and date must be manually defined in the database 
        // find the field "username":
    
        def userField = dataTree.getRootVertex().getEntity().getFields().getItems().find{it.name==userFieldName}
        def dateField = dataTree.getRootVertex().getEntity().getFields().getItems().find{it.name==dateFieldName}
        // get root vertex
        def vs=dataTree.getRootVertex()
        // get entity from the widget vertex state
        def ety=dataTree.getRootVertex().getEntity()
        // get id of the row you are adding
        selectedRowId=[event.insertedId]
    
        // go to the schema
        def rs=event.vertexState.resultSet
        def schema=dataTree.schema
    
        // get user 
        def user=DIFUtilities.findCapability(schema,IJCUserLoginService.class).getMe()
        // get date
        def today = new Date()
    
        // get entity data provider 
        def edp = ety.schema.dataProvider.getEntityDataProvider(ety)
    
        // get access to the environment through lock
        def lock = edp.lockable.withLock('Updating'){ envRW ->
            // control whether date and user fields are filled. If so, do not update them
            def data=event.vertexState.getData(selectedRowId,DFEnvironmentRO.DEV_NULL)
            println data[selectedRowId[0]][userField.id]
            if((data[selectedRowId[0]][userField.id] == null) && (data[selectedRowId[0]][dateField.id] == null)) {
                // Defines empty map
                def vals = [:]
    
                vals[userField.id] = user.getUsername()
                // date field is defined as a string, the format can be arbitrary
                vals[dateField.id] = today.format("yyyy-MM-dd \'at\' HH:mm:ss")
                // Create the DFUpdateDescription and update the DFEntityDataProvider
                def ud = DFUpdateDescription.create(ety, selectedRowId, vals)
                def submitList = Collections.singletonList(ud)
    
                edp.update(submitList, DFUndoConfig.OFF, envRW)
                println "Updating field $userFieldName to value ${user.getUsername()} and field $dateFieldName to value $today"
            }
        }
        return false
    }

    onDoubleClick

    This scripting hook is available for widgets. It is the first event which is called when double-clicking a widget and it is common to all scriptable form widgets. This event is available to all user roles.

    An example of the implementation of onDoubleClick hook can be found below.

    onDoubleClick = { event ->
        field = event.widget.boundFields[0]
        message = "Double-Click event in a widget " +
            "bound to '${field.name}' field from '${field.entity.name}' entity " +
            "and showing value '${event.value}'"
        println message // printed to IJC output window
        javax.swing.JOptionPane.showMessageDialog(null, message);
        rs = event.widget.form.resultSet
        rootVS = rs.rootVertexState
        // do something with DFResultSet or VertexState here
    }

    beforeEdit

    This scripting hook is available for widgets. When the user starts to edit the data in any way (Edit option in right click menu, F2 shortcut or double-clicking), the beforeEdit event is triggered. It can be used to run a script before editing a value in text field widget and e.g. control the editability of fields more precisely than just by using user roles.

    An example of the use of beforeEdit hook can be found below.

    // Adding a confirmation text message which asks whether a user wants to change the value
    beforeEdit = { event ->
        // use java popup dialog
        def readln = javax.swing.JOptionPane.&showInputDialog
        // newvalue stores the value edited by user. It should only accept yes/no values
        def newvalue = readln 'Do you really want to edit the value? (yes/no)'
        // unless user responds 'yes', the event is directly consumed
        if ((newvalue=='yes') || (newvalue=='no')) {
            if (newvalue=='yes') {
                return false
            }
        } else {
            println "the value must be yes or no"
            return true
        }
    }

    afterEdit

    This scripting hook is available for widgets. When the user finishes editing the data in a widget, the afterEdit event is called. It allows to run the script after editing, but yet before storing the value in the text field. This option can be used for example for normalization, auditing etc.

    An example of the use of afterEdit hook can be found below.

    // short script which notes the editing changes to the output
    afterEdit = { event ->
        def today = new Date() // current datetime value
        def field = event.widget.selectedFields[0] // stores selected field
        def vs = event.widget.getVertexState(field)
        def id = vs.selectedRowsIds // get current row id
        println "$today : change of value for ${field.name} (id=$id) from ${field.entity.name}'"
    }

    The overview of scripting hooks and their availability for widgets

    Scripting hooks - summary
    Containers
    Panel no support
    Tabbed Pane no support
    Widgets
    Single line text field onDoubleClick, beforeEdit, afterEdit
    Multi line text field onDoubleClick, beforeEdit, afterEdit
    Molecule pane onDoubleClick, beforeEdit, afterEdit
    Check box (for boolean fields) onDoubleClick, beforeEdit, afterEdit
    Date pane (for date fields) onDoubleClick, beforeEdit, afterEdit
    Label no support
    Button no support
    Browser pane no support
    List (for list fields) onDoubleClick, beforeEdit, afterEdit
    Tables
    Table onDoubleClick, beforeEdit, afterEdit
    Sheet onDoubleClick, beforeEdit, afterEdit
    Molecules matrix onDoubleClick, beforeEdit, afterEdit
    Tree table (experimental) no support
    Charts
    Box plot no support
    Histogram no support
    Radar chart no support
    Scatter plot no support

    {warning} Known issue

    In the case of table widgets that utilize an external editor of values ( e.g. molecules matrix that uses Marvin Sketch), the afterEdit event is called when the editor opens and not when the new data is going to be inserted.