Need to process numbers in js in a HtmlDialog but js doesn't cope with SketchUp format length strings

I need to process user entered numbers in a HtmlDialog but if the numbers are in SketchUp format length strings then javascript can’t deal with them. I’m tempted not to use SketchUp format lengths but stick to javascript decimals for user input but I thought I’d ask in case anyone had a solution. The Extension Warehouse reviewers are critical of extensions which don’t use SketchUp format lengths.

It would be great if it were possible to somehow pass the user entered lengths back to SketchUP Ruby, turn them there into decimal inches and then pass that back to the dialog for processing but I don’t know how to do that, or even if it is possible.

You can register an action callback for sending a string from JavaScript to Ruby, parse it as a length and make some changes to it, and execute a JavaScript to send the result back to the dialog.

What is the use case for modifying the lengths on the JS side?

I’ve written a visual design tool for window design which lets you very quickly create different window layouts in the dialog and then when you are satisfied it gets turned into a 3d SketchUp component.

That sounds interesting. How do you do that?

Some years ago for another purpose I wrote two JavaScript functions to convert between foot-inch-fraction lengths and decimal inches (in both directions).

Here they are, zipped.

foot-inch-fractions.zip (4.9 KB)

With a little bit of effort, you could convert the code to Ruby, I think. The logic would be the same or very similar, but the syntax and code would be different.

Or since SU Ruby understands SU lengths it might be very much simpler!

parseHtml

# encoding: UTF-8
#dezmo_test/main.rb
module DezmoTest
  extend self
  
  def to_locale_string( string )
    string.tr(',', '.').tr('.', Sketchup::RegionalSettings::decimal_separator)
  end
  
  def create_dialog
    options = {
        :dialog_title => "DezmoTest",
        :width => 400,
        :height => 200,
        :left => 100,
        :top => 100
      }
    dialog = UI::HtmlDialog.new(options)
    dialog.set_file(File.dirname( __FILE__ ) + "/test.html")
    dialog
  end

  def add_callbacks
    @dialog.add_action_callback('parseHtmlText') {|_context, data|
      text = to_locale_string( JSON.parse(data) )
      begin
        value = text.to_l
        raise ArgumentError if value == 0.to_l
      rescue ArgumentError
        UI.beep
        value  = "Please enter valid length > 0"
        result = Sketchup.set_status_text("Can not parse length")
      end
      puts "Parsed to: #{value}"
      js1 = %{
        document.getElementById('id1').value = #{value.to_json};
        processMore();
      }
      @dialog.execute_script(js1)
    }
  end
  
  def show_dialog
    @dialog = create_dialog()
    add_callbacks()
    @dialog.show
  end
end
DezmoTest.show_dialog

<!DOCTYPE html>
<!-- #dezmo_test/test.html -->
<html>
  <body>
    <div>Test: Please enter valid length > 0 .</div>
    <div id="example">
       Length: <input id='id1' type='text' value='10' onchange="myvalidator(this);"
    </div>
    <script>
      function myvalidator(e){
        sketchup.parseHtmlText(JSON.stringify(e.value));
      }
      function processMore(){
        console.log("Here you can do more... ");
      }
    </script>
  </body>
</html>

dezmo_test.zip (1.2 KB)

2 Likes

Thank you. That is very kind of you. I will look at the code with interest. I had tried some code I found here GitHub - dobriai/footinch: Length string parser and formatter - imperial and metric but it didn’t do everything necessary to cope with SketchUp length input. It converted feet and inch to decimal feet but treated 2 1/2 (without the ") as two and a half feet. I’d have had to do a lot of extra work on it to make it work for me.

That looks like what I need. Thank you for the example. That is very helpful and I’ll try that.

1 Like

You can register a callback and then execute a javascript from within that callback, similar to how all communication between Ruby and HTML is done.

dialog = UI::HtmlDialog.new
dialog.set_html("Test")

dialog.add_action_callback("text_to_float") do |_, text|
  float = text.to_l.to_f
  # Can replace alert by any method of your choice.
  dialog.execute_script("alert(#{float})")
rescue
  dialog.execute_script("alert('Invalid length')")
end

dialog.add_action_callback("float_to_length") do |_, float|
  string = float.to_l.to_s
  # Using to_json to add quotation marks around the string and 
  # escape any special characters.
  dialog.execute_script("alert(#{string.to_json})")
end

dialog.show

Right click the dialog and open the developer tools. In the JavasScript console you can try running commands like sketchup.text_to_float("2m") or sketchup.float_to_string(100) to test the code.

Generally when making an extension you never need to re-invent length formatting, re-invent length parsing, swap out characters in the length strings, or check what units the SketchUp model uses. String#to_l and Length#to_s handles this. Trying to re-invent what SketchUp already offers is more work to implement, and makes the extension inconsistent to SketchUp.

This doesn’t just apply to lengths and SketchUp. When working with dates, you want to use existing date functions that handles leap years and other special cases. When you want to send data as a single String, you can use something like JSON to handle serialization and escaping special characters. Re-inventing things that already exists and are well tested creates additional work and risks adding more bugs.

I can only think of one example when you need to manually check what units the model uses, and that is to come up with a reasonable default value in the current unit system. In other cases I’ve seen, checking the model unit seems to be a detour to doing what String#to_l and Length#to_s already does for free.

def metric?
  unit_options = Sketchup.active_model.options["UnitsOptions"]
  return false unless unit_options["LengthFormat"] == Length::Decimal
  
  [Length::Millimeter, Length::Centimeter, Length::Meter].include?(unit_options["LengthUnit"])
end

default = metric? ? 100.mm : 4.inch
puts default

I am doing that so I will leave that in my code. The text size in SVG needs to be approximately corrected to the likely numbers used in the size of the object being drawn in SVG.

Thank you for the advice. Both you and Demzo have been very helpful.

I just hadn’t played around with call backs before and was unsure of my ground there. Your example and Demzo’s are very helpful.

Thank you again.

1 Like