Quite a few improvements to Ajax handling here:

* Installed the RJS plugin http://www.codyfauser.com/articles/2005/12/05/rjs-templates-plugin-subversion-repository
* Used the RJS templates to update multiple page elements on addition and deletion of actions: the new action gets added, the count 'badge' is updated correctly, and a status area provides helpful information.
* If your data entry triggers validation errors e.g. no description for the next action), the errors are displayed in the status area (not very prettily as yet...)
* The message about the context/project having no uncompleted actions automagically appears/disappears without refreshing the page.

The editing and toggling of actions hasn't been updated yet.


git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@171 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
bsag 2006-01-04 19:49:15 +00:00
parent e5d9a413d5
commit c58f41775c
65 changed files with 3539 additions and 124 deletions

View file

@ -30,12 +30,6 @@ class ApplicationController < ActionController::Base
total = Todo.find_all("done=0").length - sub
end
# Returns all the errors on the page for an object...
#
def errors_for( obj )
error_messages_for( obj ) unless instance_eval("@#{obj}").nil?
end
# Reverses the urlize() method by substituting underscores for spaces
#
def deurlize(name)
@ -59,4 +53,21 @@ class ApplicationController < ActionController::Base
end
end
# Renders the given hash as xml. Primarily used to send multiple
# partials back to an ajax request
#
# * +renders+ is a Hash where the keys are string identifiers,
# and the values are partials rendered as a strings (see
# <tt>render_to_string</tt>).
def renders_to_xml(renders)
xml = '<?xml version="1.0" encoding="ISO-8859-1"?><renders>'
renders.each_key do |key|
xml += "<" + key.to_s +
"><![CDATA[#{renders[key]}]]></" +
key.to_s + ">"
end
xml += '</renders>'
render(:text => xml)
end
end

View file

@ -42,6 +42,71 @@ class ContextController < ApplicationController
render :text => "#{flash["warning"]}"
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
# in the selected context.
def add_item
self.init
@item = @user.todos.build
@item.attributes = @params["todo"]
if @item.due?
@item.due = Date.strptime(@params["todo"]["due"], DATE_FORMAT)
else
@item.due = ""
end
@saved = @item.save
@on_page = "context"
@up_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 0 and todos.context_id IN (?)", @user.id, @item.context_id]).size.to_s
return if request.xhr?
# fallback for standard requests
if @saved
flash["warning"] = 'Added new next action'
redirect_to :action => 'show', :id => @item
else
#render :action => 'new'
end
rescue
if request.xhr? # be sure to include an error.rjs
render :action => 'error'
else
flash["warning"] = 'An error occurred on the server.'
#render :action => 'new'
end
end
# Delete a next action
#
def destroy_action
self.init
@item = check_user_return_item
@saved = @item.destroy
@down_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 0 and todos.context_id IN (?)", @user.id, @item.context_id]).size.to_s
return if request.xhr?
# fallback for standard requests
if @saved
flash["warning"] = 'Successfully deleted next action'
redirect_to :controller => 'todo', :action => 'list'
else
render :controller => 'todo', :action => 'list'
end
rescue
if request.xhr? # be sure to include an error.rjs
render :action => 'error'
else
flash["warning"] = 'An error occurred on the server.'
render :controller => 'todo', :action => 'list'
end
end
# Edit the details of the context
#
@ -112,11 +177,23 @@ class ContextController < ApplicationController
render_text ""
end
end
def check_user_return_item
item = Todo.find( @params['id'] )
if @session['user'] == item.user
return item
else
flash["warning"] = "Item and session user mis-match: #{item.user.name} and #{@session['user'].name}!"
render_text ""
end
end
def init
@user = @session['user']
@projects = @user.projects.collect { |x| x.done? ? nil:x }.compact
@contexts = @user.contexts
@todos = @user.todos
@done = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 1", @user.id], :include => [:project], :order => "completed DESC")
end
def init_todos

View file

@ -62,6 +62,71 @@ class ProjectController < ApplicationController
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
# in the selected context.
def add_item
self.init
@item = @user.todos.build
@item.attributes = @params["todo"]
if @item.due?
@item.due = Date.strptime(@params["todo"]["due"], DATE_FORMAT)
else
@item.due = ""
end
@saved = @item.save
@on_page = "project"
@up_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 0 and todos.project_id IN (?)", @user.id, @item.project_id]).size.to_s
return if request.xhr?
# fallback for standard requests
if @saved
flash["warning"] = 'Added new next action'
redirect_to :action => 'show', :name => urlize(@item.project.name)
else
#render :action => 'new'
end
rescue
if request.xhr? # be sure to include an error.rjs
render :action => 'error'
else
flash["warning"] = 'An error occurred on the server.'
#render :action => 'new'
end
end
# Delete a next action
#
def destroy_action
self.init
@item = check_user_return_item
@saved = @item.destroy
@down_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 0 and todos.project_id IN (?)", @user.id, @item.project_id]).size.to_s
return if request.xhr?
# fallback for standard requests
if @saved
flash["warning"] = 'Successfully deleted next action'
redirect_to :controller => 'todo', :action => 'list'
else
render :controller => 'todo', :action => 'list'
end
rescue
if request.xhr? # be sure to include an error.rjs
render :action => 'error'
else
flash["warning"] = 'An error occurred on the server.'
render :controller => 'todo', :action => 'list'
end
end
# Edit the details of the project
#
def update
@ -142,10 +207,22 @@ class ProjectController < ApplicationController
end
end
def check_user_return_item
item = Todo.find( @params['id'] )
if @session['user'] == item.user
return item
else
flash["warning"] = "Item and session user mis-match: #{item.user.name} and #{@session['user'].name}!"
render_text ""
end
end
def init
@user = @session['user']
@projects = @user.projects
@projects = @user.projects.collect { |x| x.done? ? nil:x }.compact
@contexts = @user.contexts
@todos = @user.todos
@done = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 1", @user.id], :include => [:project], :order => "completed DESC")
end
def init_todos

View file

