Populating UI::HtmlDialog with cached values

My fairly complex Ruby Tool features a UI::HtmlDialog that lets the user input a bunch of parameters (via plain HTML drop-downs and input fields) in order to generate some geometry.

The problem is that I have too many of these inputs and they are currently lost when the user closes the dialog (which also deactivates the Ruby Tool). It would therefore be ideal to save the values of all drop-downs and input fields so that when the user relaunches the Tool, the dialog gets populated from a cache as opposed to being initialized with default values. We may assume that the values donā€™t have to persist between separate runs of SketchUp (that is, itā€™s fine to rely on the defaults immediately after the app was opened).

In my current implementation, almost all JavaScript code lives inside Ruby via calls to dialog.execute_script(...). Although not ideal, this prevents people from looking at the front-end source code and also makes certain things easier (e.g. passing different Ruby Strings depending on whatever logic exists in the UI).

My idea would be to store the latest inputs to a file and then populate the dialog from that cache whenever the Ruby Tool is activated. Is that a good enough solution or are there any SU-specific tricks that I can leverage here? The only issue I see with this approach is that the file would persist in the Plugins directory and therefore the dialog would be initialized from the cache even when re-opening the application (not a big deal, but something I would like to avoid).

Iā€™d have to check my code, but you should be able to pass the values back to Ruby when the DOM unload event runs?

1 Like

Wow, brilliant! I didnā€™t know about the unload event, but this is going to make things very convenient!

Just tested this out by putting

window.addEventListener("unload", function(e) {
    sketchup.saveState()
})

in JavaScript and

dialog.add_action_callback('saveState') { |action_context|
  puts "Here we save the state, woo-hoo!"
}

in the Ruby Tool, and I can see the message printed to the console right after closing the dialog.

Thanks a lot!

@costica1234

Glad I could help. Iā€™ve found it works fine in SU (SketchUp), but there are some caveats when using it in ā€˜normalā€™ web pages.

See MDNā€™s pages at Window: unload event - Web APIs | MDN and Page Lifecycle API - Chrome Developers

1 Like

Right, I saw mentions about unload not being triggered on mobile web pages, but this shouldnā€™t matter here. That being said, I hope that unload will work in SU2017 on Windows just as it does in SU2022 on macOS.

I think Iā€™ve been using unload since maybe SU 2008, certainly SU 2013, both of which used WebDialog. Iā€™ve also tested it with several versions 2017 or later (HtmlDialog).

Iā€™ve been using Windows/DOS for decades, and recently WSL2/Ubuntu. I very recently got a mac desktop for testingā€¦

1 Like

If you store the values of the input elements in a JS object, you can pass that object back to Ruby and it will be converted to a Ruby hash. Just make that hash persistent for the session.

1 Like

This is what I ended up doing as keeping track of so many JS variables was getting a bit unwieldy. In fact, I wanted more than just storing the final state, so I added a bit of extra logic to update the JS cache depending on changes made to various UI elements (dropdown menus and checkboxes).

As a result, I also needed to pass the Ruby hash back to JS (e.g. when invoking the Tool a second time) and I did so by leveraging the to_json method:

@ruby_cache = Hash.new

dialog.add_action_callback('init') { |action_context|
  # Initialize the default screen.
  dialog.execute_script(%(
    js_cache = {
        "#{FIRST_KEY}": 111,
        "#{SECOND_KEY}": 222,
        "#{THIRD_KEY}": 333
    }
  ))

  # Populate all subsequent dialogs from the cache.
  dialog.execute_script(%(
    js_cache = #{@ruby_cache.to_json}
  )) unless @ruby_cache.empty?

  ...
}

Iā€™m a big fan of how this mechanism is able to hide the JS code from anyone trying to reverse-engineer a dialog-based SketchUp Tool.

What makes you think that we cannot see the JavaScript or JS objects with the Chrome DevTools ?

Regardless, I would not worry about it as no one really cares what your JS code objects are unless they are trying to help you run down a bug.

On the Ruby side, as a coding language, it has very robust reflection methods so we can see your variables, and constants and see what their values are. But again, nobody really wastes time poking into someone elseā€™s Ruby tool. We all have better things to do.

If I type sketchup.init ā€“ a call that lives under the <script> section of my HTML file ā€“ in the DevTools console I only get the following output:

ʒ init() { [native code] }

Is there a way to access the ā€œnative codeā€ part? Just curious.

No this is obvious to us SketchUp developers, that you are calling a Ruby callback named ā€œinitā€ attached to your Ruby dialog object.

What I was speaking of is YOUR JavaScript objects not the sketchup JS object which is injected by the API when it creates the CEF window.

Oh, right, I got it now.

From what I can tell, only global variables and functions can be accessed via the console (and some very long functions appear truncated, but perhaps there is a way to show their entire source code). However, the logic found in event listeners (e.g. onchange) appears to be hidden away from more curious eyes. :upside_down_face:

General reflection:


See this primer specifically on Ruby reflection:


Do not be surprised if the logic in methods (including observer callback methods) can be seen by those who know how. The old RBS scrambling was quickly hacked and is no longer recommended (perhaps not even supported.) I wouldnā€™t be surprised if the newer RBE encryption has already been broken.

1 Like