class Rtml::Widgets::ScreenVariants < Rtml::Widget include Rtml::TmlizedConditions affects :screen entry_point :variant, :next, :next_screen, :goto disable_subprocessing! # we don't support it # Builds a TML next element. If the element already exists, its URI is changed. # If an :if or :unless option is provided, builds a variant instead. # See Rtml::Rules::ExpressionProcessing for information about the :if and :unless options. # Other options include :timeout, which specifies a timeout interval, or :key, which specifies # a trigger key. # Examples: # screen :main do # next_screen :terminate_loop # next_screen :loop_again, :if => 'tmlvar:counter < tmlvar:loop_count' # end def next(uri = :__not_set, options = {}) if uri == :__not_set next_element = (parent / 'next').first return next_element.property('uri') if next_element return parent.property('next') end unless options.empty? variant process_variant_options(options.merge(:uri => uri)) else next_element = (parent / 'next').first if next_element next_element.property('uri', uri) else next_opts = { :uri => uri } next_opts[:timeout] = options[:timeout] if options.key?(:timeout) parent.property('next', '') parent.build(:next, next_opts) end end end alias next_screen next alias goto next # Builds a TML variant element. If the corresponding NEXT element does not exist, then it is created. Its # attributes are inferred from the screen's NEXT property, if any. If the NEXT element neither exists nor # can be inferred, its URI will be set to raise an assertion error (#assert). If a variant already exists # with the same conditions, its URI will be replaced. # Examples: # screen :main, :next => :terminate_loop do # variant :uri => :loop_again, :lo => 'tmlvar:counter', :op => 'less', :ro => 'tmlvar:loop_count' # end def variant(options) options = options.with_indifferent_access validate_options(options, :variant) default_next = parent.property('next') next_element = (parent / 'next').first unless next_element if default_next.blank? next_element = parent.build(:next, :uri => 'assert') # raise Rtml::Errors::ScreenVariantError, "NEXT element does not exist, and could not infer destination" else next_element ||= parent.build(:next, :uri => default_next) # if it doesn't exist, create it. end end variants = (next_element / "variant") variants.each do |variant| # If variant contains the same conditions, replace the URI and return. if replaces_variant?(variant, options) variant.property('uri', options[:uri]) return variant end end next_element.build(:variant, options) end private def replaces_variant?(variant, options) equivalent_operation?(variant, options) \ || same_key?(variant, options) \ || both_timeout?(variant, options) end def equivalent_operation?(variant, options) options.key?(:lo) \ && variant.property('lo') == options[:lo] \ && variant.property('ro') == options[:ro] \ && variant.property('op') == options[:op] end def same_key?(variant, options) options.key?(:key) \ && variant.property('key') == options[:key] end def both_timeout?(variant, options) options.key?(:timeout) \ && variant.property('timeout') == options[:timeout] end def process_variant_options(options) ret = {} if options[:lo] || options[:ro] || options[:op] options.delete(:unless) options[:if] = [options.delete(:lo), options.delete(:op), options.delete(:ro)] end if options[:unless] || options[:if] ret[:lo], ret[:op], ret[:ro] = process_condition((options[:unless] ? :unless : :if), options[:unless] || options[:if]) end [:uri, :timeout, :key, :format].each { |k| ret[k] = options[k] if options.key?(k) } ret end def process_condition(condition_type, condition) condition = condition.split if condition.kind_of?(String) tmlize_condition(*(condition + [condition_type])) end def validate_options(options, type) case type when :variant if options.key?(:uri) options_valid = if options.key?(:lo) && options.key?(:ro) && options.key?(:op) true elsif not options.key?(:lo) && options.key?(:ro) && options.key?(:op) options.key?(:key) || options.key?(:timeout) end unless options_valid if options.key?(:lo) || options.key?(:op) || options.key?(:op) raise ArgumentError, "Expected :lo, :ro, :op options; see TML docs" else raise ArgumentError, "Expected condition or :key or :timeout options; see TML docs" end end else raise ArgumentError, "All variants must include a :uri option" end end end end