I’m finalizing work on a new version of my SketchUp IFC Manager extension.
Internationalising it a bit, but most of the work went into a new HtmlDialog.
I tried making the connection between SketchUp and JavaScript as simple as possible.
I don’t use a html template, but generate html with Ruby, because I felt it took too long to wait on the dom to be ready and fill it up afterwards using JavaScript. Re-generating the html on every selection change seems to have a better performance, and I also figured it would be more reliable than keeping the Ruby and JavaScript in sync.
Most of the time it works perfectly, but sometimes the dialog stops sending messages back to Ruby and I get this JavaScript error:
Uncaught TypeError: sketchup.Name is not a function
Somehow the “sketchup” handle in JavaScript is lost.
Screenshot:
Code:
I hope anyone can give me some tips on reliably using HtmlDialog for such a simple window.
BTW … “BimTools” is not really a properly unique toplevel module name.
(Ie, it is very likely that other author(s) or companies could pick the same name.)
Thanks for looking at it.
You are right that “sketchup” is an object, handle is probably a wrong term here…
I’ve been using “BimTools” for quite a while, my “company” is called BIM-Tools, I even have “bim-tools.com” as a domain name
What would you suggest as a proper toplevel module?
Re the object access and “not a function” error:
The UI::HtmlDialog class can loose it’s callback attachments (especially if you close the window and reopen it with the same Ruby object.)
So what I do (usually) is put all the callback definitions within a method I (usually) name “add_callbacks()”. This way you can call the method and reattach the callbacks anytime.
ADD: I also pass the dialog object into this method.
It’s likely that reloading of the html document may break the attachments of the Ruby callbacks. You might add a JavaScript error handler function to detect the “not a function” error, and send a request over to Ruby (hopefully by a still functioning callback) to reattach the callbacks.
Other than that perhaps a Ruby timer that sends “pings” to the dialog every so often and if the dialog does not respond back in a certain amount of time, then the Rubyside goes ahead and reattaches the callbacks.
The only I’ve seen that error myself is when I tried to call a callback from JS that did not have any parameters, and I omitted the parenthesis, ie …
Thank you!
I was also thinking along these lines, good to have some confirmation. Seems there are still some issues around the Web/HtmlDialogs.
I also thought about “pinging” it to see if it’s still alive, but wondered if that wasn’t more of a workaround instead of a neat solution.
My idea was to re-attach them on every selection-change(re-draw), and see if that has any performance implications.
Or would you say the pinging solution is preferable?
For my 2011 bim-tools plugin https://sketchucation.com/pluginstore?pln=bim-tools (more privately developed, but the reason I picked that name) I used “Brewsky::BimTools” but I didn’t want to use “Brewsky” here, but maybe that’s still best…
I keep a variable reference to my dialog in ruby.
I add a block to execute when the dialog is closed.
Then I have a method to show the dialog. If it’s not initialized it does that and adds the callbacks.
Using this method I have had zero issues with JavaScript to Ruby communication and vise versa.
#inside your module namespace
def self.init_spec_dlg
unless @spec_dlg.nil?
@spec_dlg.set_url 'about:blank'
@spec_dlg.close
end
@spec_dlg = UI::HtmlDialog.new(
#initilization stuff
)
@spec_dlg.set_on_closed do
@spec_dlg.close
@spec_dlg = nil
end
#add call backs here...
@spec_dlg.set_file(@dlg_path_spec)
end
def self.show_specs_dialog
init_spec_dlg unless @spec_dlg
return if @spec_dlg.visible?
@spec_dlg.show
end
Yes I don’t myself have many issues either. For me the “lost callbacks” only occurs if the dialog window is closed and callbacks need to get triggered. Ie, a timer or something doesn’t know the window was closed.
With the old UI::WebDialog we were used to just having the dialog remain closed for awhile, and then have it get opened again and the callbacks would work again. But the newer UI::HtmlDialog class will not do this. (The callbacks must be reattached if the same dialog object is to be reopened., so it is smart to put the callback definitions in a method of their own.)
All that said, … I’ve myself come to the conclusion that building dialog HTML is tedious and slow.
So I’ve switched to using template and template fragments with %{hashkey} replacement parameters. I’ve found it is also quite easy to replace the innerHTML using these template fragments when a list of data needs to be changed. It is much faster and looks better from the end users point of view, than rebuilding and rerendering the entire dialog page.
I also need to say, that I’ve seen (recently) this occur with other devs extensions, when the callback name begins with a capital character. (I myself have always named callbacks using ruby method name conventions. [lower case with underscores as spacing.])
Looks good!
I used JavaScript to load all the content at first but my experience with waiting for the DOM to be ready before pushing the content wasn’t that good.
How long does it take to appear when opening the dialog for the first time?
Looks like about 425ms from the time the window shows until the html is first rendered. It’s not really a big deal in my case because normally the window stays open all the time.
You also might need to take a look at some javascript state handling mechanisms.
I have used knockout.js in the past when my UI was built in vanilla javascript.
I nowadays use react.js with redux and send state updates from sketchup to javascript.
Anyhow, my workflow has always been: attach a callback to signal the page has been loaded and then execute a global state update.
Sorry for the late post. I had similar select elements in a few html dialogs, and sending an array from SU to JS seemed to work very quickly in terms of loading the array into a select…
I simply show/hide divs as necessary to show the desired content.
That’s a good idea for when you know all options beforehand, but a little more difficult for mine because the content is dependant on changes in the model.