Example html dialog (suite)

ui::htmldialog

#1

A few months ago Camlaman wrote an example to help write extensions using html dialog. ([Example]An example to help you how to write SketchUp Ruby code with HtmlDialog) Topic now closed.
Well it did help me mainly because it was reduce to most simple expression.
Of course this is not the way a rb file should be written (it was not intended to be such).
So as an exercice, I wrote it as an extension. And to further help, following his intention, I post the result.

module Htmlexample
  def self.box_dialog
	html_file =  File.join(__dir__, 'htmlF', 'box.html')
	  options = {
	  :dialog_title => 'Box',
	  :style => UI::HtmlDialog::STYLE_DIALOG
	  }
	dialog = UI::HtmlDialog.new(options)
	dialog.set_file(html_file)
	dialog.center
	dialog
  end
  
  def self.show_dialog
	dialog ||= box_dialog
	if dialog.visible?
	  dialog.bring_to_front
	else
	dialog.show
	end #if
	
	dialog.add_action_callback("getUserInput"){|action_context, user_input1, user_input2, user_input3|
		puts("JavaScript said user_input1 is #{user_input1}, user_input2 is #{user_input2} and user_input3 is #{user_input3}.")
		@width = user_input1.to_f
		@breadth = user_input2.to_f
		@depth = user_input3.to_f
		makebox
		dialog.close
	}
	end
	
	def self.makebox
		model = Sketchup.active_model
		entities = model.active_entities
		pts = []
		pts[0] = [0, 0, 0]
		pts[1] = [@width, 0, 0]
		pts[2] = [@width, @breadth, 0]
		pts[3] = [0, @breadth, 0]
		# Add the face to the entities in the model
		face = entities.add_face(pts)
		face.pushpull -@depth
	end

show_dialog
end #module

The referred html file, box.html, must be in a folder named htmlF, on same level as the rb file.
box.html.zip (1.1 KB)
If you put these in the plugins folder, the script will run at SU launch.

Now my next quest is html localisation.


#2

What exactly is the question?


Stylewise, some comments:

  • In Ruby, people prefer to write method names like showDialog in lowercase snake_case: show_dialog
  • HtmlDialogs/WebDialogs silently absorb exceptions because they are triggered outside of the main Ruby evaluation thread. You will not know about when the callback raises an exception (e.g. if makebox raises an exception). If you want to debug an issue why something does not work as expected, you can wrap the content of the callback in a begin rescue clause and print the exception to stderr or stdout:
dialog.add_action_callback("callbackName"){ |*args|
  begin
    # .. callback
  rescue Exception => error
    puts(error)
    puts(error.backtrace.join($/)
  end
}
  • Variables that start with @ are a special type of variables and should only be used for that purpose. These instance variables (or here module variables) stay persistently within an instance of a class or within the module, even when the method terminates. This can cause a lot of problems:

    • When you call a method again, the instance variables may already hold a value from previous invocations, leading to unpredictable behavior (hard to debug) depending on the sequence of operations before this method.
    • It is unobvious for a reader from where the value was assigned and how data flows through your application.

    For beginners it is better to keep the amount of state in classes and global state at a minimum.

    In your example, @entities should be entities. The parameters (@width, @breadth, @depth) can be passed into the method as method parameters to make the data flow more explicit:
    makebox(width, breadth, depth)
    However if using the dialog is optional, and all other times when the method is called it should default to the last used parameters, you could leave them as module variables.


#3

Thanks a lot Aerillius. Corrected.
Yes the 3 @ parameters will be ‘last used’ by default.
Wrt localization, my extensions have all used languagehandler to date (no html in them). You saw that I am a beginner with html dialog. One of my worries is what is a good way to deal with localization of rb using html.


#4

Continuing @Aerilius discussion,

I have noticed that misuse/abuse of @ and @@ variables is one of the most common flaws in code written by newbies (and sometimes by “experienced” Ruby programmers). I speculate that this misuse is for lack of adequate training in Object Oriented Programming, hence lack of understanding of what these variables are for.

These kinds of variables are meant to capture the “state” of an object. That is, the minimum amount of data required for an object to remember what has been done to it over its lifetime so as to be able to respond correctly to subsequent method calls by other objects.

I put those last words in italics, because they are key to understanding the difference between state variables and regular variables or parameters. State variables should be reserved for things that another object can’t be assumed to know but which will affect the behavior of the object receiving a method call. As @Aerilius pointed out, when non-essential data is improperly preserved in state variables, it can lead to unpredictable behavior later.

Aside from simple overuse due to lack of understanding, the most common misuse is as a means to reduce the parameter list of a method by passing some of the required data “around the side” when an object invokes one of its own methods. The fact that the object is talking to itself is not a valid justification for putting transient data into state variables instead of a method’s parameter list. They are remembered long after that transient usage! And one never knows how that method may be used later, in a context where even the current object doesn’t know when or why the value of each state variable was set.


#5

I usually prefer to insert defaults at the place of invocation of the method, not within the method (so that the method remains more generalized, predictable without side-effects, testable):
e.g. in the menu handler:

UI.menu('extensions').add_item('Make box'){
  makebox(@width, @breadth, @depth)
}

Regarding localization of HtmlDialogs/WebDialogs, LanguageHandler lacks tools to localize dialogs. This has been heavily criticized when LanguageHandler was overhastily promoted into the API without the needed rework being done.

  • You can translate static dialogs by providing html resource files for every language that you want to support and store them in your_plugin/Resources/<language code>. When you set the html to the dialog, choose the html file depending on Sketchup.get_locale
  • You can translate the dialog content dynamically with a JavaScript analog of LanguageHandler, only that you need to push the translation data to the dialog first (which you can get with LanguageHandler#strings).
    • To translate dynamic JavaScript apps, you would query every string from your JavaScript version of LanguageHandler with a method like get(string).
    • You could also translate all static html at once by traversing all text nodes and querying their translation. As an example, my translate.rb has a method Translate#webdialog(dialog) which does that.

#6

Very Interesting. I have a quite a bit to explore.
Thanks again.
Ha, and also for your Ruby Console+ extension which I find quite useful.


#7

ah… but here the defaults have to come from the html file. They are in the input form. Still have to figure that out. Maybe with an external file.