Remove_observer Method

Perhaps this topic has already been covered but I have not been able find it.

I’m trying to implement a fairly simple observer that detects changes to the selection set.

I’m finding the documentation for observers a bit cryptic but this may just be my lack of understanding and familiarity with these methods. To be honest this is my first crack at trying to implement an observer in SU so its going to be a bit of a learning process.

I think I’ve figured out how to implement the observer, however I would like to remove the observer when I terminate my HTML UI (inside of my close dialog):

dlg00.add_action_callback("CLOSE_DIALOG") {|action_context, params|
dlg00.close
} 

The remove_observer documentation shows an observer object as the parameter however when I initially create the observer I am passing to the add_observer method the following variable:

MySelectionObserver.new

So what would be the correct variable or object to pass to the remove_observer method?

So I add the observer just before I open up my HTML Dialog:

# Attach the selection observer.
status_add_obs1 = Sketchup.active_model.selection.add_observer(MySelectionObserver.new)

Then I attempt to remove the observer here:

dlg00.add_action_callback("CLOSE_DIALOG") {|action_context, params|
   dlg00.close
   status_remove_obs1 = Sketchup.active_model.selection.remove_observer(MySelectionObserver)
}

But it does not remove the observer, repeated openings of the HTML dialog by the other methods then create new observers so pretty soon I have multiple observers all doing the same thing, obviously this is not good. I need to figure out a way to shut down the observer once I’m done with it.

My class definition for the observer is the following (at least for now):

# This observer watches the selection for changes
class MySelectionObserver < Sketchup::SelectionObserver

def onSelectionBulkChange(selection)
   puts "onSelectionBulkChange: #{selection}"
end

end

@MySelectionObserver = MySelectionObserver.new

Then add it [assuming the model.selection is referenced as @ss]

@ss.add_observer(@MySelectionObserver)

Then later on - probably in some other method…

@ss.remove_observer(@MySelectionObserver)

???

1 Like

Okay, that was too easy.

Now I have another more difficult problem. Since I can’t use global variables I typically use class variables in most situations where I need to pass information between methods.

However since the observer is its own class I cannot check to see if the HTML dialog is still active:

# This observer watches the selection for changes
class MySelectionObserver < Sketchup::SelectionObserver

def onSelectionBulkChange(selection)
	if @dlg001.visible?
		puts "onSelectionBulkChange: #{selection}"
	else
		MedeekMethods.remove_observer
	end
end

end

Where in the Medeek Methods class I have defined:

    class MedeekMethods
      	class << self
...
@dlg001.add_action_callback("CLOSE_DIALOG") {|action_context, params|
		@dlg001.close
		MedeekMethods.remove_observer
	}
...
    def remove_observer
    	status_remove_obs1 = Sketchup.active_model.selection.remove_observer(@Select_observer_object1)
    end

    end # << self
    end # MedeekMethods Class

The reason this extra logic is required in the observer class is because the user might close out the HTML dialog box by simply hitting the “x” button rather than clicking on the close button. If this happens the CLOSE_DIALOG callback never gets fired and the observer stays active.

Perhaps there is a better way to handle this?

You need to know some secrets.

When you call dialog#close() it does not immediately close.

It first processes any closure callback blocks whilst the dialog is still open and therefore dialog#visible? returns true.) IE, …

* Even though they used a past tense method name for the newer class, my tests show that dialog#visible? still returns true whilst the closure block is executing.

I previously had the reverse false assumption, leading to this issue thread …

Basically it was my code that was causing the 2nd call, and some crashes when dialog#close() was called from within a closure block when the dialog object was already within a close cycle. (So don’t do that.)


I solved the “X” button closure issue, by setting an instance variable @closed_by_callback = true within the “CLOSE_DIALOG” callback, and testing this same variable within the #set_on_close(d) blocks and if not true, then the user used the “X” button to close the dialog. Ie …

if @dlg001.respond_to?(:get_element_value) # it's an UI::WebDialog
  @dlg001.set_on_close {|action_context, params|
    if !@closed_by_callback # user clicked X button
      MedeekMethods.remove_observer
    end
  }
else # it's an UI::HtmlDialog
  @dlg001.set_on_closed {|action_context, params|
    if !@closed_by_callback # user clicked X button
      MedeekMethods.remove_observer
    end
  }
end
1 Like

Wow, works as advertised.

This allows me to remove the conditional from the observer class and not have to deal with the global variable issue.

It’s safe to say I would never have figured this one out on my own.

Thank-you, Dan, your SU knowledge is powerful magic.

Also a thank-you to TIG for helping me see the forest from the trees.

One last minute related question:

I’ve created the observer class within my estimating module, on the same level as my MedeekMethods class.

If I do want to have these two classes talk to each other (access specific variables within each class), what would be the preferred method for doing this?

Specifically I am wanting to have the observer detect a selection change and then execute a small amount of code and then fire off a javascript command using the HTML Dialog object similar to a callback. Maybe this isn’t even possible.

js_command = 'javascript_function_name(some_data_goes_here );'
@dlg001.execute_script(js_command)

Or rather than putting the javascript command within the observer class, just detect the change to the selection set with the observer and then somehow trigger the HTML Dialog (callback?) to do something. My brain is tied up in knots right now trying to figure out how to do this.

Maybe this is a good example of an application where the mixin module might be relevant.

Most coders will write a wrapper class around their use of a web dialog, so the js command would be wrapped in an instance method.

But you can define a singleton method directly upon the @dlg001 object …

def @dlg001.fire_js(data_hash)
  json = data_hash.to_json # has embedded double quotes
  js_command = %[javascript_set_data('#{json}');]
  @dlg001.execute_script(js_command)
end

It is likely best if you use data hashes and send them over as JSON.
on the JS side you convert the JSON string to a JS Object …

  // an Object to hold data ...
  var myObject = {}

  var javascript_set_data = function (json) {
    myObject = JSON.parse(json);
  }

Oh and I forgot, in order for the observer to “know” the dialog object you’d pass it’s reference into the observer’s constructor …

@spy = MySelectionObserver.new(@@dlg001)

… and then you assign the dialog reference to an internal instance variable so that you can call the singleton method …

class MySelectionObserver < Sketchup::SelectionObserver

  def initialize(dlg)
    @dlg = dlg # @dlg is now a reference to the dialog
  end

  def callback_name(selection)
    data_hash = {}
    # in some callback ... calc some data
    @dlg.fire_js(data_hash)
  end

end
1 Like

If you have a big class of methods it likely would work well as a mixin module instead.
Then you you include it into the namespaces where you wish to use it’s methods and you do not then need to qualify every call.

1 Like

Where would I place the singleton method, in the observer class or my MedeekMethods class along with the rest of the HTML dialog code? I’m thinking it needs to go in the observer class but then the @dlg001 in the first line of the method has me confused, shouldn’t this be @dlg?

You can also use your main class as the observer class. No need to have a dedicated observer class.

Then you have less problem to communicate with you main method.

2 Likes

After experimenting with this further I found that all I need to do is initialize the dialog object in the observer class as suggested above in Dan’s post, and then I can call this method/command directly within my onSelectionBulkChange method in the observer class:

@dlg.execute_script(js_command)

There is no need for the singleton method. I kind of happened upon this accidentally so I’m not sure if its right, but it seems to work and its not throwing any errors.

As I said before I’d likely wrap the use of a dialog inside a wrapper class. So that is where it’d go. At the same place you’d defined the callback blocks.

Ruby is multi-paradigm.

You are just calling another method of the dialog object, is all.

1 Like