module Zafu class NodeContext # The name of the variable halding the current object or list ("@node", "var1") attr_reader :name # The previous NodeContext attr_reader :up # The type of object contained in the current context (Node, Page, Image) attr_reader :klass # The current DOM prefix to use when building DOM ids. This is set by the parser when # it has a name or dom id defined ('main', 'related', 'list', etc). attr_writer :dom_prefix # Any kind of information that the compiler might need to use (QueryBuilder query used # to fetch the node for example). attr_reader :opts def initialize(name, klass, up = nil, opts = {}) @name, @klass, @up, @opts = name, klass, up, opts end def move_to(name, klass, opts={}) NodeContext.new(name, klass, self, opts) end # Since the idiom to write the node context name is the main purpose of this class, it # deserves this shortcut. def to_s name end def single_class @single_class ||= Array(klass).flatten.first end # Return true if the NodeContext represents an element of the given type. We use 'will_be' because # it is equivalent to 'is_a', but for future objects (during rendering). def will_be?(type) single_class.ancestors.include?(type) end # Return a new node context that corresponds to the current object when rendered alone (in an ajax response or # from a direct 'show' in a controller). The returned node context has no parent (up is nil). # The convention is to use the class of the current object to build this name. # You can also use an 'after_class' parameter to move up in the current object's class hierarchy to get # ivar name (see #master_class). def as_main(after_class = nil) klass = after_class ? master_class(after_class) : single_class NodeContext.new("@#{klass.to_s.underscore}", single_class) end # Find the class just afer 'after_class' in the class hierarchy. # For example if we have Dog < Mamal < Animal < Creature, # master_class(Creature) would return Animal def master_class(after_class) klass = single_class begin up = klass.superclass return klass if up == after_class end while klass = up return self.klass end # Generate a unique DOM id for this element based on dom_scopes defined in parent contexts. def dom_id(opts = {}) options = {:list => true, :erb => true}.merge(opts) if options[:erb] dom = dom_id(options.merge(:erb => false)) if dom =~ /^#\{([^\{]+)\}$/ "<%= #{$1} %>" elsif dom =~ /#\{/ "<%= %Q{#{dom}} %>" else dom end else if @up [dom_prefix] + @up.dom_scopes + (options[:list] ? [make_scope_id] : []) else [dom_prefix] + (options[:list] ? [make_scope_id] : []) end.compact.uniq.join('_') end end # This holds the current context's unique name if it has it's own or one from the hierarchy. If # none is found, it builds one. def dom_prefix @dom_prefix || (@up ? @up.dom_prefix : nil) end # Mark the current context as being a looping element (each) whose DOM id needs to be propagated to sub-nodes # in order to ensure uniqueness of the dom_id (loops in loops problem). def propagate_dom_scope! @dom_scope = true end def get(klass) if single_class <= klass return self unless list_context? res_class = self.klass method = self.name while res_class.kind_of?(Array) method = "#{method}.first" res_class = res_class.first end move_to(method, res_class) elsif @up @up.get(klass) else nil end end def up(klass = nil) klass ? @up.get(klass) : @up end # Return true if the current klass is an Array. def list_context? klass.kind_of?(Array) end # Return the name of the current class with underscores like 'sub_page'. def underscore class_name.to_s.underscore end # Return the class name or the superclass name if the current class is an anonymous class. # FIXME: just use klass.to_s (so that we can do clever things with 'to_s') def class_name if list_context? klass = single_class "[#{(klass.name.blank? ? klass.superclass : klass).name}]" else (@klass.name.blank? ? @klass.superclass : @klass).name end end # Return the name to use for input fields def form_name @form_name ||= master_class(ActiveRecord::Base).name.underscore end protected # List of scopes defined in ancestry (used to generate dom_id). def dom_scopes (@up ? @up.dom_scopes : []) + (@dom_scope ? [make_scope_id] : []) end private def make_scope_id "\#{#{@name}.zip}" end end end