Another way for Htmldialog get_element_value. Need Help

When get_element_value do not support on HTHML dialog. So i deicide make a method called get_value to replace it. This is my code at first i write this method.

     # param 1: dialog was created before, html id
def get_value(dlg,id)
  # temporaty var
  @get_value_tmp_data ='';
  # add new action callback 
  dlg.add_action_callback("getvalue"){|dialogs, var|
    # assgin data
    @get_value_tmp_data = var
    puts 'go new value:' +@get_value_tmp_data
  }
  dlg.execute_script("get_element_value('"+id+"')")
   puts 'last checking before return:' @get_value_tmp_data
  return @get_value_tmp_data
end

The javascript is:
function get_element_value(e) {
var value = $(e).val();
window.location=‘skp:getvalue@’+value;
}
When i run the medthod everything seem be ok but the last checking before return is not. That return the old value = ‘’
Thanks for reading and helping me.

1 Like

in your HtmlDialog javascript you should be using…

sketchup.getvalue(value)

does that not work?

john

1 Like

As John suggest, passing the values directly to the sketchup. callback is the way to go with HtmlDialog. Because HtmlDialog is all async one need a different way to pass data - this means using callbacks. Another aspect that is different is that where one often used input element to pass data with the old WebDialog because the skp: callbacks had encoding and size issues the new sketchup. callbacks are much better in that they don’t have any size limits and they also automatically convert simple types, so you can send arrays or hashes of data from JS to Ruby with ease.

1 Like

@nhanco102 I’m trying to find an efficient solution to emulate the deprecated get_element_value method in UI::HtmlDialog, and I ended up here. In my case, it is not working either with

Did you find a solution?

Can you elaborate on what isn’t working with sketchup.getvalue(value)? (This assumes you defined a callback “getvalue” on the Ruby side.)

:warning: Changed the name of get_element_value() to request_element_value()
because there is no way to receive synchronous values from JS in the UI::HtmlDialog class.

This is example code. No copyright intended nor warranty implied.

# Ruby
module MyTopLevelNamespaceModule::MyExtensionModule

  class ChromeDialog < ::UI::HtmlDialog

    def initialize(*args)
      super
      @values = {}
      set_on_closed() # no block argument uses default block !
      self
    end

    def attach_callbacks
      add_action_callback('receiveValue') do |not_used,id,val|
        receive_value(id,val)
      end
      # ... Add more callbacks here as needed ...
    end

    def request_element_value(id)
      return unless id.is_a?(String) || id.respond_to?(:to_s)
      execute_script("sendValue('#{id}');")
    end

    def receive_value(id,val)
      @values[id]= val
      process_value(id)
    end

    def process_value(id)
      # Do something with @values[id]
    end

    def process_values
      @values.keys.each { |id| process_value(id) }
      @values
    end

    def set_file(*args)
      super
      self # allows chaining
    end

    def set_html(*args)
      super
      self # allows chaining
    end

    def close(*args)
      super # call close() in UI::HtmlDialog superclass
        # If a set_can_close() block is defined and returns true then ...
          # Any set_on_closed() block would run here ...
          # The CEF window would close here ...
      unless @internal_reference.nil?
        # Wait a bit for the window to close
        sleep(0.5)
        # Release the internal reference if the window was closed:
        @internal_reference = nil unless self.visible?
        # If the instance is not externally referenced then Ruby GC
        # can destroy the unreferenced object next time it runs.
      end
      self # allows chaining
    end

    def set_on_closed(&block)
      if block_given?
        super Proc::new {
          block.call
          @internal_reference = nil
        }
      else # default block to release internal reference:
        super { @internal_reference = nil }
      end
    end

    def show(*args)
      if self.visible?
        self.bring_to_front
      else
        attach_callbacks()
        @internal_reference = self # backup reference whilst dialog is open
        super
      end
      self # allows chaining
    end

    def show_modal(*args)
      return self if self.visible?
      attach_callbacks()
      @internal_reference = self # backup reference whilst dialog is open
      super # this is a modal call !
      self # allows chaining
    end

  end # dialog subclass
end # extension module
// JavaScript:
function sendValue(id) {
  sketchup.receiveValue( id, document.getElementById(id).value );
}

