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):
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?
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
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.
* 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
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.
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
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.
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?
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.