Sending Multiple Callbacks in your HTML menus

Here is a chunk of some my Javascript code that sends the information back to the Ruby:

function callRuby(ActionName) 
{	

	// Basic Options

	var s0 = document.getElementById("rooftype");
	var v0 = s0.options[s0.selectedIndex].value;

	var i1 = document.getElementById("pitch");
	var v1 = i1.value;

	var i2 = document.getElementById("overhang");
	var v2 = i2.value;

	var i3 = document.getElementById("birdcut");
	var v3 = i3.value;

	var i4 = document.getElementById("rd");
	var v4 = i4.value;

	var i5 = document.getElementById("ply");
	var v5 = i5.value;

	var i6 = document.getElementById("rbd");
	var v6 = i6.value;

	var i7 = document.getElementById("rbw");
	var v7 = i7.value;

	var i8 = document.getElementById("hbd");
	var v8 = i8.value;

	var i9 = document.getElementById("hbw");
	var v9 = i9.value;

	var i10 = document.getElementById("vbd");
	var v10 = i10.value;

	var i11 = document.getElementById("vbw");
	var v11 = i11.value;

	var i12 = document.getElementById("rafter_spacing");
	var v12 = i12.value;

	var s13 = document.getElementById("framingoption");
	var v13 = s13.options[s13.selectedIndex].value;

	if (licensemode == 'lt')
	{
		if (v13 == 'YES')
		{
			v13 = 'NO';
			s13.value = 'NO';

		}
	}

	var s14 = document.getElementById("advroofoptions");
	var v14 = s14.options[s14.selectedIndex].value;

	var i15 = document.getElementById("assy_name");
	var v15 = i15.value;
	
	document.getElementById("submitstatus").innerHTML = 'Updating Assembly...';

	var paramstring1 = v0 + '|' + v1 + '|' + v2 + '|' + v3 + '|' + v4 + '|' + v5 + '|' + v6 + '|' + v7 + '|' + v8 + '|' + v9 + '|' + v10 + '|' + v11 + '|' + v12 + '|' + v13 + '|' + v14 + '|' + v15;
	var query1 = 'skp:GET_ROOF_EDIT@' + paramstring1;
	window.location.href = query1;


	if (v14 == 'YES')
	{
		// setTimeout(function(){
			callRubyADV('Update');
		// },50);
	}
	

	// setTimeout(function(){
		callRubyexec('Submit');
		finalizechange();
	// },750);
	
}



function callRubyADV(ActionName) 
{
	// Advanced Options

	var s1 = document.getElementById("roofsheath_option");
	var v1 = s1.options[s1.selectedIndex].value;
	
	var i2 = document.getElementById("roofsheath_thk");
	var v2 = i2.value;

	var s3 = document.getElementById("roofsheathmat");
	var v3 = s3.options[s3.selectedIndex].value;

	var s4 = document.getElementById("roofclad_option");
	var v4 = s4.options[s4.selectedIndex].value;

	var i5 = document.getElementById("roofclad_thk");
	var v5 = i5.value;

	var i6 = document.getElementById("roofcladext");
	var v6 = i6.value;
	
	var s7 = document.getElementById("roofcladmat");
	var v7 = s7.options[s7.selectedIndex].value;

	var s8 = document.getElementById("fascia_option");
	var v8 = s8.options[s8.selectedIndex].value;
	
	var s9 = document.getElementById("fascia_type");
	var v9 = s9.options[s9.selectedIndex].value;

	var i10 = document.getElementById("fascia_width");
	var v10 = i10.value;
	
	var i11 = document.getElementById("fascia_depth");
	var v11 = i11.value;

	var s12 = document.getElementById("soffitcut");
	var v12 = s12.options[s12.selectedIndex].value;

	var s13 = document.getElementById("ridgecap_option");
	var v13 = s13.options[s13.selectedIndex].value;

	var s14 = document.getElementById("gutter_option");
	var v14 = s14.options[s14.selectedIndex].value;
	

	var paramstring3 = v1 + '|' + v2 + '|' + v3 + '|' + v4 + '|' + v5 + '|' + v6 + '|' + v7 + '|' + v8 + '|' + v9 + '|' + v10 + '|' + v11 + '|' + v12 + '|' + v13 + '|' + v14;
	var query3 = 'skp:GET_ROOF_EDIT_ADV@' + paramstring3;
	window.location.href = query3;


	if (v8 == 'YES')
	{
		// setTimeout(function(){
			callRubySOF('Update');
		// },100);
	}


	if (v14 == 'YES')
	{
		// setTimeout(function(){
			callRubyGUT('Update');
		// },150);
	}
	
	if (v13 == 'YES')
	{
		// setTimeout(function(){
			callRubyHR('Update');
		// },200);
	}
}

