WebDialog.execute_script on OSX
-
Howdy all;
For the last couple of months I've been working on building a UI for a plug-in using the APIs WebDialog object. During this time I've encountered a fair amount of difficulty in exchanging data between WebDialog API objects and their HTML/CSS/Javascript. The most significant problem I've had is getting the WebDialog's execute_script method to work as I expect on OSX. I wanted to use this post to document some of the problems I've discovered, and workarounds I've found.
My difficulty centres on using the execute_script method to call user-defined JavaScript functions during WebDialog creation.
For example:
HTML
<html> <script type="text/javascript"> function test_alert() { alert("Hello world!"); } </script> <body> <h3>TEST</h3> </body> </html>
Ruby
def test_wd $my_dialog = UI;;WebDialog.new("Selection Info", false, "Selection Info", 200, 200, 200, 200, true) html_path = Sketchup.find_support_file "rubyjstest.html" ,"Plugins/testing" $my_dialog.set_file(html_path) $my_dialog.show_modal() $my_dialog.execute_script("alert('Hello World!');") $my_dialog.execute_script("test_alert();") end
When I execute the test_wd method, the alert that's called directly within the execute_script method fires, but the one wrapped in the test_alert function in the HTML file does not. Changing the order of the execute_script calls doesn't change this.
However, if the test_alert method is called later -- whether by attaching it to a UI event (eg a button), or from the Ruby console, it works as expected. This suggests the issue in some way involves timing -- whether because of threading issues, or asynchronism, or something else.
To investigate the issue of timing, I did some more tests. First, I modified the Ruby code to look like so:
def test_wd $my_dialog = UI;;WebDialog.new("Selection Info", false, "Selection Info", 200, 200, 200, 200, true) html_path = Sketchup.find_support_file "rubyjstest.html" ,"Plugins/testing" $my_dialog.set_file(html_path) $my_dialog.show_modal() $my_dialog.execute_script("alert('Hello World!');") sleep 5 ## ADDED THIS LINE, USING RUBY'S BUILT IN SLEEP COMMAND $my_dialog.execute_script("test_alert();") end
I quickly found that this crashes Sketchup every time. To test if this was just a matter of the "sleep" command and the API not getting along period, I ran the following code:
def test_sleep puts "start" sleep 5 puts "end" end
This runs as expected. Armed with this new knowledge, I further modified the test_wd method to look like this:
def test_wd $my_dialog = UI;;WebDialog.new("Selection Info", false, "Selection Info", 200, 200, 200, 200, true) html_path = Sketchup.find_support_file "rubyjstest.html" ,"Plugins/testing" $my_dialog.set_file(html_path) $my_dialog.show_modal() $my_dialog.execute_script("alert('Hello World!');") for i in 1..9999999 ## 2 + 4 / 3 ## UGLY JUNK ADDED TO USE UP TIME end ## $my_dialog.execute_script("test_alert();") end
Running this results in the first alert fires as expected. At about the same time, an empty WebDialog window is created, but the HTML within it is not rendered -- this doesn't happen until after the for loop finishes executing. And, most importantly, the second alert still doesn't fire.
Knowing that the HTML doesn't render until, it seems, everything within the test_wd method has run, I got the idea to use jQuery's ready() method. So, I modified the HTML to look like so:
<html> <script type="text/javascript" src="./jquery-1.4.4.min.js"></script> <script type="text/javascript"> function test_alert() { alert("This is a test alert"); } $(document).ready(function() { test_alert(); }) </script> <body> <h3>TEST</h3> </body> </html>
I also got rid of the $my_dialog.execute_script("test_alert();") line from the test_wd method -- essentially, I moved it, from the Ruby code into the $(document).ready() callback function. The result: SUCCESS!
After completing these tests, I set about restructuring my code to take advantage of the knowledge I had gained, and ended sticking a few consecutive calls to functions I'd created for my menu's initialization into the $(document).ready() callback. It was here that I ran into yet another stumbling block that has been noted in other posts: the asynchronism of Ruby callbacks. The final thing I'll add to this thread is a simple workaround I developed that seems to help here: the JavaScript setTimeout function. This function allows you to execute a JavaScript command, provided as a string argument, after a given delay, provided, in milliseconds, as a numerical argument. For example:
setTimeout("my_function()",1000)
calls my_function() after a 1 second delay. I found that setting the delay to even 1 ms seems to avoid any trouble with consecutive Ruby callbacks.
So, that's it. Hopefully this is in some way helpful to people like me who are hacking around with the eccentric, somewhat buggy, and sparsely-documented world of WebDialogs.
-
I use the jQuery .ready() event that loads when the DOM is ready to make a callback back to Ruby. Then I can 100% sure I can use execute_script. Works on both platforms. No need for sleep or timers.
I've made a few notes about WebDialogs: http://forums.sketchucation.com/viewtopic.php?f=180&t=23445
Advertisement