module Rtml::WidgetCore::ClassMethods @@gui_enabled_widgets = {} def proxy_module return @proxy_module if @proxy_module @proxy_module = Module.new @proxy_module.instance_variable_set("@shared_variables", shared_variables) @proxy_module.instance_eval do def included(base) @shared_variables.each do |sv| base.send(:attr_accessor, sv[:name]) end end end @proxy_module end # Yields this Widget's GUI configuration to the supplied block, allowing you to set up your Widget to be accessible # from the RTML Application Builder. Note that if this method is never called, your Widget will not be available to # the GUI. def gui yield(gui_configuration) end # Returns the +Rtml::WidgetCore::GuiConfiguration+ instance for this Widget. # Shorthand for: # widget.gui_enabled_widgets[widget] def gui_configuration gui_enabled_widgets[self] ||= Rtml::WidgetCore::GuiConfiguration.new(self) end ## Returns the array of Widgets that have called the #gui method, thereby marking themselves as accessible to the ## RTML Application Builder. def gui_enabled_widgets @@gui_enabled_widgets end # Creates a "shared" variable for the parent object with the specified default value, if any. This is much like # a class variable, except that it is unique to the parent object in question. Multiple calls to a Widget's # entry point will result in multiple Widgets being instantiated. If you use an instance variable to hold data, # then that instance variable will be different for every entry point called. However, sometimes you need to share # data across multiple instances of the same Widget, but not across *all* instances of the same Widget. For # instance, your Widget may want to track references to variables in an individual Screen, but not across multiple # Screens. To accomplish this, you essentially need an instance variable for the Screen. The "shared" method # manages these variables for you. # # Examples: # shared :tml_variables # shared :variable_definitions, :variable_declarations # shared :tml_variables => [] # initializes #tml_variables to [].dup def shared(*names) names.each do |name| if name.kind_of? Hash value = name.values.first name = name.keys.first.to_s shared_variables << { :name => name, :default_value => value } else name = name.to_s shared_variables << { :name => name, :default_value => nil } end delegate name, "#{name}=", :to => :parent end reinclude_proxy_module end # By default, if a Widget's entry point is called with a block argument, it will be passed into the #process method # of the entry point's return value. This is called subprocessing -- that is, processing the element to be returned # at a lower level. This method disables subprocessing and its related assertions. It's useful, for instance, if you # want to manually control how or when the block argument is processed, or if you want to ignore it entirely. # # If you want to disable subprocessing only under certain conditions, see the instance method of the same name. def disable_subprocessing! @disable_subprocessing = true end # Use this to enable subprocessing after it has already been disabled. See #disable_subprocessing! def enable_subprocessing! @disable_subprocessing = false end # Returns true if subprocessing has been disabled. See also #disable_subprocessing! def subprocessing_disabled? @disable_subprocessing ||= false end alias disable_subprocessing? subprocessing_disabled? # Specifies the targets that this Widget will affect. A target can be a name of a model or the name of a TML tag, # which matches that model's "name" attribute. # # Examples: # affects :document, :element, :property # names of models # affects :tml, :head, :screen # names of TML tags # def affects(*targets) targets.flatten.each do |target| target = target.to_s unless target.kind_of?(String) unless self.targets.include? target self.targets << target map = Rtml::Widgets.mapping(target) map.proxied_widgets << self map.imbue(proxy_module) end end end # Goes through each of the targets of this Widget, looks up its mapping in Rtml::Widgets.mapping, and calls # proxy_module.append_features(mapping) for each of those mappings. def reinclude_proxy_module targets.each do |target| Rtml::Widgets.mapping(target).imbue(proxy_module) end end # Specifies entry points, which are names of methods that can be called from the targets to invoke this Widget. # # Examples: # entry_point :screen # def entry_point(*names) names.each do |name| name = name.to_s unless entry_points.include?(name) entry_points << name add_entry_point(name) end end end # Adds the specified entry point to this Widget's proxy module. def add_entry_point(entry_point) # This validation doesn't work because it's not this proxy module, it's those of the OTHER Widgets # that should be checked. Not sure of the best way to do that yet. # if proxy_module.public_instance_methods.include?(entry_point) # raise Rtml::Errors::RulesViolationError, # "Widget entry point #{entry_point} cannot be defined because it already exists!" # end entry_point = entry_point.to_s if entry_point.is_a?(Symbol) enter_from_hash = "#{entry_point}_from_hash" ['?','!'].each do |i| enter_from_hash = enter_from_hash.gsub(/#{Regexp::escape(i)}/, '') + i if enter_from_hash[i] end line = __LINE__ + 2 code = <<-end_code def #{entry_point}(*args, &block) # def screen(*args, &block) widget = #{self.name}.new(self) # widget = Rtml::Widgets::Screens.new(self) r = widget.#{entry_point}(*args, &block) # r = widget.screen(*args, &block) unless widget.disable_subprocessing? || !block_given? unless r.respond_to?(:process) raise Rtml::Errors::SubprocessingNotSupported, "Return value \#{r} does not support subprocessing" end r.process &block end r rescue ArgumentError => ae if ae.backtrace[0] =~ /#{Regexp::escape entry_point}/ raise ArgumentError, ae.message, caller else raise ae end end # end # Calls the entry point after reformatting the arguments in hash to fit the entry point's requirements # per +Rtml::WidgetCore::GuiConfiguration#construct_arguments+ # # This is primarily for interfacing with the RTML GUI. # def #{enter_from_hash}(hash, &block) arguments = #{self}.gui_configuration.construct_arguments(hash || {}) self.#{entry_point} *arguments, &block end end_code proxy_module.class_eval code, __FILE__, line reinclude_proxy_module end # Returns true if the specified class is a valid target of this Widget def target?(base) if base.respond_to?(:name) name = base.name.underscore.sub(/^.*\//, '') targets.each do |target| if base.kind_of?(ActiveRecord::Base) # it's an instance, so we should check the tag name, not the class name return true if target == base.name else return true if target == name end end end false end end