As you can see I am sending my parameters back to Ruby in multiple calls since I don’t want to send it all via one large call which might become problematic since there are a lot of parameters.

I’m utilizing timers (setTimeout) to allow for the asynchronous behavior needed between Ruby and the HTML/Javascript. Without the timers (I’ve commented them out in this example) the data does not get all transmitted before the Ruby resumes and completes the operation however I am probably doing something wrong here, it wouldn’t surprise me.

I’ve never run into problems with a single callback being too long. i think the limit is on a few thousand characters when sent as an URI. If you use HTMLDialogs I’m not sure there even is such a limitation anymore.

1 Like

Yeah, I probably shouldn’t worry about it but when your list of parameters can exceed 100+ I just wanted to make sure it isn’t a problem.

I’ve removed my timer system and have a more robust method of sending multiple calls now.

Nat, you mentioned 2017 being your least common denominator … if so you should be using the sketchup JS object to send data back as a JSO --> Ruby Hash.

Using the old UI::WebDialog's skp: protocol is undocumented for the newer UI::HtmlDialog class.
It still works because SketchUp implements it to serve the deprecated UI::WebDialog class. When that class is removed it’s likely that the custom skp: protocol also goes bye-bye.

So I’d suggest getting ahead of the game, and switch to using the sketchup JS object with the UI::HtmlDialog class on versions of SketchUp 2017+.

On the JS side, I’d also suggest using JSON.stringify(opts) rather than the clunky var1 + '|' + var2etc. then convert directly to a Hash on the Ruby side. IE …
opts = JSON.parse(json_params)
Where on the JS side of things, opts is a JS Object whose properties are linked to your dialog’s control elements.

3 Likes

Can you provide me with a very simple sample of the JS and the ruby side with the new JS object. I agree my methods are often clunky and have gotten me into trouble more than once now. I’m learning though…

I have many times posted (in this category) about how easy to is to convert to&from JSON and Hashes, Structs and OpenStructs in Ruby.

On the JavaScript side, a JSON string is a definition of a JavaScript Object ready to be evaluated directly into a JS object.
Basically a JS Object definition looks just like a Ruby Hash definition. But it has the added benefit of automatic dot notation access (via keys) to any of the members like a Ruby Struct or OpenStruct has.

So the basic premise I want to get across to you (again) is on the Ruby-side instead of using individual named @vars, to use a Hash, Struct or OpenStruct, whose keys are the option names.
Then to send this data set over to the Html/JS-side, you simply use the data set object’s #to_json method, that is added by Ruby’s JSON library, to create a JSON string that’ll be sent over via the dialog object’s #execute_script method to a JS loader function.

dialog.execute_script("load_options('#{@options.to_json}');")

Now on the JS/HTML side you’ll have a JS Object whose keys will have the same names as they do on the Ruby-side. In addition, the actual HTML form control elements will have corresponding IDs assigned.

This will allow a JS iterator loop to be used to “lookup” each element and attach the change listeners, as well as set each control element’s initial value from that sent over from Ruby. (Right now your writing oodles and oodles of code with a statement to get or set each and every HTML control element value. Not needed if the IDs and data object property names are the same.)
You use the data object’s property names (keys) to lookup the corresponding element’s in the page’s DOM.

  • It would better if I write up an example an post in another thread or maybe a Git repo.

There are a number of examples on StackOverflow for the JS Object <-> HTML element binding part and attach change event listeners.

For sending back to Ruby you can pass objects back via the sketchup.my_ruby_callback() function call. As noted in the SUAPI doc …

Basic types such as booleans, numbers, strings, arrays and hashes are automatically converted between Ruby and JavaScript.

That sentence should actually say “between JavaScript and Ruby” because that is way the communication is going. It mentions hashes, but JS has Objects, so if you pass your JS-side option data object back, the UI::HtmlDialog callback mechanism will convert it to a Ruby hash.

sketchup.options_update_callback(options_data)

Then you can merge that temp hash parameter with your persistent Ruby-side options hash within the callback block …

# options_update_callback's block:
@options.merge(hash_param)
# maybe do something with the new set of options ?

For a more complex example you might consider using Aerilius’ bridge …

1 Like

I don’t understand why the timeouts are needed?

1 Like

It has to do with the asynchronous nature of the callbacks when you are running multiple callbacks within a single menu. I’m sending and retrieving the data in chunks. However, I do have better way of handling it now which eliminates the timers.

1 Like

With HTMLDialog you can add a callback in JS that will execute when you JS2Ruby callback have completed.

sketchup.say('Hello World', 42, {
  onCompleted: function() {
    console.log('Ruby side done.');
  }
});

Have you looked at our examples on GitHub? This one for example breaks down the basics:

2 Likes