class FabulatorPage < Page attr_accessor :inner_content description %{ A Fabulator page allows you to create a simple, interactive web application that manages data in RDF models defined in the Fabulator > RDF Models tab of the administrative area. } # need a reasonable name for the XML part XML_PART_NAME = 'extended' after_save :set_defaults attr_accessor :resource_ln, :c_state_machine # create tags to access filtered data in page display # might also create tags for form fields, etc., so it's easy to # create a form and fill in data def cache? false end def find_by_url(url, live = true, clean = false) p = super return p if !p.nil? && !p.is_a?(FileNotFoundPage) url = clean_url(url) if clean if url =~ %r{^#{ self.url }([-_0-9a-zA-Z]+)/?$} self.resource_ln = $1 return self else return p end end def url u = super if !self.resource_ln.nil? u = u + '/' + self.resource_ln end u end def state_machine if self.compiled_xml.nil? || self.compiled_xml == '' self.c_state_machine = nil else self.c_state_machine = (YAML::load(self.compiled_xml) rescue nil) unless self.c_state_machine end self.c_state_machine end def fabulator_context if @roots.nil? @roots = { } end if @roots['data'].nil? @roots['data'] = Fabulator::Expr::Node.new('data', @roots, nil, []) ctx = Fabulator::Expr::Context.new ctx.root = @roots['data'] ctx.traverse_path(['resource'], true).first.value = self.resource_ln if self.resource_ln self.state_machine.init_context(ctx) end @roots['data'] end def fabulator_context=(c) fc = self.fabulator_context @roots = { } if @roots.nil? @roots['data'] = c end def headers if @resetting_context { :location => self.url, } elsif @redirecting { :location => @redirecting, } else { } end end def response_code @resetting_context ? 302 : ( @redirecting ? 304 : 200 ) end def render_snippet(p) if p.name != XML_PART_NAME super else FabulatorLibrary.all.each do |library| if library.compiled_xml.is_a?(Fabulator::Lib::Lib) library.compiled_xml.register_library end end sm = self.state_machine return '' if sm.nil? # run state machine if POST context = FabulatorContext.find_by_page(self) @resetting_context = false if request.method == :get && params[:reset_context] if !context.new_record? context.destroy end # redirect without the reset_context param? @response.redirect(url,302) @resetting_context = true return end begin sm.context = YAML::load(context.context) if sm.context.empty? sm.init_context(self.fabulator_context) end #sm.context.merge!(self.resource_ln, ['resource'] ) if self.resource_ln if request.method == :post sm.run(params) # save context @sm_missing_args = sm.missing_params @sm_errors = sm.errors context.context = YAML::dump(sm.context) context.save end # save statemachine state # display resulting view rescue Fabulator::FabulatorRequireAuth => e @redirecting = e.to_s rescue => e return "
#{e.to_s}
" + e.backtrace.join("\n") + "" end return '' if @redirecting if sm.state != XML_PART_NAME return self.render_part(sm.state) else return 'Error: Fabulator application is not in a displayable state.' end end end def missing_args @sm_missing_args end tag 'fabulator' do |tag| tag.locals.fabulator_context = tag.locals.page.fabulator_context tag.expand end desc %{ Formats the enclosed logical form markup, adds default values, existing values, and marks errors or warnings and required fields. } tag 'form' do |tag| # get xml markup of form and transform it via xslt while adding # default values and such # wrap the whole thing in a form tag to post back to this page xml = tag.expand return '' if xml.blank? text_parser = Fabulator::Template::Parser.new c = get_fabulator_context(tag) root = nil missing_args = tag.locals.page.missing_args form_base = tag.attr['base'] if form_base.nil? || form_base == '' root = c form_base = c.root.path.gsub(/^.*::/, '').gsub('/', '.').gsub(/^\.+/, '') else root = c.nil? ? nil : c.with_root(c.eval_expression('/' + form_base.gsub('.', '/')).first) root = c if !c.nil? && root.root.nil? end root = c xml = "
...
}
tag 'for-each' do |tag|
selection = tag.attr['select']
c = get_fabulator_context(tag)
#Rails.logger.info("context: #{YAML::dump(c)}")
#Rails.logger.info("for-each: #{selection} from #{c}")
ns = get_fabulator_ns(tag)
items = c.nil? ? [] : c.eval_expression(selection)
sort_by = tag.attr['sort']
sort_dir = tag.attr['order'] || 'asc'
as = tag.attr['as']
if !sort_by.nil? && sort_by != ''
parser = Fabulator::Expr::Parser.new
sort_by_f = parser.parse(sort_by, c)
items = items.sort_by { |i| c.with_root(i).eval_expression(sort_by_f).first.value }
if sort_dir == 'desc'
items.reverse!
end
end
res = ''
#Rails.logger.info("Found #{items.size} items for for-each")
items.each do |i|
next if i.empty?
tag.locals.fabulator_context = c.with_root(i)
if !as.blank?
tag.locals.fabulator_context.set_var(as, i)
end
res = res + tag.expand
end
res
end
desc %{
Selects the value and returns it in HTML.
TODO: allow escaping of HTML special characters
*Usage:*
}
tag 'value' do |tag|
selection = tag.attr['select']
c = get_fabulator_context(tag)
items = c.nil? ? [] : c.eval_expression(selection)
items.collect{|i| i.to([Fabulator::FAB_NS, 'html']).value }.join('')
end
desc %{
Chooses the first test which returns content. Otherwise,
uses the 'otherwise' tag.
}
tag 'choose' do |tag|
@chosen ||= [ ]
@chosen.unshift false
ret = tag.expand
@chosen.shift
ret
end
desc %{
Renders the enclosed content if the test passes.
}
tag 'choose:when' do |tag|
return '' if @chosen.first
selection = tag.attr['test']
c = get_fabulator_context(tag)
items = c.nil? ? [] : c.eval_expression(selection)
if items.is_a?(Array)
if items.empty? || !items[0].value
return ''
else
@chosen[0] = true
return tag.expand
end
elsif items
@chosen[0] = true
return tag.expand
end
return ''
end
desc %{
Renders the enclosed content.
}
tag 'choose:otherwise' do |tag|
return '' if @chosen.first
tag.expand
end
desc %{
Renders the inherited view.
}
tag 'inner' do |tag|
@inner_content.nil? ? '' : @inner_content
end
desc %{
Renders the parent view providing the child view as an augmentation.
}
tag 'augment' do |tag|
parent_page = self.state_machine.isa
inner = tag.expand
return inner if parent_page.nil?
parent_page.inner_content = inner
parent_page.render_part(self.state_machine.state)
end
private
def get_fabulator_ns(tag)
c = tag.locals.page
if c.nil?
c = tag.globals.page
end
#Rails.logger.info("page: #{c}")
#Rails.logger.info("state machine: #{c.state_machine}")
#Rails.logger.info("namespaces: #{c.state_machine.namespaces}")
ret = (c.state_machine.namespaces rescue { })
#Rails.logger.info("get_fabulator_ns: [#{ret}]")
ret
end
def get_fabulator_context(tag)
c = tag.locals.fabulator_context
if c.nil?
c = tag.locals.page.state_machine.fabulator_context
if c.nil?
c = tag.globals.page.state_machine.fabulator_context
end
end
# TODO: move serialization back into the model
if c.is_a?(String)
c = YAML::load(c)
end
if c.is_a?(Hash)
c = c[:data]
end
return c
end
def set_defaults
# create a part for each state in the document
# 'body' is a description/special
# 'sidebar' is reserved
# compile statemachine into a set of Ruby objects and save
# not the most efficient, but we don't usually have hundreds of states
self[:compiled_xml] = nil
@compiled_xml = nil
sm = self.state_machine
Rails.logger.info("SM: #{YAML::dump(sm)}")
return if sm.nil?
#Rails.logger.info("Ensuring the right parts are present")
sm.state_names.sort.each do |part_name|
parts.create(:name => part_name, :content => %{