Alternative Component Advanced Attributes Panel

Hello. I’m working on an alternative “component advanced attributes” panel. The code automatically updates the selected components in a dialog box. I have added a function to the button next to the “Price” value, where the user can enter a new “Price” value. However, I have not been able to get the function to work, and the button appears to be non-functional. I need your help in identifying where I have gone wrong.

require 'sketchup.rb'

class MySelectionObserver < Sketchup::SelectionObserver
  def initialize(dialog)
    @dialog = dialog
    @selection = Sketchup.active_model.selection
  end
  
  def onSelectionBulkChange(selection)
    update_dialog_values
  end
  
  def onSelectionCleared(selection)
    update_dialog_values
  end
  
  def onSelected(selection)
    update_dialog_values
  end
  
  def update_dialog_values
    selection = Sketchup.active_model.selection[0]
    if selection && selection.is_a?(Sketchup::ComponentInstance)
      definition = selection.definition
      name = definition.name
      price = definition.get_attribute('SU_DefinitionSet', 'Price', '0.0')
      @dialog.execute_script("document.getElementById('component_name').innerHTML = '#{name}';")
      @dialog.execute_script("document.getElementById('price').value = '#{price}';")
    end
  end
end

dialog = UI::HtmlDialog.new({
  :title => "Alternative Component Advanced Attributes Panel",
  :preferences_key => "ferhatatmaca",
  :scrollable => true,
  :resizable => false,
  :width => 400,
  :height => 500,
  :left => 100,
  :top => 100,
})

html = <<~HTML
  <html>
  <head>
    <title>Alternative Component Advanced Attributes Panel</title>
  </head>
  <body>
    <h1 id="component_name"></h1>
    <hr>
    <label title="Bileşenin Fiyatı">Fiyat:</label>
    <input style= 'width:180px; text-align: right;' disabled type='text' id='price' value=''>
    <button type="new_price">Degistir</button>  

<script>
    document.getElementById('new_price').addEventListener('click', function() {
    selection = Sketchup.active_model.selection
   if selection.length == 1 && selection[0].is_a?(Sketchup::ComponentInstance)
    component = selection[0]
    definition = component.definition
    prompts = ["Enter new price:"]
    defaults = ["0"]
    input = UI.inputbox(prompts, defaults, "New Price")
   if input
    new_price = input[0] 
    definition.set_attribute('SU_DefinitionSet', 'Price', '')
   end
  end
      }
    }
  })
</script>

  </body>
  </html>
HTML
dialog.set_html(html)
observer = MySelectionObserver.new(dialog)
model.selection.add_observer(observer)
dialog.show

Video is here.

Because this function is a JavaScript function not a Ruby method. The code within the JS function is written in Ruby. Chrome Embedded Framework is not running a Ruby interpreter.

To do this all on the HTML-side, you would need to display a hidden HTML <div> with a <input type='text'> element that has event listeners attached to respond to a input change. When the changes are complete, update the displayed price element and send it to the Ruby-side via a dialog callback.

To do this on the Ruby-side, the JS click event function should simply call a Ruby-side dialog callback that has the code which you’ve misplaced into the JS function. When the value from the Ruby inputbox is valid, then store it on the Ruby side, and send it to the JavaScript-side to update the HTML display.


Note: The key for the dialog …

  :preferences_key => "ferhatatmaca",

… needs to be unique to that extension and dialog (if using more than 1 per an extension.)

The identifier "ferhatatmaca" is a nice unique namespace identifier, but should have a qualified suffix for the extension and dialog.

This is because the model data space is a shared space where all attribute dictionaries are attached and might clash with one another if not qualified with namespace prefixes.

The same goes for Ruby’s top level ObjectSpace. ALL of your code must be within a unique namespace module. Ie … "FerhatAtmaca" is a nice unique namespace identifier.

Within this namespace module, each one of your extension needs to be separated by it’s own extension submodule so as not to clash with one another.

module FerhatAtmaca
  module AltAdvancedAttrPanel

    extend self

    # Create an options key for saving default settings:
    OPTS_KEY ||= Module.nesting[0].name.gsub('::','_')
    # Create a dialog key for saving window metrics:
    DLG_KEY ||= "#{OPTS_KEY}_dialog"

    # Selection Observer class definition ...
    class SelectionObserver
      def initialize(dialog)
        @dialog = dialog
        @selection = Sketchup.active_model.selection
      end
      def detach
        @selection.remove_observer(self)
      end
      #
      # Other callbacks and methods ...
      #
    end


    # Extension method definitions ...

    def open_panel
      if @dialog
        if @dialog.visible?
          @dialog.bring_to_front
        else
          # Callbacks are dettached when dialog is closed:
          attach_callbacks(@dialog)
          @dialog.show
        end
      else
        @dialog = create_dialog()
        @dialog.show
        @observer = SelectionObserver.new(@dialog)
        Sketchup.active_model.selection.add_observer(@observer)
      end
    end

    def attach_callbacks(dialog)
      # Define callbacks here ...
    end

    def create_dialog()
      # dialog and HTML creation code here ...
      return dialog
    end

    def close_panel(dialog)
      @observer.detach
      @dialog.close
      @dialog = nil
    end

    unless defined?(@gui_loaded)
      # Define GUI objects here ...
      UI.menu('Window').add_item('Alternative Advanced Attributes Panel') {
        open_panel() 
      }

      @gui_loaded = true
    end

  end
end

In the selection observer definition, there is no onSelected callback. There is/was a bug where the engine was calling onSelectedRemoved instead of onSelectionRemoved. The latter should be aliased for older versions as shown in the documentation.

You should test and see what callbacks get called in what scenarios, and you may need to handle the situation where more than 1 callback is called when a selection change happens, too avoid multiple updates to the dialog.

You may also want to cache the object reference currently shown and only update if there are differences from what is displayed.

2 Likes

Thanks :slight_smile: