module Zafu
module Process
module Ajax
def save_state
super.merge(:@markup => @markup.dup)
end
# This method process a list and handles building the necessary templates for ajax 'add'.
def expand_with_finder(finder)
return super unless finder[:class].kind_of?(Array)
# reset scope
@context[:saved_template] = nil
# Get the block responsible for rendering each elements in the list
each_block = descendant('each')
add_block = descendant('add')
form_block = descendant('form') || each_block
edit_block = descendant('edit')
# Should 'edit' and 'add' auto-publish ?
publish_after_save = (form_block && form_block.params[:publish]) ||
(edit_block && edit_block.params[:publish])
# class name for create form
klass = (add_block && add_block.params[:klass]) ||
(form_block && form_block.params[:klass])
if need_ajax?(each_block)
# We need to build the templates for ajax rendering.
# 1. Render inline
# assign [] to var
out "<% if (#{var} = #{finder[:method]}) || (#{node}.#{finder[:class].first <= Comment ? "can_comment?" : "can_write?"} && #{var}=[]) %>"
# The list is not empty or we have enough rights to add new elements.
set_dom_prefix
# New node context.
open_node_context(finder, :node => self.node.move_to(var, finder[:class])) do
# Pagination count and other contextual variables exist here.
# TODO: DRY with r_block...
# INLINE ==========
out wrap(
expand_with(
:in_if => false,
# 'r_add' needs the form when rendering. Send with :form.
:form => form_block,
:publish_after_save => publish_after_save,
# Do not render the form block directly: let [add] do this.
:ignore => ['form'],
:klass => klass
)
)
# Render 'else' clauses
else_clauses = expand_with(
:in_if => true,
:only => ['elsif', 'else'],
:markup => @markup
)
# 2. Save 'each' template
store_block(each_block, :node => self.node.move_to(var, finder[:class])) #, :klass => klass) # do we need klass here ?
# 3. Save 'form' template
cont = {
:saved_template => form_url(node.dom_prefix),
:klass => klass,
:make_form => each_block == form_block,
:publish_after_save => publish_after_save,
}
store_block(form_block, cont)
end
out "<% end %>"
else
super
end
# out wrap(expand_with(:node => node.move_to(var, finder[:class]), :in_if => true))
#query = opts[:query]
#
#
#if need_ajax?
# new_dom_scope
# # ajax, build template. We could merge the following code with 'r_block'.
#
# # FORM ============
# if each_block != form_block
# form = expand_block(form_block, :klass => klass, :add=>add_block, :publish_after_save => publish_after_save, :saved_template => true)
# else
# form = expand_block(form_block, :klass => klass, :add=>add_block, :make_form=>true, :publish_after_save => publish_after_save, :saved_template => true)
# end
# out helper.save_erb_to_url(form, form_url)
#else
# # no form, render, edit and add are not ajax
# if descendant('add') || descendant('add_document')
# out "<% if (#{list_var} = #{list_finder}) || (#{node}.#{node.will_be?(Comment) ? "can_comment?" : "can_write?"} && #{list_var}=[]) %>"
# elsif list_finder != 'nil'
# out "<% if #{list_var} = #{list_finder} %>"
# else
# out "<% if nil %>"
# end
#
#
# out render_html_tag(expand_with(:list=>list_var, :in_if => false))
# out expand_with(:in_if=>true, :only=>['elsif', 'else'], :html_tag => @html_tag, :html_tag_params => @html_tag_params)
# out "<% end %>"
#end
end
# Store a context as a sub-template that can be used in ajax calls
def r_block
if parent.method == 'each' && @method == parent.single_child_method
# Block stored in 'each', do nothing
# What happens when this is used as remote target ?
return expand_with
end
# Since we are using ajax, we will need this object to have an ID set.
set_dom_prefix
@markup.done = false
if @context[:block] == self
# Storing template (called from within store_block)
# Set id with the template's node context (<%= @node.zip %>).
@markup.set_id(node.dom_id(:list => false))
expand_with
else
# 1. store template
# will wrap with @markup
store_block(self, :ajax_action => 'show')
if edit_block = descendant('edit')
form_block = descendant('form') || self
publish_after_save = form_block.params[:publish] ||
(edit_block && edit_block.params[:publish])
# 2. store form
cont = {
:saved_template => form_url(node.dom_prefix),
:make_form => self == form_block,
:publish_after_save => publish_after_save,
:ajax_action => 'edit',
}
store_block(form_block, cont)
end
# 3. render
# Set id with the current node context (<%= var1.zip %>).
@markup.set_id(node.dom_id(:list => false))
out expand_with
end
end
def r_edit
# ajax
if cancel = @context[:form_cancel]
# cancel button
out cancel
else
# edit button
# TODO: show 'reply' instead of 'edit' in comments if visitor != author
block = ancestor(%w{each block})
# 'publish' is detected by r_block and set in form.
# removed so it does not polute link
@params.delete('publish')
@params.delete('cancel')
link = wrap(make_link(:default_text => _('edit'), :update => block, :action => 'edit'))
out "<% if #{node}.can_write? %>#{link}<% end %>"
end
end
def r_cancel
r_edit
end
def r_add
return parser_error("Should not be called from within 'each'") if parent.method == 'each'
return parser_error("Should not be called outside list context") unless node.list_context?
return '' if @context[:make_form]
if node.will_be?(Comment)
out "<% if #{node.up(Node)}.can_comment? %>"
else
out "<% if #{node.up(Node)}.can_write? %>"
end
unless descendant('add_btn')
# Add a descendant between self and blocks. ==> add( add_btn(blocks) )
blocks = @blocks.dup
@blocks = []
add_btn = make(:void, :method => 'add_btn', :params => @params.dup, :text => '')
add_btn.blocks = blocks
@all_descendants = nil
end
if @context[:form]
# ajax add
@markup.set_id("#{node.dom_prefix}_add")
@markup.append_param(:class, 'btn_add')
if @params[:focus]
focus = "$(\"#{node.dom_prefix}_#{@params[:focus]}\").focus();"
else
focus = "$(\"#{node.dom_prefix}_form_t\").focusFirstElement();"
end
# Expand 'add' block
out wrap("#{expand_with(:onclick=>"[\"#{node.dom_prefix}_add\", \"#{node.dom_prefix}_form\"].each(Element.toggle);#{focus}return false;")}")
if klass = @context[:klass]
return parser_error("Invalid class '#{klass}'") unless klass = get_class(klass)
else
klass = Array(node.klass).first
end
# New object to render form.
new_node = node.move_to("#{var}_new", klass, :new_record => true)
if new_node.will_be?(Node)
# FIXME: BUG if we set the user cannot select class with menu...
# FIXME: inspect '@context[:form]' to see if it contains v_klass ?
out "<% if #{new_node} = secure(Node) { Node.new_node('class' => '#{new_node.klass}') } %>"
else
out "<% if #{new_node} = #{new_node.class_name}.new %>"
end
form_block = @context[:form]
# Expand (inline) 'form' block
out expand_block(form_block,
# Needed in form to be able to return the result
:template_url => template_url(node.dom_prefix),
# ??
:in_add => true,
# Used to get parameters like 'publish' or 'klass'
:add => self,
# Transform 'each' block into a form
:make_form => form_block.method == 'each',
# Node context = new node
:node => new_node
)
out "<% end %>"
else
# no ajax
@markup.append_param(:class, 'btn_add') if @markup.tag
out wrap(expand_with)
end
out "<% end %>"
end
def r_add_btn
default = node.will_be?(Comment) ? _("btn_add_comment") : _("btn_add")
out "#{text_for_link(default)}"
end
def r_each
if @context[:saved_template]
# render to start a saved template
options = form_options
@markup.set_id(options[:id]) if options[:id]
@markup.set_param(:style, options[:style]) if options[:style]
out wrap(expand_with)
else
super
end
end
# Block visibility of descendance with 'do_list'.
def public_descendants
all = super
if ['context', 'each', 'block'].include?(method)
# do not propagate 'form',etc up
all.reject do |k,v|
# FIXME: Zena leakage
['form','unlink', 'count'].include?(k)
end
elsif ['if', 'case'].include?(method) || (method =~ /^[A-Z]/)
# conditional
all.reject do |k,v|
['else', 'elsif', 'when'].include?(k)
end
else
all
end
end
# Return true if we need to insert the dom id for this element.
def need_dom_id?
@context[:form] || descendant('unlink') || descendant('drop')
end
# Set a unique DOM prefix to build unique ids in the page.
def set_dom_prefix(node = self.node)
@name ||= unique_name
# TODO: should rebuild descendants list in parents...
#puts [parent.method, method, @context[:name], @name].inspect
node.dom_prefix = @name
end
# Unique template_url, ending with dom_id
def template_url(dom_prefix = node.dom_prefix)
"#{root.options[:root]}/#{dom_prefix}"
end
def form_url(dom_prefix = node.dom_prefix)
template_url(dom_prefix) + '_form'
end
# Return a different name on each call
def unique_name
base = @name || @context[:name] || 'list'
root.get_unique_name(base, base == @name).gsub(/[^\d\w\/]/,'_')
end
def get_unique_name(key, own_id = false)
@next_name_index ||= {}
if @next_name_index[key]
@next_name_index[key] += 1
key + @next_name_index[key].to_s
elsif own_id
@next_name_index[key] = 0
key
else
@next_name_index[key] = 1
key + '1'
end
end
protected
def need_ajax?(each_block)
return false unless each_block
# Inline editable
each_block.descendant('edit') ||
# Ajax add
descendant('add') ||
# List is reloaded from the 'add_document' popup
descendant('add_document') ||
# We use 'each' as block instead of the declared 'block' or 'drop'
['block', 'drop'].include?(each_block.single_child_method)
end
def context_for_partial(cont)
context_without_vars.merge(cont)
end
private
# Find a block to update on the page
def find_target(name)
# Hack for drop until descendants is rebuilt on set_dom_prefix
return self if name == self.name
root.descendants('block').each do |block|
return block if block.name == name
end
out parser_error("could not find a block named '#{name}'")
return nil
end
def store_block(block, cont = {})
cont, prefix = context_for_partial(cont)
# Create new node context
node = cont[:node].as_main(ActiveRecord::Base)
node.dom_prefix = @name
cont[:template_url] = template_url(node.dom_prefix)
cont[:node] = node
cont[:block] = block
cont[:saved_template] ||= cont[:template_url]
template = nil
# We overwrite all context: no merge.
with_context(cont, false) do
template = prefix.to_s + expand_block(block)
end
out helper.save_erb_to_url(template, cont[:saved_template])
end
#template = expand_block(self, :)
#
#if @context[:block] == self
# # called from self (storing template)
# @context.reject! do |k,v|
# # FIXME: reject all stored elements in a better way then this
# k.kind_of?(String) && k =~ /\ANode_\w/
# end
# @markup.done = false
# @markup.params.merge!(:id=>node.dom_id)
# @context[:scope_node] = node if @context[:scope_node]
# out expand_with(:node => node)
# if @method == 'drop' && !@context[:make_form]
# out drop_javascript
# end
#else
# if parent.method == 'each' && @method == parent.single_child_method
# # use parent as block
# # FIXME: will not work with block as distant target...
# # do nothing
# else
# @markup.tag ||= 'div'
# new_dom_scope
#
# unless @context[:make_form]
# # STORE TEMPLATE ========
#
# context_bak = @context.dup # avoid side effects when rendering the same block
# ignore_list = @method == 'block' ? ['form'] : [] # do not show the form in the normal template of a block
# template = expand_block(self, :block=>self, :list=>false, :saved_template=>true, :ignore => ignore_list)
# @context = context_bak
# @result = ''
# out helper.save_erb_to_url(template, template_url)
#
# # STORE FORM ============
# if edit = descendant('edit')
# publish_after_save = (edit.params[:publish] == 'true')
# if form = descendant('form')
# # USE BLOCK FORM ========
# form_text = expand_block(form, :saved_template=>true, :publish_after_save => publish_after_save)
# else
# # MAKE A FORM FROM BLOCK ========
# form = self.dup
# form.method = 'form'
# form_text = expand_block(form, :make_form => true, :list => false, :saved_template => true, :publish_after_save => publish_after_save)
# end
# out helper.save_erb_to_url(form_text, form_url)
# end
# end
#
# # RENDER
# @markup.done = false
# @markup.params.merge!(:id=>node.dom_id)
# end
#
# out expand_with
# if @method == 'drop' && !@context[:make_form]
# out drop_javascript
# end
#end
end
end
end