Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Allow inline editing of user login and email by admin

  • Loading branch information...
commit 57b28f21a487debfafba0d1af1567e66ea5fa4db 1 parent a40b729
Ariejan de Vroom authored
4  app/controllers/admin/users_controller.rb
@@ -2,6 +2,10 @@ class Admin::UsersController < ApplicationController
2 2
   require_role :admin
3 3
   layout 'admin'
4 4
   
  5
+  %w(email login).each do |attr|
  6
+    in_place_edit_for :user, attr.to_sym
  7
+  end
  8
+  
5 9
   def reset_password
6 10
     @user = User.find(params[:id])
7 11
     @user.reset_password!
4  app/views/admin/users/show.html.erb
@@ -32,12 +32,12 @@
32 32
 <% end -%>
33 33
 
34 34
 <table class="overview">
35  
-	<%= cell "Login", h(@user.login) %>
  35
+	<%= cell "Login", "#{in_place_editor_field :user, 'login'}" %>
36 36
 	<%= cell "User ID", h(@user.id) %>
37 37
 
38 38
 	<%= cell_separator %>
39 39
 	
40  
-	<%= cell "Email", h(@user.email) %>
  40
+	<%= cell "Email", "#{in_place_editor_field :user, 'email'}" %>
41 41
 	<%= cell "Password", "#{"*"*8} <small>#{link_to_if !@user.deleted?, "send new password", reset_password_admin_user_url(@user), :method => :put}</small>" %>
42 42
 	
43 43
 	<%= cell_separator %>
14  vendor/plugins/in_place_editing/README
... ...
@@ -0,0 +1,14 @@
  1
+InPlaceEditing
  2
+==============
  3
+
  4
+Example:
  5
+
  6
+  # Controller
  7
+  class BlogController < ApplicationController
  8
+    in_place_edit_for :post, :title
  9
+  end
  10
+
  11
+  # View
  12
+  <%= in_place_editor_field :post, 'title' %>
  13
+
  14
+Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license  
22  vendor/plugins/in_place_editing/Rakefile
... ...
@@ -0,0 +1,22 @@
  1
+require 'rake'
  2
+require 'rake/testtask'
  3
+require 'rake/rdoctask'
  4
+
  5
+desc 'Default: run unit tests.'
  6
+task :default => :test
  7
+
  8
+desc 'Test in_place_editing plugin.'
  9
+Rake::TestTask.new(:test) do |t|
  10
+  t.libs << 'lib'
  11
+  t.pattern = 'test/**/*_test.rb'
  12
+  t.verbose = true
  13
+end
  14
+
  15
+desc 'Generate documentation for in_place_editing plugin.'
  16
+Rake::RDocTask.new(:rdoc) do |rdoc|
  17
+  rdoc.rdoc_dir = 'rdoc'
  18
+  rdoc.title    = 'InPlaceEditing'
  19
+  rdoc.options << '--line-numbers' << '--inline-source'
  20
+  rdoc.rdoc_files.include('README')
  21
+  rdoc.rdoc_files.include('lib/**/*.rb')
  22
+end
2  vendor/plugins/in_place_editing/init.rb
... ...
@@ -0,0 +1,2 @@
  1
+ActionController::Base.send :include, InPlaceEditing
  2
+ActionController::Base.helper InPlaceMacrosHelper
25  vendor/plugins/in_place_editing/lib/in_place_editing.rb
... ...
@@ -0,0 +1,25 @@
  1
+module InPlaceEditing
  2
+  def self.included(base)
  3
+    base.extend(ClassMethods)
  4
+  end
  5
+
  6
+  # Example:
  7
+  #
  8
+  #   # Controller
  9
+  #   class BlogController < ApplicationController
  10
+  #     in_place_edit_for :post, :title
  11
+  #   end
  12
+  #
  13
+  #   # View
  14
+  #   <%= in_place_editor_field :post, 'title' %>
  15
+  #
  16
+  module ClassMethods
  17
+    def in_place_edit_for(object, attribute, options = {})
  18
+      define_method("set_#{object}_#{attribute}") do
  19
+        @item = object.to_s.camelize.constantize.find(params[:id])
  20
+        @item.update_attribute(attribute, params[:value])
  21
+        render :text => @item.send(attribute).to_s
  22
+      end
  23
+    end
  24
+  end
  25
+end
78  vendor/plugins/in_place_editing/lib/in_place_macros_helper.rb
... ...
@@ -0,0 +1,78 @@
  1
+module InPlaceMacrosHelper
  2
+  # Makes an HTML element specified by the DOM ID +field_id+ become an in-place
  3
+  # editor of a property.
  4
+  #
  5
+  # A form is automatically created and displayed when the user clicks the element,
  6
+  # something like this:
  7
+  #   <form id="myElement-in-place-edit-form" target="specified url">
  8
+  #     <input name="value" text="The content of myElement"/>
  9
+  #     <input type="submit" value="ok"/>
  10
+  #     <a onclick="javascript to cancel the editing">cancel</a>
  11
+  #   </form>
  12
+  # 
  13
+  # The form is serialized and sent to the server using an AJAX call, the action on
  14
+  # the server should process the value and return the updated value in the body of
  15
+  # the reponse. The element will automatically be updated with the changed value
  16
+  # (as returned from the server).
  17
+  # 
  18
+  # Required +options+ are:
  19
+  # <tt>:url</tt>::       Specifies the url where the updated value should
  20
+  #                       be sent after the user presses "ok".
  21
+  # 
  22
+  # Addtional +options+ are:
  23
+  # <tt>:rows</tt>::              Number of rows (more than 1 will use a TEXTAREA)
  24
+  # <tt>:cols</tt>::              Number of characters the text input should span (works for both INPUT and TEXTAREA)
  25
+  # <tt>:size</tt>::              Synonym for :cols when using a single line text input.
  26
+  # <tt>:cancel_text</tt>::       The text on the cancel link. (default: "cancel")
  27
+  # <tt>:save_text</tt>::         The text on the save link. (default: "ok")
  28
+  # <tt>:loading_text</tt>::      The text to display while the data is being loaded from the server (default: "Loading...")
  29
+  # <tt>:saving_text</tt>::       The text to display when submitting to the server (default: "Saving...")
  30
+  # <tt>:external_control</tt>::  The id of an external control used to enter edit mode.
  31
+  # <tt>:load_text_url</tt>::     URL where initial value of editor (content) is retrieved.
  32
+  # <tt>:options</tt>::           Pass through options to the AJAX call (see prototype's Ajax.Updater)
  33
+  # <tt>:with</tt>::              JavaScript snippet that should return what is to be sent
  34
+  #                               in the AJAX call, +form+ is an implicit parameter
  35
+  # <tt>:script</tt>::            Instructs the in-place editor to evaluate the remote JavaScript response (default: false)
  36
+  # <tt>:click_to_edit_text</tt>::The text shown during mouseover the editable text (default: "Click to edit")
  37
+  def in_place_editor(field_id, options = {})
  38
+    function =  "new Ajax.InPlaceEditor("
  39
+    function << "'#{field_id}', "
  40
+    function << "'#{url_for(options[:url])}'"
  41
+
  42
+    js_options = {}
  43
+
  44
+    if protect_against_forgery?
  45
+      options[:with] ||= "Form.serialize(form)"
  46
+      options[:with] += " + '&authenticity_token=' + encodeURIComponent('#{form_authenticity_token}')"
  47
+    end
  48
+
  49
+    js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text]
  50
+    js_options['okText'] = %('#{options[:save_text]}') if options[:save_text]
  51
+    js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text]
  52
+    js_options['savingText'] = %('#{options[:saving_text]}') if options[:saving_text]
  53
+    js_options['rows'] = options[:rows] if options[:rows]
  54
+    js_options['cols'] = options[:cols] if options[:cols]
  55
+    js_options['size'] = options[:size] if options[:size]
  56
+    js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control]
  57
+    js_options['loadTextURL'] = "'#{url_for(options[:load_text_url])}'" if options[:load_text_url]        
  58
+    js_options['ajaxOptions'] = options[:options] if options[:options]
  59
+    js_options['htmlResponse'] = !options[:script] if options[:script]
  60
+    js_options['callback']   = "function(form) { return #{options[:with]} }" if options[:with]
  61
+    js_options['clickToEditText'] = %('#{options[:click_to_edit_text]}') if options[:click_to_edit_text]
  62
+    js_options['textBetweenControls'] = %('#{options[:text_between_controls]}') if options[:text_between_controls]
  63
+    function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
  64
+    
  65
+    function << ')'
  66
+
  67
+    javascript_tag(function)
  68
+  end
  69
+  
  70
+  # Renders the value of the specified object and method with in-place editing capabilities.
  71
+  def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {})
  72
+    tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
  73
+    tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
  74
+    in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
  75
+    tag.to_content_tag(tag_options.delete(:tag), tag_options) +
  76
+    in_place_editor(tag_options[:id], in_place_editor_options)
  77
+  end
  78
+end
89  vendor/plugins/in_place_editing/test/in_place_editing_test.rb
... ...
@@ -0,0 +1,89 @@
  1
+require File.expand_path(File.dirname(__FILE__) + "/test_helper")
  2
+
  3
+class InPlaceEditingTest < Test::Unit::TestCase
  4
+  include InPlaceEditing
  5
+  include InPlaceMacrosHelper
  6
+  
  7
+  include ActionView::Helpers::UrlHelper
  8
+  include ActionView::Helpers::TagHelper
  9
+  include ActionView::Helpers::TextHelper
  10
+  include ActionView::Helpers::FormHelper
  11
+  include ActionView::Helpers::CaptureHelper
  12
+  
  13
+  def setup
  14
+    @controller = Class.new do
  15
+      def url_for(options)
  16
+        url =  "http://www.example.com/"
  17
+        url << options[:action].to_s if options and options[:action]
  18
+        url
  19
+      end
  20
+    end
  21
+    @controller = @controller.new
  22
+    @protect_against_forgery = false
  23
+  end
  24
+
  25
+  def protect_against_forgery?
  26
+    @protect_against_forgery
  27
+  end
  28
+
  29
+  def test_in_place_editor_external_control
  30
+      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {externalControl:'blah'})\n//]]>\n</script>),
  31
+        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :external_control => 'blah'})
  32
+  end
  33
+  
  34
+  def test_in_place_editor_size
  35
+      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {size:4})\n//]]>\n</script>),
  36
+        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :size => 4})
  37
+  end
  38
+  
  39
+  def test_in_place_editor_cols_no_rows
  40
+      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {cols:4})\n//]]>\n</script>),
  41
+        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :cols => 4})
  42
+  end
  43
+  
  44
+  def test_in_place_editor_cols_with_rows
  45
+      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {cols:40, rows:5})\n//]]>\n</script>),
  46
+        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :rows => 5, :cols => 40})
  47
+  end
  48
+
  49
+  def test_inplace_editor_loading_text
  50
+      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {loadingText:'Why are we waiting?'})\n//]]>\n</script>),
  51
+        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :loading_text => 'Why are we waiting?'})
  52
+  end
  53
+  
  54
+  def test_in_place_editor_url
  55
+    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value')",
  56
+    in_place_editor( 'id-goes-here', :url => { :action => "action_to_set_value" })    
  57
+  end
  58
+  
  59
+  def test_in_place_editor_load_text_url
  60
+    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {loadTextURL:'http://www.example.com/action_to_get_value'})",
  61
+    in_place_editor( 'id-goes-here', 
  62
+      :url => { :action => "action_to_set_value" }, 
  63
+      :load_text_url => { :action => "action_to_get_value" })
  64
+  end
  65
+  
  66
+  def test_in_place_editor_html_response
  67
+    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {htmlResponse:false})",
  68
+    in_place_editor( 'id-goes-here', 
  69
+      :url => { :action => "action_to_set_value" }, 
  70
+      :script => true )
  71
+  end
  72
+  
  73
+  def form_authenticity_token
  74
+    "authenticity token"
  75
+  end
  76
+
  77
+  def test_in_place_editor_with_forgery_protection
  78
+    @protect_against_forgery = true
  79
+    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {callback:function(form) { return Form.serialize(form) + '&authenticity_token=' + encodeURIComponent('authenticity token') }})",
  80
+    in_place_editor( 'id-goes-here', :url => { :action => "action_to_set_value" })
  81
+  end
  82
+
  83
+  def test_in_place_editor_text_between_controls
  84
+    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {textBetweenControls:'or'})",
  85
+    in_place_editor( 'id-goes-here',
  86
+      :url => { :action => "action_to_set_value" },
  87
+      :text_between_controls => "or" )
  88
+  end
  89
+end
8  vendor/plugins/in_place_editing/test/test_helper.rb
... ...
@@ -0,0 +1,8 @@
  1
+$:.unshift(File.dirname(__FILE__) + '/../lib')
  2
+
  3
+require 'test/unit'
  4
+require 'rubygems'
  5
+require 'action_controller'
  6
+require 'action_controller/assertions'
  7
+require 'in_place_editing'
  8
+require 'in_place_macros_helper'

0 notes on commit 57b28f2

Please sign in to comment.
Something went wrong with that request. Please try again.