Event Handlers

USolver has many features that you can use out of the box to create engaging content. However, sometime you need to implement custom behavior or perform an action that would otherwise require complicated spreadsheet logic. To address these use cases, USolver allows you to write JavaScript functions and provides flexible APIs to interact with the data and the canvas.

To illustrate how to use scripts, add a button, click on it, and view the Actions tab in the property bar. Here you can defined event handlers.

If you just need to populate a spreadsheet cell, you can use "Feedback Cell" property. However, if logic is too complicated, it might be easier to write a script. Click the "Edit Script" button that will open the Script Editor and type the following code:

let x = data.get('a1') data.set('a1', x + ' >> 2')

This code will trigger on button click in run time. You can also organize your code as separate functions that could be called individually, but still be within the same module. Here is an example how to do that:

reset() { // do something }, action() { // do something }

This code defines two empty functions: reset and action. Save the script by clicking the Save button on the toolbar, then select one of the functions in the drop down in the property panel. This will be the function that will be executed when you click the button.

Read/Write Spreadsheet Cells

You can read/write to/from a spreadsheet cell with "data" object as well as calculate any formula supported by spreadsheet without changing values in the spreadsheet.

data.get(cell) - read from a single or multiple cells.

action() { let a1 = data.get('a1') // read a single cell let ab = data.get('a1', 'b1') // read multiple cells at the same time let rg = data.get('a1:b5') // read range of cells a1:a5 // output values into console console.log('single:', a1, '2 cells:', ab, 'range:', rg) }

data.set(cell, value, [callBack]) - write to a single or multiple cells.

action() { data.set('a1', 5) // read a single cell data.set({ // set multiple cells at the same time a2: 10, a3: 15, a4: '=sin(a2)' }) data.set('a4', "=sin(a1:a3)", function (effected) { // set formula with a callback console.log('effected cells', effected) }) }

Changing cells might require recalculation dependent formulas. If it is the case, you can specify callBack function that will be called when recalculation is complete and a list of effected cells will be passed to this function.

data.calc(formula, callBack) - calculates a formula using spreadsheet data.

action() { data.calc("sum(a1:a5)", function (result) { console.log(result) }) }

Working with Data tables

This module also allows you to work with data tables.

data.dt.add(cell, data, callBack) - adds a row in a data table cell.

action() { data.dt.add('b2', { a: 1, b: 2, c: 3 }, function (effected) { console.log(effected) }) }

This function appends a row to a data table and triggers recalculation of dependent cells (if any). The callback function will be called when recalculation is completed with all the cells that were effected.

data.dt.get(cell, rowNumber) - returns specified row from a data table.

action() { let row = data.dt.get('b2', 1) console.log('row:', row) }

The index of the first row is 1. You can get number of rows using data.dt.size() method.

data.dt.remove(cell, rowNumber, callBack) - removes a row in a data table.

action() { data.dt.remove('b2', 1, function (effected) { console.log(effected) }) }

This function removes a row from a data table and triggers recalculation of dependent cells (if any).

data.dt.size(cell) - returns size of the data table.

action() { let size = data.dt.size('b2') console.log(size) }

data.dt.clear(cell, callBack) - clears data table in the specified cell.

action() { data.dt.clear('b2', function (effected) { console.log(effected) }) }

This function removes all rows in the data table cell specified and triggers recalculation of dependent cells (if any).

Working with Canvas Elements

The "canvas" modules allows to add/remove elements on the canvas as well as change properties of elements, navigate slide, etc.

canvas.list([slide], [full]) - returns names of all elements.

action() { let all = canvas.list() console.log(all) }

You can return names of all elements on a particular slide, however, if slide is not defined it will return names of all elements in the document. The slide argument is a number, where 1 is the first slide. If second argument is set to true, it will return entire object, not just names of the elements.

canvas.point([slide], x, y, [full]) - returns names of all elements at specified point on the canvas.

drag(event) { if (event.type == 'drag-move') { let all = canvas.point(null, event.canvasX, event.canvasY) console.log('Elements at the position:', all) } }

You can return names of all elements at specified position on the screen. The slide argument is a number, where 1 is the first slide. If forth argument is set to true, it will return entire object, not just names of the elements.

canvas.get(name) - returns properties of the element specified by name.

action() { let all = canvas.list() console.log(canvas.get(all[0])) }

canvas.set(name, changes) - set properties for the element specified by name.

action() { canvas.set('ELP1', { top: 10, left: 10 }) }

canvas.add(type, props) - adds element to the canvas.

action() { console.log('created', canvas.add('label', { left: 10, top: 10 })) }

The function will return to you properties of the created element.

canvas.remove(name) - removes specified element from the canvas.

action() { let all = canvas.list() if (all.length > 0) canvas.remove(all[0]) }

canvas.getBoxSize(name) - returns position and dimension of the specified element.

action() { let all = canvas.list() if (all.length > 0) { let size = canvas.getBoxSize(all[0]) console.log('size of', all[0], size) } }

Position and dimension of the element can be linked to a cell. If you want to return actual value of the coordinate, use getBoxSize() method.

canvas.getModel(name) - returns model of the specified element.

action() { let all = canvas.list() if (all.length > 0) { let model = canvas.getModel(all[0]) console.log('model', model) } }

canvas.nav.slide(slideNumber) - go to a slide.

action() { canvas.nav.slide(2) // go to second slide }

canvas.nav.boobmark(name) - scroll to element with specified name.

action() { canvas.nav.bookmark('TXT1') }

Using External Libraries And Globals

USolver uses a lot of open source libraries internally. Some of them are available to you to use too. Here is the list of the libraries that you can use in your code: jQuery, lodash, d3. It is not always the best idea to manipulate DOM directly using this libraries.

Each module works in an isolated environment and they have its own variable scopes. If you declare variable in module A, it will not be visible in module B. However, there is a special object "global" that is used to pass data between modules. Here is an example how to use same variable in 2 different modules:

Module A

global.a = 1 exports = { action: function () { global.a += 1 console.log('global.a', global.a) } }

Module B

exports = { action: function () { global.a += 1 console.log('global.a', global.a) } }

Please note that alternative syntax with exports is used here. It is needed in order to define initial value of the global variable.

Using System Level USolver functions

The two libraries defined above are high level abstraction to most useful USolver functionality. However, you can get access to low level USolver functions in the following way:

action() { let udoc = require('system.udoc') let ucanvas = require('system.canvas') console.log('system.udoc', udoc) console.log('system.canvas', ucanvas) }

Debugging Your Code

Use built-in JavaScript tools to debug your code. Most frequently used method is to output into console using console.log() or setting up debugger command to stop execution of the script in the debug mode.

action() { debugger let all = canvas.list() }