@ -32,29 +32,44 @@ class TodoController < ApplicationController
@count = @todos.collect { |x| ( !x.done? and !x.context.hidden? ) ? x:nil }.compact.size
end
def update_element
end
# Called by a form button
# Parameters from form fields are passed to create new action
# in the selected context.
def add_item
self.init
if @params["on_project_page"]
@on_page = "project"
end
item = @user.todos.build
item.attributes = @params["new_item"]
@item = @user.todos.build
@item.attributes = @params["todo"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
if @item.due?
@item.due = Date.strptime(@params["todo"]["due"], DATE_FORMAT)
else
item.due = ""
@item.due = ""
end
if item.save
render :partial => 'item', :object => item
@saved = @item.save
@on_page = "home"
@up_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 0", @user.id]).size.to_s
return if request.xhr?
# fallback for standard requests
if @saved
flash["warning"] = 'Added new next action'
redirect_to :action => 'list'
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
render :action => 'list'
end
rescue
if request.xhr? # be sure to include an error.rjs
render :action => 'error'
else
flash["warning"] = 'An error occurred on the server.'
render :action => 'list'
end
end
def edit_action
@ -101,16 +116,32 @@ class TodoController < ApplicationController
end
end
# Delete a next action in a context
# Delete a next action
#
def destroy_action
item = check_user_return_item
if item.destroy
render_text ""
self.init
@item = check_user_return_item
@saved = @item.destroy
@down_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = 0", @user.id]).size.to_s
return if request.xhr?
# fallback for standard requests
if @saved
flash["warning"] = 'Successfully deleted next action'
redirect_to :action => 'list'
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
render_text ""
render :action => 'list'
end
rescue
if request.xhr? # be sure to include an error.rjs
render :action => 'error'
else
flash["warning"] = 'An error occurred on the server.'
render :action => 'list'
end
end
# List the completed tasks, sorted by completion date

View file

@ -41,17 +41,10 @@ module TodoHelper
)
end
def link_to_remote_todo( item )
def link_to_remote_todo( item, handled_by)
str = link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
{
:update => "item-#{item.id}-container",
:loading => visual_effect(:fade, "item-#{item.id}-container"),
:url => { :controller => "todo", :action => "destroy_action", :id => item.id },
:confirm => "Are you sure that you want to delete the action, \'#{item.description}\'?"
},
{
:class => "icon"
}) + "\n"
{:url => { :controller => handled_by, :action => "destroy_action", :id => item.id }},
{:class => "icon"}) + "\n"
if !item.done?
str << link_to_remote( image_tag("blank", :title =>"Edit action", :class=>"edit_item", :id=>"action-#{item.id}-edit-icon"),
{

View file

@ -7,10 +7,9 @@
<%= link_to( sanitize("#{context.name}"), { :controller => "context", :action => "show", :name => urlize(context.name) }, { :title => "Go to the #{context.name} context page" } ) %>
</h2>
<div class="items toggle_target">
<div class="empty-nd" style="display:<%= @not_done.empty? ? 'block' : 'none'%>;">
<%= render :partial => "shared/empty",
:locals => { :message => "Currently there are no uncompleted actions in this context"} %>
<div id="empty-nd" style="display:<%= @not_done.empty? ? 'block' : 'none'%>;">
<div class="message"><p>Currently there are no uncompleted actions in this context</p></div>
</div>
<%= render :partial => "todo/item", :collection => @not_done %>
</div><!-- [end:items] -->
</div><!-- [end:c<%= context.id %>] -->
</div><!-- [end:c<%= context.id %>] -->

View file

@ -0,0 +1,14 @@
if @saved
page.insert_html :bottom, "c#{@item.context_id}", :partial => 'todo/item'
page.hide "status"
page.replace_html "status", content_tag("div", "Added new next action", "class" => "confirmation")
page.visual_effect :appear, 'status', :duration => 0.5
page.replace_html "badge_count", @up_count
page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3
page.hide "empty-nd" # If we are adding an new action, the uncompleted actions must be > 0
page.send :record, "Form.reset('todo-form-new-action');Form.focusFirstElement('todo-form-new-action')"
else
page.hide "status"
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
page.visual_effect :appear, 'status', :duration => 0.5
end

View file

@ -0,0 +1,11 @@
if @saved
page.alert "Are you sure that you want to delete the next action: \'#{@item.description}\'?"
page.visual_effect :fade, "item-#{@item.id}-container", :duration => 2.0
page.remove "item-#{@item.id}-container"
page.replace_html "badge_count", @down_count
if @down_count == "0"
page.show 'empty-nd'
end
else
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
end

View file

@ -0,0 +1 @@
page.replace_html "status", "A server error has occurred"

View file

@ -7,9 +7,10 @@
<div id="input_box">
<%= render "shared/add_new_item_form" %>
<%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %>
<%= render "shared/sidebar" %>
</div><!-- End of input box -->
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>
<% if @flash["warning"] %><div class="warning"><%= @flash["warning"] %></div><% end %>
<% if @flash["warning"] %>
<div id="warning_box" class="warning"><%= @flash["warning"] %></div>
<% end %>

View file

@ -21,7 +21,7 @@
<div>
<h1>
<% if @count %>
<span class="badge"><%= @count %></span>
<span id="badge_count" class="badge"><%= @count %></span>
<% end %>
<%= Time.now.strftime("%A, %d %B %Y") %></h1>
</div>

View file

@ -1,4 +1,5 @@
<% @not_done = project.find_not_done_todos -%>
<div id="p<%= project.id %>" class="container project">
<h2>
<% if collapsible %>
@ -14,10 +15,8 @@
<p class="project_completed">Project has been marked as completed</p>
<% end -%>
<div class="items toggle_target">
<div id="empty-nd" style="display:<%= @not_done.empty? ? 'block' : 'none'%>;">
<%= render :partial => "shared/empty",
:locals => { :message => "Currently there are no uncompleted actions in this project"} %>
<div class="message"><p>Currently there are no uncompleted actions in this project</p></div>
</div>
<%= render :partial => "todo/item", :collection => @not_done %>
</div><!-- [end:items] -->

View file

@ -0,0 +1,14 @@
if @saved
page.insert_html :bottom, "p#{@item.project_id}", :partial => 'todo/item'
page.hide "status"
page.replace_html "status", content_tag("div", "Added new next action", "class" => "confirmation")
page.visual_effect :appear, 'status', :duration => 0.5
page.replace_html "badge_count", @up_count
page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3
page.hide "empty-nd" # If we are adding an new action, the uncompleted actions must be > 0
page.send :record, "Form.reset('todo-form-new-action');Form.focusFirstElement('todo-form-new-action')"
else
page.hide "status"
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
page.visual_effect :appear, 'status', :duration => 0.5
end

View file

@ -0,0 +1,11 @@
if @saved
page.alert "Are you sure that you want to delete the next action: \'#{@item.description}\'?"
page.visual_effect :fade, "item-#{@item.id}-container", :duration => 2.0
page.remove "item-#{@item.id}-container"
page.replace_html "badge_count", @down_count
if @down_count == "0"
page.show 'empty-nd'
end
else
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
end

View file

@ -0,0 +1 @@
page.replace_html "status", "A server error has occurred"

View file

@ -39,7 +39,7 @@
<div id="input_box">
<%= render "shared/add_new_item_form" %>
<%= render "shared/_add_new_item_form" %>
<%= render "shared/sidebar" %>
</div><!-- End of input box -->

View file

@ -0,0 +1,57 @@
<%
case controller.controller_name
when "context"
add_string = "Add a next action in this context &#187;"
when "project"
add_string = "Add a next action in this project &#187;"
else
add_string = "Add a next action &#187;"
end
%>
<% unless hide_link -%>
<%= link_to_function(
add_string,
"Element.toggle('todo_new_action');Form.focusFirstElement('todo-form-new-action');",
{:title => "Add the next action", :accesskey => "n"}) %>
<% end -%>
<div id="todo_new_action" class="context_new" style="display:<%= hide_link ? 'block' : 'none'%>">
<div id="status">
</div>
<!--[form:todo]-->
<%= form_remote_tag(
:url => { :controller => controller.controller_name, :action => "add_item" },
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }) %>
<label for="todo_description">Description</label><br />
<%= text_field( "todo", "description", "size" => 25, "tabindex" => 1) %><br />
<label for="todo_notes">Notes</label><br />
<%= text_area( "todo", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2) %><br />
<% unless controller.controller_name == "context" -%>
<label for="todo_context_id">Context</label><br />
<%= collection_select( "todo", "context_id", @contexts, "id", "name",
{}, {"tabindex" => 3}) %><br />
<% end -%>
<% unless controller.controller_name == "project" -%>
<label for="todo_project_id">Project</label><br />
<%= collection_select( "todo", "project_id", @projects, "id", "name",
{ :include_blank => true }, {"tabindex" => 4}) %><br />
<% end -%>
<label for="item_due">Due</label><br />
<%= text_field("todo", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5) %>
<% if controller.controller_name == "project" -%>
<%= hidden_field( "todo", "project_id", "value" => "#{@project.id}") %>
<% elsif controller.controller_name == "context" -%>
<%= hidden_field( "todo", "context_id", "value" => "#{@context.id}") %>
<% end -%>
<br /><br />
<input type="submit" value="Add item" tabindex="6">
<%= end_form_tag %><!--[eoform:todo]-->
<%= calendar_setup( "todo_due" ) %>
</div><!-- [end:todo-new-action] -->

View file

@ -1,61 +0,0 @@
<%
case controller.controller_name
when "context"
add_string = "Add a next action in this context &#187;"
update_div = "c" + @context.id.to_s
when "project"
add_string = "Add a next action in this project &#187;"
update_div = "p" + @project.id.to_s
else
add_string = "Add a next action &#187;"
update_div = "new_actions"
end
%>
<%= link_to_function(
add_string,
"Element.toggle('todo_new_action');Form.focusFirstElement('todo-form-new-action');",
{:title => "Add the next action", :accesskey => "n"}) %>
<div id="todo_new_action" class="context_new" style="display:none">
<!--[form:todo]-->
<%= form_remote_tag(
:url => { :controller => "todo", :action => "add_item" },
:update => update_div,
:position => "bottom",
:loading => "ensureVisibleWithEffectAppear('#{update_div}');",
:complete => "Form.focusFirstElement('todo-form-new-action');",
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }) %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2) %><br />
<% unless controller.controller_name == "context" -%>
<label for="new_item_context_id">Context</label><br />
<%= collection_select( "new_item", "context_id", @contexts, "id", "name",
{}, {"tabindex" => 3}) %><br />
<% end -%>
<% unless controller.controller_name == "project" -%>
<label for="new_item_project_id">Project</label><br />
<%= collection_select( "new_item", "project_id", @projects, "id", "name",
{ :include_blank => true }, {"tabindex" => 4}) %><br />
<% end -%>
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5) %>
<% if controller.controller_name == "project" -%>
<%= hidden_field( "new_item", "project_id", "value" => "#{@project.id}") %>
<input type="hidden" name="on_project_page" value="true" />
<% elsif controller.controller_name == "context" -%>
<%= hidden_field( "new_item", "context_id", "value" => "#{@context.id}") %>
<% end -%>
<br /><br />
<input type="submit" value="Add item" tabindex="6">
<%= end_form_tag %><!--[eoform:todo]-->
<%= calendar_setup( "new_item_due" ) %>
</div><!-- [end:todo-new-action] -->

View file

@ -4,7 +4,7 @@
<%= form_remote_tag_toggle_todo( item ) %>
<%= form_tag( { :controller => "todo", :action => "toggle_check", :id => item.id },
{ :class => "inline-form" }) %>
<%= link_to_remote_todo( item ) %>
<%= link_to_remote_todo( item, controller.controller_name ) %>
<input type="checkbox" class="item-checkbox" name="item_id" value="<%= item.id %>"<% if item.done? %> checked="checked" <% end %> />
<div class="description<%= staleness_class( item ) %>"><% # start of div which has a class 'description', and possibly 'stale_11', 'stale_12', 'stale_13' etc %>
<% if item.done? -%>

View file

@ -0,0 +1,13 @@
if @saved
page.insert_html :bottom, "c#{@item.context_id}", :partial => 'todo/item'
page.hide "status"
page.replace_html "status", content_tag("div", "Added new next action", "class" => "confirmation")
page.visual_effect :appear, 'status', :duration => 0.5
page.replace_html "badge_count", @up_count
page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3
page.send :record, "Form.reset('todo-form-new-action');Form.focusFirstElement('todo-form-new-action')"
else
page.hide "status"
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
page.visual_effect :appear, 'status', :duration => 0.5
end

View file

@ -0,0 +1,8 @@
if @saved
page.alert "Are you sure that you want to delete the next action: \'#{@item.description}\'?"
page.visual_effect :fade, "item-#{@item.id}-container", :duration => 2.0
page.remove "item-#{@item.id}-container"
page.replace_html "badge_count", @down_count
else
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
end

View file

@ -0,0 +1 @@
page.replace_html "status", "An error occurred on the server."

View file

@ -1,7 +1,4 @@
<div id="display_box">
<div id="new_actions" class="next_actions container" style="display:none">
<h2>Fresh actions (hit <a href="javascript:window.location.reload()">refresh</a> to sort)</h2>
</div>
<%= render :partial => "context/context", :collection => @contexts_to_show,
:locals => { :collapsible => true } %>
<%= render :partial => "todo/completed",
@ -9,13 +6,11 @@
</div><!-- End of display_box -->
<div id="input_box">
<%= render "shared/add_new_item_form" %>
<%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %>
<%= render "shared/sidebar" %>
</div><!-- End of input box -->
<% if @flash["confirmation"] -%>
<div class="confirmation"><%= @flash["confirmation"] %></div>
<% end -%>
<div class="status-box" id="confirmation-box"></div>
<% if @flash["warning"] -%>
<div class="warning"><%= @flash["warning"] %></div>
<% end -%>

View file

@ -53,7 +53,7 @@ ActionController::Routing::Routes.draw do |map|
map.connect 'feed/:action/:name/:user', :controller => 'feed'
map.connect 'add_item', :controller => 'todo', :action => 'add_item'
#map.connect 'add_item', :controller => 'todo', :action => 'add_item'
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'

View file

@ -31,6 +31,9 @@ Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
13. The TXT view is now sorted by position, just as the home page is.
14. <b>Contributed by lolindrath</b>: Items that are overdue are coloured red, and have the text 'Overdue by X days' in the badge. Other due dates are given as days from now (up to a week away in orange, more than a week away in green), and the tool tip shows the actual date.
15. Projects and Contexts in [tracks_url]/projects and [tracks_url]/contexts can now be rearranged in order by dragging and dropping. Just pick the item up by the 'DRAG' label and drop it where you want.
16. Got rid of the 'fresh actions' box on the home page. When you create a new action, it is now automatically inserted (via the magic of Ajax and RJS templates) at the bottom of the correct context box.
17. The next action count badge is now dynamically updated whenever you add or delete a next action, so that you don't have to refresh to see the count updated.
18. Validation errors are now reported in a status box (just above the new next action form).
== Version 1.03

View file

@ -273,21 +273,17 @@ a.footer_link:hover {color: #fff; background-color: #cc3334 !important;}
/* The alert box notifications */
.confirmation {
margin: 20px 50px 20px 490px;
border: 1px solid #007E00;
background-color: #c2ffc2;
padding: 5px;
color: #007E00;
text-align: center;
}
.warning {
margin: 20px 50px 20px 490px;
border: 1px solid #ED2E38;
background-color: #F6979C;
padding: 5px;
color: #ED2E38;
text-align: center;
text-align: left;
}
.project_completed {
@ -519,4 +515,43 @@ div.message {
text-align: center;
font-size: 1em;
color: #666;
}
}
/* Error message styles */
.fieldWithErrors {
padding: 2px;
background-color: red;
display: table;
}
#ErrorExplanation {
width: 400px;
border: 2px solid #red;
padding: 7px;
padding-bottom: 12px;
margin-bottom: 20px;
background-color: #f0f0f0;
}
#ErrorExplanation h2 {
text-align: left;
font-weight: bold;
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
background-color: #c00;
color: #fff;
}
#ErrorExplanation p {
color: #333;
margin-bottom: 0;
padding: 5px;
}
#ErrorExplanation ul li {
font-size: 12px;
list-style: square;
}
ul.warning { list-style-type: circle; font-size: 1em; }

View file

@ -0,0 +1,15 @@
December 25, 2005
* Added changeset 3316: http://dev.rubyonrails.org/changeset/3316
* Added tests for javascript_generator_templates up to changeset 3116
* Added changeset 3319: http://dev.rubyonrails.org/changeset/3319
* Adds support for alert, redirect_to, call, assign, <<
* Added changeset 3329 and 3335
* Adds support for toggle, delay
December 15, 2005
* Updated prototype.js to 1.4.0
* Enabled the test test_render_file_with_locals
* Add CHANGELOG file
* Update README to reflect version 1.0 of Rails
* Added MIT-LICENSE

View file

@ -0,0 +1,20 @@
Copyright (c) 2004 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,15 @@
JavaScriptGeneratorTemplates
============================
This plugin allows the usage of the new RJS templates without having to run
edge rails. For more information about RJS templates please check out these
resources on the web:
http://rails.techno-weenie.net/tip/2005/11/29/ajaxed_forms_with_rjs_templates
http://www.codyfauser.com/articles/2005/11/20/rails-rjs-templates
http://dev.rubyonrails.org/changeset/3078
The RJS templates need at least version 1.4.0_rc4 of the prototype library to
function correctly. Run rake update_prototype from this source directory to
update your project's version of prototype to 1.4.0.

View file

@ -0,0 +1,27 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the javascript_generator_templates plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the javascript_generator_templates plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'JavaScriptGeneratorTemplates'
rdoc.options << '--line-numbers --inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end
desc "Install prototype.js file to public/javascripts"
task :update_prototype do
FileUtils.cp(File.dirname(__FILE__) + "/javascripts/prototype.js", File.dirname(__FILE__) + '/../../../public/javascripts/')
end

View file

@ -0,0 +1,3 @@
require 'add_rjs_to_action_view'
require 'add_rjs_to_action_controller'
require 'add_rjs_to_javascript_helper'

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module ActionController #:nodoc:
class Base
protected
def render_action(action_name, status = nil, with_layout = true)
template = default_template_name(action_name)
if with_layout && !template_exempt_from_layout?(template)
render_with_layout(template, status)
else
render_without_layout(template, status)
end
end
private
def template_exempt_from_layout?(template_name = default_template_name)
@template.javascript_template_exists?(template_name)
end
def default_template_name(default_action_name = action_name)
default_action_name = default_action_name.dup
strip_out_controller!(default_action_name) if template_path_includes_controller?(default_action_name)
"#{self.class.controller_path}/#{default_action_name}"
end
def strip_out_controller!(path)
path.replace path.split('/', 2).last
end
def template_path_includes_controller?(path)
path.to_s['/'] && self.class.controller_path.split('/')[-1] == path.split('/')[0]
end
end
module Layout #:nodoc:
private
def apply_layout?(template_with_options, options)
template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
end
def candidate_for_layout?(options)
(options.has_key?(:layout) && options[:layout] != false) ||
options.values_at(:text, :file, :inline, :partial, :nothing).compact.empty? &&
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
end
end
end

View file

@ -0,0 +1,142 @@
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module ActionView
class Base
def pick_template_extension(template_path)#:nodoc:
if match = delegate_template_exists?(template_path)
match.first
elsif erb_template_exists?(template_path): 'rhtml'
elsif builder_template_exists?(template_path): 'rxml'
elsif javascript_template_exists?(template_path): 'rjs'
else
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}"
end
end
def javascript_template_exists?(template_path)#:nodoc:
template_exists?(template_path, :rjs)
end
def file_exists?(template_path)#:nodoc:
%w(erb builder javascript delegate).any? do |template_type|
send("#{template_type}_template_exists?", template_path)
end
end
private
# Create source code for given template
def create_template_source(extension, template, render_symbol, locals)
if template_requires_setup?(extension)
body = case extension.to_sym
when :rxml
"xml = Builder::XmlMarkup.new(:indent => 2)\n" +
"@controller.headers['Content-Type'] ||= 'text/xml'\n" +
template
when :rjs
"@controller.headers['Content-Type'] ||= 'text/javascript'\n" +
"update_page do |page|\n#{template}\nend"
end
else
body = ERB.new(template, nil, @@erb_trim_mode).src
end
@@template_args[render_symbol] ||= {}
locals_keys = @@template_args[render_symbol].keys | locals
@@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
locals_code = ""
locals_keys.each do |key|
locals_code << "#{key} = local_assigns[:#{key}] if local_assigns.has_key?(:#{key})\n"
end
"def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
end
def template_requires_setup?(extension)
templates_requiring_setup.include? extension.to_s
end
def templates_requiring_setup
%w(rxml rjs)
end
def assign_method_name(extension, template, file_name)
method_name = '_run_'
method_name << "#{extension}_" if extension
if file_name
file_path = File.expand_path(file_name)
base_path = File.expand_path(@base_path)
i = file_path.index(base_path)
l = base_path.length
method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone
method_name_file_part.sub!(/\.r(html|xml|js)$/,'')
method_name_file_part.tr!('/:-', '_')
method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s}
method_name += method_name_file_part
else
@@inline_template_count += 1
method_name << @@inline_template_count.to_s
end
@@method_names[file_name || template] = method_name.intern
end
def compile_template(extension, template, file_name, local_assigns)
method_key = file_name || template
render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name)
render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
line_offset = @@template_args[render_symbol].size
if extension
case extension.to_sym
when :rxml, :rjs
line_offset += 2
end
end
begin
unless file_name.blank?
CompiledTemplates.module_eval(render_source, file_name, -line_offset)
else
CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)
end
rescue Object => e
if logger
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
logger.debug "Function body: #{render_source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise TemplateError.new(@base_path, method_key, @assigns, template, e)
end
@@compile_time[render_symbol] = Time.now
# logger.debug "Compiled template #{method_key}\n ==> #{render_symbol}" if logger
end
end
end

View file

@ -0,0 +1,204 @@
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module ActionView
module Helpers
module JavaScriptHelper
# JavaScriptGenerator generates blocks of JavaScript code that allow you
# to change the content and presentation of multiple DOM elements. Use
# this in your Ajax response bodies, either in a <script> tag or as plain
# JavaScript sent with a Content-type of "text/javascript".
#
# Create new instances with PrototypeHelper#update_page, then call
# #insert_html, #replace_html, #remove, #show, or #hide on the yielded
# generator in any order you like to modify the content and appearance of
# the current page. (You can also call other helper methods which
# return JavaScript, such as
# ActionView::Helpers::ScriptaculousHelper#visual_effect.)
#
# Example:
#
# update_page do |page|
# page.insert_html :bottom, 'list', '<li>Last item</li>'
# page.visual_effect :highlight, 'list'
# page.hide 'status-indicator', 'cancel-link'
# end
#
# generates the following JavaScript:
#
# new Insertion.Bottom("list", "<li>Last item</li>");
# new Effect.Highlight("list");
# ["status-indicator", "cancel-link"].each(Element.hide);
#
# You can also use PrototypeHelper#update_page_tag instead of
# PrototypeHelper#update_page to wrap the generated JavaScript in a
# <script> tag.
class JavaScriptGenerator
def initialize(context) #:nodoc:
@context, @lines = context, []
yield self
end
def to_s #:nodoc:
@lines * $/
end
# Inserts HTML at the specified +position+ relative to the DOM element
# identified by the given +id+.
#
# +position+ may be one of:
#
# <tt>:top</tt>:: HTML is inserted inside the element, before the
# element's existing content.
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
# element's existing content.
# <tt>:before</tt>:: HTML is inserted immediately preceeding the element.
# <tt>:after</tt>:: HTML is inserted immediately following the element.
#
# +options_for_render+ may be either a string of HTML to insert, or a hash
# of options to be passed to ActionView::Base#render. For example:
#
# # Insert the rendered 'navigation' partial just before the DOM
# # element with ID 'content'.
# insert_html :before, 'content', :partial => 'navigation'
#
# # Add a list item to the bottom of the <ul> with ID 'list'.
# insert_html :bottom, 'list', '<li>Last item</li>'
#
def insert_html(position, id, *options_for_render)
insertion = position.to_s.camelize
call "new Insertion.#{insertion}", id, render(*options_for_render)
end
# Replaces the inner HTML of the DOM element with the given +id+.
#
# +options_for_render+ may be either a string of HTML to insert, or a hash
# of options to be passed to ActionView::Base#render. For example:
#
# # Replace the HTML of the DOM element having ID 'person-45' with the
# # 'person' partial for the appropriate object.
# replace_html 'person-45', :partial => 'person', :object => @person
#
def replace_html(id, *options_for_render)
call 'Element.update', id, render(*options_for_render)
end
# Removes the DOM elements with the given +ids+ from the page.
def remove(*ids)
record "#{javascript_object_for(ids)}.each(Element.remove)"
end
# Shows hidden DOM elements with the given +ids+.
def show(*ids)
call 'Element.show', *ids
end
# Hides the visible DOM elements with the given +ids+.
def hide(*ids)
call 'Element.hide', *ids
end
# Toggles the visibility of the DOM elements with the given +ids+.
def toggle(*ids)
call 'Element.toggle', *ids
end
# Displays an alert dialog with the given +message+.
def alert(message)
call 'alert', message
end
# Redirects the browser to the given +location+, in the same form as
# +url_for+.
def redirect_to(location)
assign 'window.location.href', @context.url_for(location)
end
# Calls the JavaScript +function+, optionally with the given
# +arguments+.
def call(function, *arguments)
record "#{function}(#{arguments_for_call(arguments)})"
end
# Assigns the JavaScript +variable+ the given +value+.
def assign(variable, value)
record "#{variable} = #{javascript_object_for(value)}"
end
# Writes raw JavaScript to the page.
def <<(javascript)
@lines << javascript
end
# Executes the content of the block after a delay of +seconds+. Example:
#
# page.delay(20) do
# page.visual_effect :fade, 'notice'
# end
def delay(seconds = 1)
record "setTimeout(function() {\n\n"
yield
record "}, #{(seconds * 1000).to_i})"
end
private
def method_missing(method, *arguments, &block)
record(@context.send(method, *arguments, &block))
end
def record(line)
returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do
self << line
end
end
def render(*options_for_render)
Hash === options_for_render.first ?
@context.render(*options_for_render) :
options_for_render.first.to_s
end
def javascript_object_for(object)
object.respond_to?(:to_json) ? object.to_json : object.inspect
end
def arguments_for_call(arguments)
arguments.map { |argument| javascript_object_for(argument) }.join ', '
end
end
# Yields a JavaScriptGenerator and returns the generated JavaScript code.
# Use this to update multiple elements on a page in an Ajax response.
# See JavaScriptGenerator for more information.
def update_page(&block)
JavaScriptGenerator.new(@template, &block).to_s
end
# Works like update_page but wraps the generated JavaScript in a <script>
# tag. Use this to include generated JavaScript in an ERb template.
# See JavaScriptGenerator for more information.
def update_page_tag(&block)
javascript_tag update_page(&block)
end
end
end
end

