Skip to content

Instantly share code, notes, and snippets.

@ThomasRohde
Last active July 12, 2023 14:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasRohde/92ab43ec8982c863bd2a85ae3907c701 to your computer and use it in GitHub Desktop.
Save ThomasRohde/92ab43ec8982c863bd2a85ae3907c701 to your computer and use it in GitHub Desktop.
Bulk property editor for #Archi #JArchi #ArchiMateTool
/*
Author: Thomas Klok Rohde
Description: Edit properties of selected elements in a spreadsheet
History:
October 22, 2022 : Created
*/
console.show();
console.clear();
let objects = [];
//$(selection).each(o => { if ($(o).is("element")) objects.push(o); } );
$(selection).each(o => objects.push(o));
if (objects.length == 0) throw new Error("No elements selected");
// Super-set of all the properties defined for the selected objects
let properties = new Set();
objects.forEach(o => {
let props = o.prop();
props.forEach(p => properties.add(p));
});
let columns = new Array({
type: 'text',
width: '250',
name: 'name',
title: 'Name',
align: 'left',
readOnly: true,
});
let mapping = new Map();
let reverse = new Map();
let jstypes = new Set(["hidden", "text", "checkbox", "calendar", "dropdown", "color", "image", "html"]);
properties.forEach(p => {
let cmps = p.split(":");
let jstype = 'text';
let jsvalues;
let jsname = cmps[1];
if (jstypes.has(jsname)) {
if (cmps.length == 2) {
// Syntax: <name> | <name> ":" <JSON object>
let parse = cmps[1].split(/\s*(\w+)\s*(\[[^\]]*\])/);
if (parse.length > 0) {
if (parse.length == 1)
jstype = parse[0];
else
jstype = parse[1];
if (parse.length == 4) {
try {
jsvalues = JSON.parse(parse[2]);
}
catch (error) {
console.log("Not a JSON value: ", parse[2]);
}
}
}
else jsname = cmps[1];
}
} else jsname = p;
mapping.set(jsname, p);
reverse.set(p, jsname);
let column = {
type: jstype,
width: '100',
name: jsname,
align: 'left',
title: jsname
};
if (jsvalues && jstype == 'dropdown') column.source = jsvalues;
if (jstype == 'color') column.render = 'square';
if (jstype == 'rating') column.render = 'stars';
if (jstype == 'calendar') column.options = { format: 'DD/MM/YYYY' };
columns.push(column);
})
// We need the id's to properly get the data back to the elements - but we don't need to show them
columns.push({
type: 'hidden',
name: 'id',
align: 'left',
title: 'id'
});
let data = [];
objects.forEach(o => {
let row = {
name: ($(o).is("relationship") ? `${o.type} (${o.source.name} - ${o.target.name})` : o.name),
};
properties.forEach(p => {
let value = o.prop(p);
row[reverse.get(p)] = (value) ? o.prop(p) : "";
})
row.id = o.id;
data.push(row);
})
const SWT = Java.type('org.eclipse.swt.SWT');
const FillLayout = Java.type('org.eclipse.swt.layout.FillLayout');
const Shell = Java.type('org.eclipse.swt.widgets.Shell');
const Browser = Java.type('org.eclipse.swt.browser.Browser');
const ProgressAdapter = Java.extend(Java.type('org.eclipse.swt.browser.ProgressAdapter'));
const LocationAdapter = Java.extend(Java.type('org.eclipse.swt.browser.LocationAdapter'));
const CustomFunction = Java.extend(Java.type('org.eclipse.swt.browser.BrowserFunction'));
const IArchiImages = Java.type('com.archimatetool.editor.ui.IArchiImages');
const ImageFactory = Java.type('com.archimatetool.editor.ui.ImageFactory');
let display = shell.getDisplay();
let newShell = new Shell(display, SWT.SHELL_TRIM | SWT.ON_TOP | SWT.APPLICATION_MODAL);
newShell.setText("Edit properties");
newShell.setLayout(new FillLayout());
let sheet = [];
let headers = [];
let jsondata = {};
html = `<html>
<script src="${__DIR__ + "/lib/jexcel.js"}"></script>
<script src="${__DIR__ + "/lib/jsuites.js"}"></script>
<link rel="stylesheet" href="https://bossanova.uk/jspreadsheet/v4/jexcel.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v4/jsuites.css" type="text/css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<style>
:root {
font-family: Sans-serif;
}
button {
margin-right: 10px;
padding: 5px;
width: 100px;
}
.jexcel > tbody > tr > td.readonly {
color: rgba(0,0,0,0.6);
}
table
{
font-size:0.8em;
}
</style>
<div id="buttons">
<button onclick="okPressed()">Ok</button><button onclick="cancelPressed()">Cancel</button>
<p style="font-size: 0.7em;">Double-click in a cell to edit. Right-click in the table header to add or rename properties (columns) or save to Excel. Empty cells will delete properties.
Keyboard shortcuts work as expected, including copy/paste and undo. You can add a calculated column and enter an 'Excel' formula, e.g., '=B2*1.1'. The calculated values will be returned and the formulas lost.
</p>
</div>
<div id="spreadsheet"></div>
<script>
let data = ${JSON.stringify(data, null, 3)};
let columns = ${JSON.stringify(columns, null, 3)};
function okPressed() {
okPressedEvent(
JSON.stringify(table.getData(false,false)),
JSON.stringify(table.getHeaders(true)),
JSON.stringify(table.getJson()));
}
function cancelPressed() {
cancelPressedEvent();
}
let table = jspreadsheet(document.getElementById('spreadsheet'), {
data: data,
columns: columns,
allowDeleteColumn: false,
includeHeadersOnDownload: true,
freezeColumns: 1,
allowInsertRow: false,
allowManualInsertRow: false,
allowDeleteRow: false,
allowDeletingAllRows: false,
copyCompatibility: true
});
</script>
</html>`;
var okPressed = false;
var cancelPressed = false;
let browser = new Browser(newShell, SWT.NONE);
browser.addProgressListener(new ProgressAdapter({
completed: function (event) {
let fncOk = new CustomFunction(browser, "okPressedEvent", {
function: (args) => {
okPressed = true;
sheet = JSON.parse(args[0]);
headers = JSON.parse(args[1]);
jsondata = JSON.parse(args[2]);
}
});
let fncCancel = new CustomFunction(browser, "cancelPressedEvent", {
function: (args) => {
cancelPressed = true;
}
});
browser.addLocationListener(new LocationAdapter({
changed: (e) => {
browser.removeLocationListener(this);
fncOk.dispose();
fncCancel.dispose();
}
}));
}
}));
// Write the HTML to a temporary file, so we are allowed to execute a local script
let System = Java.type('java.lang.System');
let tmpfile = System.getProperty("java.io.tmpdir") + "editproperties.html";
$.fs.writeFile(tmpfile, html);
browser.setUrl("file:///" + tmpfile);
// Set icon to Archi icon
newShell.setImage(IArchiImages.ImageFactory.getImage(IArchiImages.ICON_APP));
// Some sizing heuristics. I have no clue what I'm doing!
// newShell.setSize(((columns.length * 100) + 250 + 50) * 1.25, (objects.length > 30 ? 30 : objects.length) * 41 * 1.25);
newShell.open();
while (!newShell.isDisposed() && !okPressed && !cancelPressed) {
if (!display.readAndDispatch()) display.sleep();
}
if (okPressed) {
jsondata.forEach(row => {
let concept = $('#' + row.id);
let props = new Set();
concept.prop().forEach(p => props.add(p));
for (const key in row) {
// Skip if columns are id or name - we shouldn't mess with them
if (key == "id" || key == "name") continue;
// New columns will have the column number as key in the jsondata
let columnId = parseInt(key);
let realKey = key;
if (!isNaN(columnId))
// Praying that headers contain the column names in the right order!
realKey = headers[columnId] ? headers[columnId] : realKey;
// The table name have typing removed for readability. Fetch the original property names
let originalKey = mapping.get(realKey);
if (!originalKey) originalKey = realKey;
if (row[key])
concept.prop(originalKey, row[key].toString());
else
concept.removeProp(originalKey);
// Make sure all keys are marked as visited
props.delete(key);
props.delete(realKey);
props.delete(originalKey);
}
// Remove properties from renamed columns
props.forEach(p => {
concept.removeProp(p);
})
})
}
else if (cancelPressed)
console.log('Cancelled.')
newShell.dispose();
@trischbeck
Copy link

Hi Thomas, nothing happens when I run this script. All I see is a window with instructions and two buttons "OK" and "cancel". Any ideas? Kind regards, Thomas.

@ThomasRohde
Copy link
Author

@trischbeck Sorry for the delay - I don't check alerts often. I have commented out the setSize() code, and I got it to work again here. Please remember to select a elements with some properties.

@trischbeck
Copy link

Dear @ThomasRohde - Thanks for your response. I tried many thinks now. Commented out the setSize() and also placed jsuites.js and excel.js in the lib directory. But yet I only get the welcome screen and nothing else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment