September 11, 2019
by Tibor Lorántfy
modified at March 5, 2020

Browser control in ARCHICAD and JavaScript connection

Since ARCHICAD 23 the Dialog Manager modul was extended with a Browser control. The Browser control uses the Chromium Embedded Framework (CEF). Using this control an embedded browser can be implemented inside ARCHICAD, which can load any site from a given URL. Using JavaScript the loaded site can communicate with the C++ Add-On and through that it can retrieve information from or control ARCHICAD.

Embedded browser in ARCHICAD

This blog post demonstrates how to use this new Browser control and how to estabilish connection with ARCHICAD from JavaScript.

I implemented a new Add-On named “Browser Control Test” with a palette (dockable modeless dialog) in ARCHICAD which will load an HTML file. Therefore this post is also a good example for “how to implement a modeless dialog in ARCHICAD”.

I encourage you to download it, build it and test it in ARCHICAD 23!

Preparations

I had to implement a new Add-On named “Browser Control Test”.
The first step of implementing a new Add-On is building up the skeleton structure of the Add-On. I created the Browser_Control folder inside the installed DevKit’s Examples folder and set up the common Add-On folder structure.

Browser_Control Example Add-On structure
Structure of the Browser_Control Example Add-On

RFIX folder

RFIX folder contains the non-localizable platform independent resources (for example images). A GRC file must contain the descriptors of these non-localizable resources, so I created a text file (with UTF-8 with BOM encoding) Browser_ControlFix.grc into the RFIX folder.
Each Add-On must have a unique identifier called MDID, since it does not require localization, my Fix.grc will contain this 'MDID' resource. I generated a new identifier at my profile and copied the generated ids to this GRC file.

Generation of new identifiers for Add-Ons

The GRC file will be processed by GRAPHISOFT Resource Converter tool (ResConv).

'MDID' 32500 "Add-On Identifier" {
    628121456
    719857724
}

RINT folder

RINT folder contains the localizable resources (for example strings and dialogs). I created the Browser_Control.grc into this folder with the following content:

  • 'STR#' 32000: The string resource with 32000 identifier contains the main information of the Add-On: name and short description. It will be used by CheckEnvironment function and these string will appear inside the Add-On Manager dialog in ARCHICAD to show information about my Add-On.
  • 'STR#' 32500: The string resource with 32500 id defines a new menu system. The Add-On registers a new main menu named “Test” with a submenu named “Browser Control” containing a single menu item “Selection Handler Palette”.
    In ARCHICAD this menu system will look like the following way:
  • 'GDLG' 32500: Dialog description of the dockable palette with a browser control. See the description below.
'STR#' 32000 "Add-on Name and Description" {
/* [  1] */		"Browser Control Test"
/* [  2] */		"Browser Control Example"
}

'STR#' 32500 "Strings for the Menu" {
/* [   ] */		"Test"
/* [   ] */		"Browser Control"
/* [  1] */			"Selection Handler Palette^ES^EE^EI^ED^EW^E3^EL"
}

'GDLG'  32500    Palette | topCaption | close | grow   0   0  450  150  "Selection Handler"  {
/* [  1] */ Browser			0   0  450  150
}

'DLGH'  32500  DLG_32500_Browser_Palette {
1	"Browser Control"		Browser_0
}

‘GDLG’ dialog descriptor

'GDLG' resource defines a dialog. The Palette keyword after the identifier of the 'GDLG' resource means that it will be a palette, a dockable modeless dialog in ARCHICAD. topCaption keyword is trivial, close keyword means the dialog will have a close button in the corner and grow keyword results that the dialog will be resizable both horizontally and vertically. The keywords are followed by four numbers, which describe the position and size parameters (X, Y, Width, Heigh). In case of a resizable dialog these width and height parameters determines the minimum size of the dialog. Finally, before the braces the title of the dialog can be given.
Between the braces the controls of the dialog are listed. I want only one control on my dialog, a Browser control and it will completely fill my dialog, so it’s position is (0, 0) and it’s size is equal to the dialog’s size.
'DLGH' is the help resource for the dialog. It contains prompt messages for the items of the dialog.

RFIX.win folder

RFIX.win folder is for the Windows platform specific resources. This folder contains only the Browser_Control.rc2 file. The .rc2 will be processed by Windows system’s resource compiler tool. This file joins the separeted GRC resource files by including their compilation results, the .grc.rc2 files:

RFIX.mac folder

RFIX.mac folder is for the macOS platform specific resources. This folder must contain an Info.plist file (which will be placed into the built bundle) and the compileGRCs.pl script. That script helps to compile the resource files and creates Localizable.strings file into the bundle. The Xcode project file will execute this script.

Src folder

Src folder contains the C/C++ source code.
In this case the Main.cpp contains the 4 required functions of the Add-On (CheckEnvironment, RegisterInterface, Initialize, FreeData). It registers a menu, and tries to show/hide the dialog when the registered menu was selected.

GSErrCode __ACENV_CALL MenuCommandHandler (const API_MenuParams *menuParams)
{
	switch (menuParams->menuItemRef.menuResID) {
		case BrowserPaletteMenuResId:
			switch (menuParams->menuItemRef.itemIndex) {
				case BrowserPaletteMenuItemIndex:
					ShowOrHideBrowserPalette ();
					break;
			}
			break;
	}

	return NoError;
}

API_AddonType	__ACDLL_CALL	CheckEnvironment (API_EnvirParams* envir)
{
	RSGetIndString (&envir->addOnInfo.name, 32000, 1, ACAPI_GetOwnResModule ());
	RSGetIndString (&envir->addOnInfo.description, 32000, 2, ACAPI_GetOwnResModule ());

	return APIAddon_Preload;
}

GSErrCode	__ACDLL_CALL	RegisterInterface (void)
{
	GSErrCode err = ACAPI_Register_Menu (BrowserPaletteMenuResId, 0, MenuCode_UserDef, MenuFlag_Default);
	if (DBERROR (err != NoError))
		return err;

	return err;
}

GSErrCode __ACENV_CALL	Initialize (void)
{
	GSErrCode err = ACAPI_Install_MenuHandler (BrowserPaletteMenuResId, MenuCommandHandler);
	if (DBERROR (err != NoError))
		return err;

	err = ACAPI_Notify_CatchSelectionChange (BrowserPalette::SelectionChangeHandler);
	if (DBERROR (err != NoError))
		return err;

	err = BrowserPalette::RegisterPaletteControlCallBack ();
	if (DBERROR (err != NoError))
		return err;

	return err;
}

GSErrCode __ACENV_CALL	FreeData (void)
{
	return NoError;
}

How to implement a modeless dialog in ARCHICAD

BrowserPalette class implements a dockable modeless dialog (called palette in ARCHICAD). It’s a good example for a modeless dialog, feel free to reuse this code anytime if you want to implement one!
The BrowserPalette class is derived from the DG::Palette class and in the constructor it defines an own unique GUID for itself and pairs the dialog with the 'GDLG' resource. All modeless dialog must be registered in ARCHICAD to allow ARCHICAD to show/hide/disable/enable the dialog when it’s necessary (for example if you closed ARCHICAD with opened palette, then ARCHICAD will reopen your palette automatically when you restart ARCHICAD with the last profile), use ACAPI_RegisterModelessWindow function for this registration.

The logic behind the dialog

My plan is to implement a palette in ARCHICAD which lists the following information of the selected elements: GUID, type, element ID string. In addition it allows to remove specific elements from the selection and to add element identified by it’s GUID to the selection.
The constructor of the BrowserPalette class calls InitBrowserControl function. Beside initializing the starting page of the browser this function registers the ACAPI JavaScript object and updates the selection info on the page by executing UpdateSelectedElements JavaScript function, which is defined in the HTML code.

static const char*	siteURL = "http://home.sch.bme.hu/~lorantfyt/Selection_Test.html";
static const GS::Guid	paletteGuid ("{FEE27B6B-3873-4834-98B5-F0081AA4CD45}");

BrowserPalette::BrowserPalette () :
	DG::Palette (ACAPI_GetOwnResModule (), BrowserPaletteResId, ACAPI_GetOwnResModule (), paletteGuid),
	browser (GetReference (), BrowserId)
{
	Attach (*this);
	BeginEventProcessing ();
	InitBrowserControl ();
}

BrowserPalette::~BrowserPalette ()
{
	EndEventProcessing ();
}

void BrowserPalette::InitBrowserControl ()
{
	browser.LoadURL (siteURL);
	RegisterACAPIJavaScriptObject ();
	UpdateSelectedElementsOnHTML ();
}

void BrowserPalette::UpdateSelectedElementsOnHTML ()
{
	browser.ExecuteJS ("UpdateSelectedElements ()");
}

Upon startup this example loads the Selection_Test.html HTML page (the downloadable package contains the HTML file also). That HTML file defines an empty table and that table will be populated with the retrieved information of the selected elements from ARCHICAD using JavaScript by calling ACAPI.GetSelectedElements function.

<html>
<head>
	<title>Selection Test</title>

	<script type="text/javascript">
		function UpdateSelectedElements () {
			ACAPI.GetSelectedElements ().then (function (elemInfos) {
				...
				// populate the list with the selection info
				var index, len;
				for (index = 0, len = elemInfos.length; index < len; ++index) {
					...
					removeButton.onclick = function (e) {
						ACAPI.RemoveElementFromSelection (e.currentTarget.guid).then (function (res) {
						});
					};
					...
				}
				...
			});
		}

		function AddElementToSelection () {
			var elemGuidToAddInput = document.getElementById ('elemGuidToAdd');

			ACAPI.AddElementToSelection (elemGuidToAddInput.value).then (function (res) {
				elemGuidToAddInput.value = '';
			});
		}
	</script>
</head>
<body align="center">
	...
	<form>
		<input type="text" id="elemGuidToAdd" size="40">
		<input type="button" onclick="AddElementToSelection ()" value="+">
	</form>
	...
</body>
</html>

The ACAPI JavaScript object is registered from the C++ code and 3 functions were added to it (GetSelectedElements, AddElementToSelection, RemoveElementFromSelection). The given C++ lambda expressions (C++11 feature) define C++ callback functions, those will be executed when the JavaScript code calls the registered function.

void  BrowserPalette::RegisterACAPIJavaScriptObject ()
{
	DG::JSObject* jsACAPI = new DG::JSObject ("ACAPI");

	jsACAPI->AddItem (new DG::JSFunction ("GetSelectedElements", [] (GS::Ref<DG::JSBase>) {
		return ConvertToJavaScriptVariable (GetSelectedElements ());
	}));

	jsACAPI->AddItem (new DG::JSFunction ("AddElementToSelection", [] (GS::Ref<DG::JSBase> param) {
		ModifySelection (GetStringFromJavaScriptVariable (param), AddToSelection);
		return ConvertToJavaScriptVariable (true);
	}));

	jsACAPI->AddItem (new DG::JSFunction ("RemoveElementFromSelection", [] (GS::Ref<DG::JSBase> param) {
		ModifySelection (GetStringFromJavaScriptVariable (param), RemoveFromSelection);
		return ConvertToJavaScriptVariable (true);
	}));

	browser.RegisterAsynchJSObject (jsACAPI);
}

The SelectionChangeHandler function collects the information of the selected elements, since it was registered by ACAPI_Notify_CatchSelectionChange it will be called when the selection in ARCHICAD changes.
The callback associated to GetSelectedElements JavaScript function returns the collected information converted to a JavaScript array object.

GS::Array<BrowserPalette::ElementInfo> BrowserPalette::GetSelectedElements ()
{
	API_SelectionInfo	selectionInfo;
	GS::Array<API_Neig>	selNeigs;
	ACAPI_Selection_Get (&selectionInfo, &selNeigs, false, false);
	BMKillHandle ((GSHandle*)&selectionInfo.marquee.coords);

	GS::Array<BrowserPalette::ElementInfo> selectedElements;
	for (const API_Neig& neig : selNeigs) {
		API_Elem_Head elemHead = {};
		elemHead.guid = neig.guid;
		ACAPI_Element_GetHeader (&elemHead);

		ElementInfo elemInfo;
		elemInfo.guidStr = APIGuidToString (elemHead.guid);
		ACAPI_Goodies (APIAny_GetElemTypeNameID, (void*)elemHead.typeID, &elemInfo.typeName);
		ACAPI_Database (APIDb_GetElementInfoStringID, &elemHead.guid, &elemInfo.elemID);
		selectedElements.Push (elemInfo);
	}
	return selectedElements;
}

void BrowserPalette::ModifySelection (const GS::UniString& elemGuidStr, BrowserPalette::SelectionModification modification)
{
	ACAPI_Element_Select ({ API_Neig (APIGuidFromString (elemGuidStr.ToCStr ().Get ())) }, modification == AddToSelection);
}

It’s guaranteed that the registered JavaScript callback functions will be executed from the main thread, so it’s safe to execute any API functions inside them. The ACAPI_Element_Select is used in the AddElementToSelection and RemoveElementFromSelection JavaScript callback functions to manipulate the selection in ARCHICAD.

How to test it?

Unzip the downloaded file and place the “Browser_Control” folder into the “Examples” folder of the installed API Development Kit 23.
Use the attached Visual Studio project (.vcxproj file) to build the Add-On for Windows, and the Xcode project file (.xcodeproj) for macOS platform. The result of the build will be Browser_Control.apx on Windows platform and Browser_Control.bundle on macOS.
Place that result Add-On file into the Add-Ons folder (note that folder’s name is localized, so it’s name depends on your localized version) next to ARCHICAD application.

The example Browser control, Selection Handler Palette in action (on the left)
The example Browser control, Selection Handler Palette in action (video)

That’s all, now you can start ARCHICAD and try the functionality of the new Browser control by selecting the Browser Control menu item inside Test main menu.

How to debug JavaScript in CEF

You are able to debug your JavaScript code in Chrome browser if you set a valid, free port for CEF debugging.

On Windows platform modify the CefDebugPort value in your registry, see picture below.

CefDebugPort value in registry

On macOS platform you have to set that value in the com.graphisoft.debug plist file under the DG group.

Just start a Chrome Browser and open http://localhost:<CefDebugPort> (Note: change the <CefDebugPort> to the port you chose). There you can debug your JavaScript code: placing breakpoints and other useful features will help you to fix your code 😉

Paused on breakpoint in Chrome browser
The browser control is paused in debugger