View file

@ -0,0 +1,20 @@
$:.unshift(File.dirname(__FILE__) + '/..lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
rails_dir = File.dirname(__FILE__) + '/../../../rails'
if File.directory?(rails_dir)
lib_dir = rails_dir + '/actionpack/lib'
require lib_dir + '/action_controller'
require lib_dir + '/action_controller/test_process'
else
require 'rubygems'
require 'action_controller'
require 'action_controller/test_process'
end
require 'test/unit'
require 'init'
ActionController::Base.logger = nil
ActionController::Base.ignore_missing_templates = false
ActionController::Routing::Routes.reload rescue nil

View file

@ -0,0 +1,477 @@
require File.dirname(__FILE__) + '/../abstract_unit'
silence_warnings { Customer = Struct.new("Customer", :name) }
module Fun
class GamesController < ActionController::Base
def hello_world
end
end
end
class NewRenderTestController < ActionController::Base
layout :determine_layout
def self.controller_name; "test"; end
def self.controller_path; "test"; end
def hello_world
end
def render_hello_world
render :template => "test/hello_world"
end
def render_hello_world_from_variable
@person = "david"
render :text => "hello #{@person}"
end
def render_action_hello_world
render :action => "hello_world"
end
def render_text_hello_world
render :text => "hello world"
end
def render_text_hello_world_with_layout
@variable_for_layout = ", I'm here!"
render :text => "hello world", :layout => true
end
def render_custom_code
render :text => "hello world", :status => "404 Moved"
end
def render_file_with_instance_variables
@secret = 'in the sauce'
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.rhtml')
render :file => path
end
def render_file_with_locals
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.rhtml')
render :file => path, :locals => {:secret => 'in the sauce'}
end
def render_file_not_using_full_path
@secret = 'in the sauce'
render :file => 'test/render_file_with_ivar', :use_full_path => true
end
def render_xml_hello
@name = "David"
render :template => "test/hello"
end
def greeting
# let's just rely on the template
end
def layout_test
render :action => "hello_world"
end
def layout_test_with_different_layout
render :action => "hello_world", :layout => "standard"
end
def rendering_without_layout
render :action => "hello_world", :layout => false
end
def layout_overriding_layout
render :action => "hello_world", :layout => "standard"
end
def rendering_nothing_on_layout
render :nothing => true
end
def builder_layout_test
render :action => "hello"
end
def partials_list
@test_unchanged = 'hello'
@customers = [ Customer.new("david"), Customer.new("mary") ]
render :action => "list"
end
def partial_only
render :partial => true
end
def partial_only_with_layout
render :partial => "partial_only", :layout => true
end
def partial_with_locals
render :partial => "customer", :locals => { :customer => Customer.new("david") }
end
def partial_collection
render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
def partial_collection_with_locals
render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
end
def empty_partial_collection
render :partial => "customer", :collection => []
end
def partial_with_hash_object
render :partial => "hash_object", :object => {:first_name => "Sam"}
end
def partial_with_implicit_local_assignment
@customer = Customer.new("Marcel")
render :partial => "customer"
end
def hello_in_a_string
@customers = [ Customer.new("david"), Customer.new("mary") ]
render :text => "How's there? #{render_to_string("test/list")}"
end
def accessing_params_in_template
render :inline => "Hello: <%= params[:name] %>"
end
def accessing_params_in_template_with_layout
render :layout => nil, :inline => "Hello: <%= params[:name] %>"
end
def render_with_explicit_template
render "test/hello_world"
end
def double_render
render :text => "hello"
render :text => "world"
end
def double_redirect
redirect_to :action => "double_render"
redirect_to :action => "double_render"
end
def render_and_redirect
render :text => "hello"
redirect_to :action => "double_render"
end
def rendering_with_conflicting_local_vars
@name = "David"
def @template.name() nil end
render :action => "potential_conflicts"
end
def delete_with_js
@project_id = 4
end
def render_js_with_explicit_template
@project_id = 4
render :template => 'test/delete_with_js'
end
def render_js_with_explicit_action_template
@project_id = 4
render :action => 'delete_with_js'
end
def action_talk_to_layout
# Action template sets variable that's picked up by layout
end
def render_text_with_assigns
@hello = "world"
render :text => "foo"
end
def yield_content_for
render :action => "content_for", :layout => "yield"
end
def rescue_action(e) raise end
private
def determine_layout
case action_name
when "hello_world", "layout_test", "rendering_without_layout",
"rendering_nothing_on_layout", "render_text_hello_world",
"render_text_hello_world_with_layout",
"partial_only", "partial_only_with_layout",
"accessing_params_in_template",
"accessing_params_in_template_with_layout",
"render_with_explicit_template",
"render_js_with_explicit_template",
"render_js_with_explicit_action_template",
"delete_with_js"
"layouts/standard"
when "builder_layout_test"
"layouts/builder"
when "action_talk_to_layout", "layout_overriding_layout"
"layouts/talk_from_action"
end
end
end
NewRenderTestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/"
class NewRenderTest < Test::Unit::TestCase
def setup
@controller = NewRenderTestController.new
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
# a more accurate simulation of what happens in "real life".
@controller.logger = Logger.new(nil)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.host = "www.nextangle.com"
end
def test_simple_show
get :hello_world
assert_response :success
assert_template "test/hello_world"
assert_equal "<html>Hello world!</html>", @response.body
end
def test_do_with_render
get :render_hello_world
assert_template "test/hello_world"
end
def test_do_with_render_from_variable
get :render_hello_world_from_variable
assert_equal "hello david", @response.body
end
def test_do_with_render_action
get :render_action_hello_world
assert_template "test/hello_world"
end
def test_do_with_render_text
get :render_text_hello_world
assert_equal "hello world", @response.body
end
def test_do_with_render_text_and_layout
get :render_text_hello_world_with_layout
assert_equal "<html>hello world, I'm here!</html>", @response.body
end
def test_do_with_render_custom_code
get :render_custom_code
assert_response :missing
end
def test_render_file_with_instance_variables
get :render_file_with_instance_variables
assert_equal "The secret is in the sauce\n", @response.body
end
def test_render_file_not_using_full_path
get :render_file_not_using_full_path
assert_equal "The secret is in the sauce\n", @response.body
end
def test_render_file_with_locals
get :render_file_with_locals
assert_equal "The secret is in the sauce\n", @response.body
end
def test_attempt_to_access_object_method
assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
end
def test_private_methods
assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
end
def test_access_to_request_in_view
view_internals_old_value = ActionController::Base.view_controller_internals
ActionController::Base.view_controller_internals = false
ActionController::Base.protected_variables_cache = nil
get :hello_world
assert_nil(assigns["request"])
ActionController::Base.view_controller_internals = true
ActionController::Base.protected_variables_cache = nil
get :hello_world
assert_kind_of ActionController::AbstractRequest, assigns["request"]
ActionController::Base.view_controller_internals = view_internals_old_value
ActionController::Base.protected_variables_cache = nil
end
def test_render_xml
get :render_xml_hello
assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
end
def test_render_xml_with_default
get :greeting
assert_equal "<p>This is grand!</p>\n", @response.body
end
def test_render_rjs_with_default
get :delete_with_js
assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight('project-4',{});!, @response.body
end
def test_render_rjs_template_explicitly
get :render_js_with_explicit_template
assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight('project-4',{});!, @response.body
end
def test_rendering_rjs_action_explicitly
get :render_js_with_explicit_action_template
assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight('project-4',{});!, @response.body
end
def test_layout_rendering
get :layout_test
assert_equal "<html>Hello world!</html>", @response.body
end
def test_layout_test_with_different_layout
get :layout_test_with_different_layout
assert_equal "<html>Hello world!</html>", @response.body
end
def test_rendering_without_layout
get :rendering_without_layout
assert_equal "Hello world!", @response.body
end
def test_layout_overriding_layout
get :layout_overriding_layout
assert_no_match %r{<title>}, @response.body
end
def test_rendering_nothing_on_layout
get :rendering_nothing_on_layout
assert_equal " ", @response.body
end
def test_render_xml_with_layouts
get :builder_layout_test
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
end
def test_partial_only
get :partial_only
assert_equal "only partial", @response.body
end
def test_partial_only_with_layout
get :partial_only_with_layout
assert_equal "<html>only partial</html>", @response.body
end
def test_render_to_string
get :hello_in_a_string
assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
end
def test_nested_rendering
get :hello_world
assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body
end
def test_accessing_params_in_template
get :accessing_params_in_template, :name => "David"
assert_equal "Hello: David", @response.body
end
def test_accessing_params_in_template_with_layout
get :accessing_params_in_template_with_layout, :name => "David"
assert_equal "<html>Hello: David</html>", @response.body
end
def test_render_with_explicit_template
get :render_with_explicit_template
assert_response :success
end
def test_double_render
assert_raises(ActionController::DoubleRenderError) { get :double_render }
end
def test_double_redirect
assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
end
def test_render_and_redirect
assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
end
def test_rendering_with_conflicting_local_vars
get :rendering_with_conflicting_local_vars
assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
end
def test_action_talk_to_layout
get :action_talk_to_layout
assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
end
def test_partials_list
get :partials_list
assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
end
def test_partial_with_locals
get :partial_with_locals
assert_equal "Hello: david", @response.body
end
def test_partial_collection
get :partial_collection
assert_equal "Hello: davidHello: mary", @response.body
end
def test_partial_collection_with_locals
get :partial_collection_with_locals
assert_equal "Bonjour: davidBonjour: mary", @response.body
end
def test_empty_partial_collection
get :empty_partial_collection
assert_equal " ", @response.body
end
def test_partial_with_hash_object
get :partial_with_hash_object
assert_equal "Sam", @response.body
end
def test_partial_with_implicit_local_assignment
get :partial_with_implicit_local_assignment
assert_equal "Hello: Marcel", @response.body
end
def test_render_text_with_assigns
get :render_text_with_assigns
assert_equal "world", assigns["hello"]
end
def test_yield_content_for
get :yield_content_for
assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
end
end

View file

@ -0,0 +1 @@
Living in a nested world

View file

@ -0,0 +1,5 @@
module AbcHelper
def bare_a() end
def bare_b() end
def bare_c() end
end

View file

@ -0,0 +1,3 @@
module Fun::GamesHelper
def stratego() "Iz guuut!" end
end

View file

@ -0,0 +1,3 @@
module Fun::PDFHelper
def foobar() 'baz' end
end

View file

@ -0,0 +1,3 @@
xml.wrapper do
xml << @content_for_layout
end

View file

@ -0,0 +1 @@
<html><%= @content_for_layout %><%= @variable_for_layout %></html>

View file

@ -0,0 +1,2 @@
<title><%= @title || @content_for_title %></title>
<%= @content_for_layout -%>

View file

@ -0,0 +1,2 @@
<title><%= yield :title %></title>
<%= yield %>

View file

@ -0,0 +1 @@
Hello: <%= customer.name %>

View file

@ -0,0 +1 @@
<%= greeting %>: <%= customer_greeting.name %>

View file

@ -0,0 +1 @@
<%= hash_object[:first_name] %>

View file

@ -0,0 +1 @@
only partial

View file

@ -0,0 +1,2 @@
Second: <%= name %>
Third: <%= @name %>

View file

@ -0,0 +1,2 @@
<% @title = "Talking to the layout" -%>
Action was here!

View file

@ -0,0 +1,4 @@
<% days = capture do %>
Dreamy days
<% end %>
<%= days %>

View file

@ -0,0 +1,2 @@
<% content_for :title do %>Putting stuff in the title!<% end %>
Great stuff!

View file

@ -0,0 +1,2 @@
page.remove 'person'
page.visual_effect :highlight, "project-#{@project_id}"

View file

@ -0,0 +1 @@
<p>This is grand!</p>

View file

@ -0,0 +1,4 @@
xml.html do
xml.p "Hello #{@name}"
xml << render_file("test/greeting")
end

View file

@ -0,0 +1 @@
Hello world!

View file

@ -0,0 +1,11 @@
xml.html do
xml.head do
xml.title "Hello World"
end
xml.body do
xml.p "abes"
xml.p "monks"
xml.p "wiseguys"
end
end

View file

@ -0,0 +1 @@
<%= @test_unchanged = 'goodbye' %><%= render_collection_of_partials "customer", @customers %><%= @test_unchanged %>

View file

@ -0,0 +1,4 @@
First: <%= @name %>
<%= render :partial => "person", :locals => { :name => "Stephan" } -%>
Fourth: <%= @name %>
Fifth: <%= name %>

View file

@ -0,0 +1 @@
The secret is <%= @secret %>

View file

@ -0,0 +1 @@
The secret is <%= secret %>

View file

@ -0,0 +1 @@
The value of foo is: ::<%= @foo %>::

View file

@ -0,0 +1,9 @@
<% replacement_function = update_element_function("products", :action => :update) do %>
<p>Product 1</p>
<p>Product 2</p>
<% end %>
<%= javascript_tag(replacement_function) %>
<% update_element_function("status", :action => :update, :binding => binding) do %>
<b>You bought something!</b>
<% end %>

View file

@ -0,0 +1,279 @@
require File.dirname(__FILE__) + '/../abstract_unit'
module BaseTest
include ActionView::Helpers::JavaScriptHelper
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::FormHelper
include ActionView::Helpers::CaptureHelper
def setup
@controller = Class.new do
def url_for(options, *parameters_for_method_reference)
url = "http://www.example.com/"
url << options[:action].to_s if options and options[:action]
url
end
end.new
end
protected
def create_generator
block = Proc.new { |*args| yield *args if block_given? }
JavaScriptGenerator.new self, &block
end
end
class JavaScriptHelperTest < Test::Unit::TestCase
include BaseTest
def test_define_javascript_functions
# check if prototype.js is included first
assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
# check that scriptaculous.js is not in here, only needed if loaded remotely
assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
end
def test_escape_javascript
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
end
def test_link_to_function
assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')")
end
def test_link_to_remote
assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outpost</a>),
link_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
link_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
link_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
link_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" })
end
def test_periodically_call_remote
assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
end
def test_form_remote_tag
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast })
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast })
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast })
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast })
end
def test_on_callbacks
callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
callbacks.each do |callback|
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
end
#HTTP status codes 200 up to 599 have callbacks
#these should work
100.upto(599) do |callback|
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
#test 200 and 404
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
#these shouldn't
1.upto(99) do |callback|
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
600.upto(999) do |callback|
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
#test ultimate combo
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
end
def test_submit_to_remote
assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />),
submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
end
def test_observe_field
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
end
def test_observe_form
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
end
def test_effect
assert_equal "new Effect.Highlight('posts',{});", visual_effect(:highlight, "posts")
assert_equal "new Effect.Highlight('posts',{});", visual_effect("highlight", :posts)
assert_equal "new Effect.Highlight('posts',{});", visual_effect(:highlight, :posts)
assert_equal "new Effect.Fade('fademe',{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
assert_equal "new Effect.DropOut('dropme',{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
end
def test_sortable_element
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}})\n//]]>\n</script>),
sortable_element("mylist", :url => { :action => "order" })
assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}, tag:'div'})\n//]]>\n</script>),
sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}})\n//]]>\n</script>|,
sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}})\n//]]>\n</script>),
sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
end
def test_draggable_element
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable('product_13', {})\n//]]>\n</script>),
draggable_element('product_13')
assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable('product_13', {revert:true})\n//]]>\n</script>),
draggable_element('product_13', :revert => true)
end
def test_drop_receiving_element
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
drop_receiving_element('droptarget1')
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
drop_receiving_element('droptarget1', :accept => 'products')
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
drop_receiving_element('droptarget1', :accept => 'products', :update => 'infobox')
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
drop_receiving_element('droptarget1', :accept => ['tshirts','mugs'], :update => 'infobox')
end
def test_update_element_function
assert_equal %($('myelement').innerHTML = 'blub';\n),
update_element_function('myelement', :content => 'blub')
assert_equal %($('myelement').innerHTML = 'blub';\n),
update_element_function('myelement', :action => :update, :content => 'blub')
assert_equal %($('myelement').innerHTML = '';\n),
update_element_function('myelement', :action => :empty)
assert_equal %(Element.remove('myelement');\n),
update_element_function('myelement', :action => :remove)
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
update_element_function('myelement', :position => 'bottom', :content => 'blub')
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
_erbout = ""
assert_equal %($('myelement').innerHTML = 'test';\n),
update_element_function('myelement') { _erbout << "test" }
_erbout = ""
assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
end
def test_update_page
block = Proc.new { |page| page.replace_html('foo', 'bar') }
assert_equal create_generator(&block).to_s, update_page(&block)
end
def test_update_page_tag
block = Proc.new { |page| page.replace_html('foo', 'bar') }
assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
end
end
class JavaScriptGeneratorTest < Test::Unit::TestCase
include BaseTest
def setup
super
@generator = create_generator
end
def test_insert_html_with_string
assert_equal 'new Insertion.Top("element", "<p>This is a test</p>");',
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.Bottom("element", "<p>This is a test</p>");',
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.Before("element", "<p>This is a test</p>");',
@generator.insert_html(:before, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.After("element", "<p>This is a test</p>");',
@generator.insert_html(:after, 'element', '<p>This is a test</p>')
end
def test_replace_html_with_string
assert_equal 'Element.update("element", "<p>This is a test</p>");',
@generator.replace_html('element', '<p>This is a test</p>')
end
def test_remove
assert_equal '["foo"].each(Element.remove);',
@generator.remove('foo')
assert_equal '["foo", "bar", "baz"].each(Element.remove);',
@generator.remove('foo', 'bar', 'baz')
end
def test_show
assert_equal 'Element.show("foo");',
@generator.show('foo')
assert_equal 'Element.show("foo", "bar", "baz");',
@generator.show('foo', 'bar', 'baz')
end
def test_hide
assert_equal 'Element.hide("foo");',
@generator.hide('foo')
assert_equal 'Element.hide("foo", "bar", "baz");',
@generator.hide('foo', 'bar', 'baz')
end
def test_alert
assert_equal 'alert("hello");', @generator.alert('hello')
end
def test_redirect_to
assert_equal 'window.location.href = "http://www.example.com/welcome";',
@generator.redirect_to(:action => 'welcome')
end
def test_delay
@generator.delay(20) do
@generator.hide('foo')
end
assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
end
def test_to_s
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
@generator.remove('foo', 'bar')
@generator.replace_html('baz', '<p>This is a test</p>')
assert_equal <<-EOS.chomp, @generator.to_s
new Insertion.Top("element", "<p>This is a test</p>");
new Insertion.Bottom("element", "<p>This is a test</p>");
["foo", "bar"].each(Element.remove);
Element.update("baz", "<p>This is a test</p>");
EOS
end
end