EDIT: Added process_values iterator method.

2 Likes

@DanRathbun That definitely works. Thanks.

1 Like

I updated the example, as it really should not have worked as it was.

I had not actually attached an action callback to the dialog object.
This is now done in the attach_callbacks() method, inside the show() and show_modal() method overrides.
This needs to be done this way because action callbacks are detached if the dialog is closed.

1 Like

Didn’t notice that, it worked for my scenario if I remember well, maybe with a little tuning. Thanks for the update, anyways.

Has anyone solved the problem of implementing a synchronous get_element_value method for HtmlDialog?

Does Trimble have plans to provide one?

Joe…

I believe that the interaction with the CEF is asynchronous by design.
I’ve not yet found a way to get a desired value directly from a single method call.
But I did not yet try a wait loop.

    def get_element_value(id)
      return unless id.is_a?(String)
      execute_script("sendValue('#{id}');")
      while @value.nil?
        sleep 0.3
      end
      value, @value = @value, nil
      return value
    end

But this opens up the possibility of getting lost in an infinite loop, if a Javascript error occurs in the dialog, and the Ruby callback is never called. (I usually have a bailout after a certain number of seconds, or loop number.)

I don’t know, … I wouldn’t bet on it., … The SketchUp Team / Trimble employees never make public statements about specific future plans. (There are entire threads discussing this, and numerous employee posts stating this policy.)

Hi Dan,

I tried the wait loop. It didn’t work. Apparently the sketchup.receiveValue can’t trigger the callback until the loop is exited, which is too late.

The HtmlDialog is pretty useless without this capability.

Joe…

FYI : The “snippet” was not actually a drop in test for the greater asynchronous example above.
It was just an idea. (Ie, the full example has no @value variable which would need to be assigned in the add_action_callback block [instead of calling the receive_value method.])

I think I know what you mean, though. Ruby is busy in the “wait loop” and never ready to process the action callback.

Exactly. I was trying to think of ways to use threads to get around the problem, but haven’t come up with a good idea yet. I’ll let you know if I do.

i.e threads with Mutex.synchronize.

Threads don’t work well in embedded Ruby.

His code is based upon my example (in this thread.)

It would be a messy example if it used one method.

I chose for this example, to use a “trigger” method, 1 callback that receives the value, and a separate method that can process any of the values in the @values hash.

Asside: I know, you could actually have the value parameter optional, and then have a conditional inside the method, and then 1 arg makes it a “trigger” and 2 args and it is being called from the dialog’s JS.
But this is not as simple an example as I show above.

In my example I choose to have the “receive_value()” method first quickly assign the value to a hash (whose keys are id strings,) and then call a separate “process_value()” method, because it may likely be needed to have other methods initiate the processing of values at other times than when a dialog receives a value. (Such as triggered by a API observer, etc.)

It also occured to me that sometime, it may be advantageous to iterate the @values hash and process all values. This again makes sense to have the process routine in a separate method than the receive callback from JS.

EDIT: I’ve added a process iterator method to the example to illustrate this point …

    def process_values
      @values.keys.each { |id| process_value(id) }
    end

However, if you have a better idea / example, PLEASE post it rather than tearing mine down.

I edited the above example to the latest version.

  • Many method overrides return the dialog instance to allow call chaining.
  • It keeps an internal reference to itself when the CEF window is open so
    the window doesn’t disappear unexpectedly.
    • This reference is set to nil when the window closes to allow garbage collection
      if there is no external reference to the object (as is usual behavior.)

The HtmlDialog is pretty useless without this capability.

Freezing (with wait) an asynchronous operation to make it synchronous will cause a deadlock in a single threaded application.

It’s better to accept thinking in the asynchronous paradigm.
(The Ruby interpreter is not the actor that fetches a value from the dialog and continues processing it. The dialog is the actor that triggers sending a value to the “server”, and the “server”/Ruby interpreter only starts processing when the callback is triggered.)

1 Like

Well phrased. As an example, if one had a dialog for exporting, there may be several ‘option controls’. If the user clicks an ‘export ’ or ‘okay’’ button/control, the callback would include the state of the ‘option controls’.

Greg