├── init.rb
├── Rakefile
├── lib
├── feedback
│ └── version.rb
├── feedback.rb
└── generators
│ └── feedback_form
│ ├── templates
│ ├── images
│ │ ├── loading.gif
│ │ ├── closelabel.gif
│ │ ├── feedback_tab.png
│ │ └── feedback_tab_h.png
│ ├── feedback_mailer_test.rb.erb
│ ├── feedback_mailer.rb.erb
│ ├── views
│ │ ├── feedback_mailer
│ │ │ └── feedback.html.erb
│ │ └── feedbacks
│ │ │ └── new.html.erb
│ ├── feedback_model.rb.erb
│ ├── feedback_test.rb.erb
│ ├── feedbacks_helper.rb.jquery.erb
│ ├── feedbacks_helper.rb.prototype.erb
│ ├── feedbacks_controller.rb.erb
│ ├── feedbacks_controller_test.rb.erb
│ ├── feedback.css.scss
│ ├── jquery.feedback.js.coffee
│ └── prototype.feedback.js.coffee
│ ├── USAGE
│ └── feedback_form_generator.rb
├── Gemfile
├── generators
└── feedback_form
│ ├── templates
│ ├── images
│ │ ├── loading.gif
│ │ ├── closelabel.gif
│ │ ├── feedback_tab.png
│ │ └── feedback_tab_h.png
│ ├── feedback_mailer_test.rb.erb
│ ├── views
│ │ ├── feedback_mailer
│ │ │ └── feedback.html.erb
│ │ └── feedbacks
│ │ │ └── new.html.erb
│ ├── feedback_mailer.rb.erb
│ ├── feedback_model.rb.erb
│ ├── feedback_test.rb.erb
│ ├── feedbacks_helper.rb.prototype.erb
│ ├── feedbacks_helper.rb.jquery.erb
│ ├── feedbacks_controller.rb.erb
│ ├── feedbacks_controller_test.rb.erb
│ ├── feedback.css
│ ├── jquery.feedback.js
│ └── prototype.feedback.js
│ ├── USAGE
│ ├── lib
│ └── insert_routes.rb
│ └── feedback_form_generator.rb
├── .gitignore
├── feedback.gemspec
├── MIT-LICENSE
└── README.md
/init.rb:
--------------------------------------------------------------------------------
1 | # Include hook code here
2 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
--------------------------------------------------------------------------------
/lib/feedback/version.rb:
--------------------------------------------------------------------------------
1 | module Feedback
2 | VERSION = "0.0.2"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/feedback.rb:
--------------------------------------------------------------------------------
1 | require "feedback/version"
2 |
3 | module Feedback
4 | # Your code goes here...
5 | end
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in feedback.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/generators/feedback_form/templates/images/loading.gif
--------------------------------------------------------------------------------
/generators/feedback_form/templates/images/closelabel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/generators/feedback_form/templates/images/closelabel.gif
--------------------------------------------------------------------------------
/generators/feedback_form/templates/images/feedback_tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/generators/feedback_form/templates/images/feedback_tab.png
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/lib/generators/feedback_form/templates/images/loading.gif
--------------------------------------------------------------------------------
/generators/feedback_form/templates/images/feedback_tab_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/generators/feedback_form/templates/images/feedback_tab_h.png
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/images/closelabel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/lib/generators/feedback_form/templates/images/closelabel.gif
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/images/feedback_tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/lib/generators/feedback_form/templates/images/feedback_tab.png
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/images/feedback_tab_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsboulanger/feedback/HEAD/lib/generators/feedback_form/templates/images/feedback_tab_h.png
--------------------------------------------------------------------------------
/generators/feedback_form/USAGE:
--------------------------------------------------------------------------------
1 | ./script/generate feedback_form [NAME]
2 |
3 | The [NAME] is optional and will be used to
4 | name all classes and files, the default is: feedback
5 | It should be singular.
--------------------------------------------------------------------------------
/lib/generators/feedback_form/USAGE:
--------------------------------------------------------------------------------
1 | rails generate feedback_form [NAME]
2 |
3 | The [NAME] is optional and will be used to
4 | name all classes and files, the default is: feedback
5 | It should be singular.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedback_mailer_test.rb.erb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 |
4 | class <%= mailer_class_name %>Test < ActionMailer::TestCase
5 | # replace this with your real tests
6 | test "the truth" do
7 | assert true
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedback_mailer_test.rb.erb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 |
4 | class <%= mailer_class_name %>Test < ActionMailer::TestCase
5 | # replace this with your real tests
6 | test "the truth" do
7 | assert true
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedback_mailer.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= mailer_class_name %> < ActionMailer::Base
2 | default :from => 'noreply@yoursite.com'
3 |
4 | def feedback(feedback)
5 | @feedback = feedback
6 | mail(:to => 'webmaster@yoursite.com', :subject => '[Feedback for YourSite] #{feedback.subject}')
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/views/feedback_mailer/feedback.html.erb:
--------------------------------------------------------------------------------
1 | Dear site administrator,
2 |
3 | You're lucky today, you've got feedback from your users:
4 |
5 | Subject: <%=h @feedback.subject %>
6 | User's email: <%=h @feedback.email %>
7 | Sent at: <%= Time.now %>
8 | From Page: <%= @feedback.page %>
9 |
10 | Comment:
11 | <%=h @feedback.comment %>
12 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/views/feedback_mailer/feedback.html.erb:
--------------------------------------------------------------------------------
1 | Dear site administrator,
2 |
3 | You're lucky today, you've got feedback from your users:
4 |
5 | Subject: <%=h @feedback.subject %>
6 | User's email: <%=h @feedback.email %>
7 | Sent at: <%= Time.now %>
8 | From Page: <%= @feedback.page %>
9 |
10 | Comment:
11 | <%=h @feedback.comment %>
12 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedback_mailer.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= mailer_class_name %> < ActionMailer::Base
2 |
3 | def feedback(feedback)
4 | @recipients = 'webmaster@yoursite.com'
5 | @from = 'noreply@yoursite.com'
6 | @subject = "[Feedback for YourSite] #{feedback.subject}"
7 | @sent_on = Time.now
8 | @body[:feedback] = feedback
9 | end
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedback_model.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= model_class_name %>
2 | attr_accessor :subject, :email, :comment, :page
3 |
4 | def initialize(params = {})
5 | self.subject = params[:subject]
6 | self.email = params[:email]
7 | self.comment = params[:comment]
8 | self.page = params[:page]
9 | end
10 |
11 | def valid?
12 | self.comment && !self.comment.strip.blank?
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedback_model.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= model_class_name %>
2 | attr_accessor :subject, :email, :comment, :page
3 |
4 | def initialize(params = {})
5 | self.subject = params[:subject]
6 | self.email = params[:email]
7 | self.comment = params[:comment]
8 | self.page = params[:page]
9 | end
10 |
11 | def valid?
12 | self.comment && !self.comment.strip.blank?
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedback_test.rb.erb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 | class <%= model_class_name %>Test < Test::Unit::TestCase
4 | def test_should_require_comment
5 | assert !create_comment(:comment => " ").valid?
6 | end
7 |
8 | def test_should_be_valid
9 | assert create_comment.valid?
10 | end
11 |
12 | protected
13 | def create_comment(params = {})
14 | valid_feedback = {
15 | :subject => "Test",
16 | :email => "test@yoursite.com",
17 | :comment => "i like the site"
18 | }
19 | <%= model_class_name %>.new(valid_feedback.merge(params))
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedback_test.rb.erb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 | class <%= model_class_name %>Test < Test::Unit::TestCase
4 |
5 |
6 | def test_should_require_comment
7 | assert !create_comment(:comment => " ").valid?
8 | end
9 |
10 | def test_should_be_valid
11 | assert create_comment.valid?
12 | end
13 |
14 | protected
15 |
16 | def create_comment(params = {})
17 | valid_feedback = {
18 | :subject => "Test",
19 | :email => "test@yoursite.com",
20 | :comment => "i like the site"
21 | }
22 | <%= model_class_name %>.new(valid_feedback.merge(params))
23 | end
24 |
25 | end
--------------------------------------------------------------------------------
/generators/feedback_form/templates/views/feedbacks/new.html.erb:
--------------------------------------------------------------------------------
1 |
Feedback
2 | Please leave us feedback, it's really appreciated.
3 | <% form_for :feedback, @feedback, :url => feedback_path, :html => {:id => "feedback_form"} do |f| -%>
4 | <%= f.hidden_field 'page' %>
5 | <% unless @error_message.blank? %>
6 |
7 | <%=h @error_message %>
8 |
9 | <% end %>
10 |
11 | <%= f.label 'subject' %>
12 | <%= f.select 'subject', ['Problem', 'Suggestion', 'Question', 'Other'] %>
13 |
14 |
15 | <%= f.label 'email' %>
16 | <%= f.text_field 'email' %>
17 |
18 |
19 | <%= f.label 'comment' %>
20 | <%= f.text_area 'comment', :rows => 10, :cols => 30 %>
21 |
22 | <%= f.submit 'Send' %>
23 |
24 | <% end -%>
25 |
26 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedbacks_helper.rb.jquery.erb:
--------------------------------------------------------------------------------
1 | module <%= helper_class_name %>
2 |
3 | def feedback_tab(options = {})
4 | feedback_init({'position' => 'top'}.merge(options.stringify_keys))
5 | end
6 |
7 | def feedback_init(options = {})
8 | options = {
9 | "position" => "null"
10 | }.merge(options.stringify_keys)
11 |
12 | options['position'] = "'#{options['position']}'" unless options['position'].blank? || options['position'] == 'null'
13 | content_tag 'script', :type => "text/javascript" do
14 | "$(document).ready(function() { $('.feedback_link').feedback({tabPosition: #{options["position"]}}); });".html_safe
15 | end
16 | end
17 |
18 | def feedback_link(text, options = {})
19 | link_to text, '#', :class => "feedback_link"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedbacks_helper.rb.prototype.erb:
--------------------------------------------------------------------------------
1 | module <%= helper_class_name %>
2 |
3 | def feedback_tab(options = {})
4 | feedback_init({'position' => 'top'}.merge(options.stringify_keys))
5 | end
6 |
7 | def feedback_init(options = {})
8 | options = {
9 | "position" => "null"
10 | }.merge(options.stringify_keys)
11 |
12 | options['position'] = "'#{options['position']}'" unless options['position'].blank? || options['position'] == 'null'
13 | content_tag 'script', :type => "text/javascript" do
14 | "document.observe(\"dom:loaded\", function() { Feedback.init({tabPosition: #{options["position"]}}); });".html_safe
15 | end
16 | end
17 |
18 | def feedback_link(text, options = {})
19 | link_to text, '#', :class => "feedback_link"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/views/feedbacks/new.html.erb:
--------------------------------------------------------------------------------
1 | Feedback
2 |
3 | Please leave us feedback, it's really appreciated.
4 |
5 | <%= form_for @feedback, :as => :feedback, :url => feedback_path, :html => {:id => "feedback_form"} do |f| %>
6 | <%= f.hidden_field 'page' %>
7 | <% unless @error_message.blank? %>
8 |
9 | <%=h @error_message %>
10 |
11 | <% end %>
12 |
13 |
14 | <%= f.label 'subject' %>
15 | <%= f.select 'subject', ['Problem', 'Suggestion', 'Question', 'Other'] %>
16 |
17 |
18 | <%= f.label 'email' %>
19 | <%= f.text_field 'email' %>
20 |
21 |
22 | <%= f.label 'comment' %>
23 | <%= f.text_area 'comment', :rows => 10, :cols => 30 %>
24 |
25 | <%= f.submit 'Send' %>
26 | <% end %>
27 |
--------------------------------------------------------------------------------
/feedback.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'feedback/version'
5 |
6 | Gem::Specification.new do |gem|
7 | gem.name = "feedback"
8 | gem.version = Feedback::VERSION
9 | gem.authors = ["Peter Wong"]
10 | gem.email = ["dohkoos@gmail.com"]
11 | gem.description = %q{feedback plugin for web frameworks}
12 | gem.summary = %q{feedback provides your app an ajax-based feedback form triggered by a sticky tab.}
13 | gem.homepage = "https://github.com/dohkoos/feedback"
14 |
15 | gem.files = `git ls-files`.split($/)
16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18 | gem.require_paths = ["lib"]
19 | end
20 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedbacks_controller.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= controller_class_name %> < ApplicationController
2 | layout false
3 |
4 | def new
5 | @feedback = <%= model_class_name %>.new(page: request.referer)
6 | end
7 |
8 | def create
9 | @feedback = <%= model_class_name %>.new(params[:feedback])
10 | if @feedback.valid?
11 | <%= mailer_class_name %>.feedback(@feedback).deliver
12 | render :status => :created, :text => 'Thank you for your feedback!
'
13 | else
14 | @error_message = "Please enter your #{@feedback.subject.to_s.downcase}"
15 |
16 | # Returns the whole form back. This is not the most effective
17 | # use of AJAX as we could return the error message in JSON, but
18 | # it makes easier the customization of the form with error messages
19 | # without worrying about the javascript.
20 | render :action => 'new', :status => :unprocessable_entity
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedbacks_helper.rb.prototype.erb:
--------------------------------------------------------------------------------
1 | module <%= helper_class_name %>
2 |
3 | def feedback_tab(options = {})
4 | feedback_init({'position' => 'top'}.merge(options.stringify_keys))
5 | end
6 |
7 | def feedback_init(options = {})
8 | options = {
9 | "position" => "null"
10 | }.merge(options.stringify_keys)
11 |
12 | options['position'] = "'#{options['position']}'" unless options['position'].blank? || options['position'] == 'null'
13 | content_tag 'script', :type => "text/javascript" do
14 | "document.observe(\"dom:loaded\", function() { Feedback.init({tabPosition: #{options["position"]}}); });"
15 | end
16 |
17 | end
18 |
19 | def feedback_includes()
20 | stylesheet_link_tag('feedback') +
21 | javascript_include_tag('prototype.feedback.js')
22 | end
23 |
24 | def feedback_link(text, options = {})
25 | link_to text, '#', :class => "feedback_link"
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedbacks_helper.rb.jquery.erb:
--------------------------------------------------------------------------------
1 | module <%= helper_class_name %>
2 |
3 |
4 |
5 | def feedback_init(options = {})
6 | options = {
7 | "position" => "null"
8 | }.merge(options.stringify_keys)
9 |
10 | options['position'] = "'#{options['position']}'" unless options['position'].blank? || options['position'] == 'null'
11 | content_tag 'script', :type => "text/javascript" do
12 | "$(document).ready(function() { $('.feedback_link').feedback({tabPosition: #{options["position"]}}); });"
13 | end
14 |
15 | end
16 |
17 | def feedback_includes()
18 | stylesheet_link_tag('feedback') +
19 | javascript_include_tag('jquery.feedback.js')
20 | end
21 |
22 | def feedback_tab(options = {})
23 | feedback_init({'position' => 'top'}.merge(options.stringify_keys))
24 | end
25 |
26 |
27 | def feedback_link(text, options = {})
28 | link_to text, '#', :class => "feedback_link"
29 | end
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedbacks_controller.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= controller_class_name %> < ApplicationController
2 | layout false
3 |
4 | def new
5 | @feedback = <%= model_class_name %>.new(page: request.referer)
6 | end
7 |
8 | def create
9 |
10 | @feedback = <%= model_class_name %>.new(params[:feedback])
11 | if @feedback.valid?
12 | <%= mailer_class_name %>.deliver_feedback(@feedback)
13 | render :status => :created, :text => 'Thank you for your feedback!
'
14 | else
15 | @error_message = "Please enter your #{@feedback.subject.to_s.downcase}"
16 |
17 | # Returns the whole form back. This is not the most effective
18 | # use of AJAX as we could return the error message in JSON, but
19 | # it makes easier the customization of the form with error messages
20 | # without worrying about the javascript.
21 | render :action => 'new', :status => :unprocessable_entity
22 | end
23 |
24 |
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Jean-Sebastien Boulanger
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedbacks_controller_test.rb.erb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 | class <%= controller_class_name %>Test < ActionController::TestCase
4 | def setup
5 | @controller = <%= controller_class_name %>.new
6 | @request = ActionController::TestRequest.new
7 | @response = ActionController::TestResponse.new
8 | end
9 |
10 | def test_should_have_minimal_feedback_form
11 | get :new
12 | assert_select "form#feedback_form", true do
13 | assert_select "[action=?]", "/feedbacks"
14 | assert_select "[method=?]", /post/i
15 | assert_select "textarea[name=?]", "feedback[comment]"
16 | end
17 | end
18 |
19 | def test_should_post_create
20 | post :create, :feedback => {:comment => "Great website!"}
21 | assert :success # Doesn't test much
22 | assert_nil @error_message
23 | end
24 |
25 | def test_should_set_error_message_when_not_valid
26 | post :create, :feedback => {:comment => ""}
27 | assert !assigns(:error_message).blank?
28 | end
29 |
30 | protected
31 | def create_feedback(params = {})
32 | valid_feedback = {
33 | :subject => "Test",
34 | :email => "test@yoursite.com",
35 | :comment => "i like the site"
36 | }
37 | <%= model_class_name %>.new(valid_feedback.merge(params))
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/generators/feedback_form/lib/insert_routes.rb:
--------------------------------------------------------------------------------
1 |
2 | # Adapted from restful_authentication plugin
3 | Rails::Generator::Commands::Create.class_eval do
4 | def route_name(name, path, route_options = {})
5 | sentinel = 'ActionController::Routing::Routes.draw do |map|'
6 |
7 | logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
8 | gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
9 | "#{match}\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
10 | end
11 | end
12 | end
13 |
14 | Rails::Generator::Commands::Destroy.class_eval do
15 |
16 | def route_name(name, path, route_options = {})
17 | look_for = "\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
18 | logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
19 | gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
20 | end
21 | end
22 |
23 | Rails::Generator::Commands::List.class_eval do
24 |
25 | def route_name(name, path, options = {})
26 | logger.route "map.#{name} '#{path}', :controller => '{options[:controller]}', :action => '#{options[:action]}'"
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedbacks_controller_test.rb.erb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/../test_helper'
2 |
3 | class <%= controller_class_name %>Test < ActionController::TestCase
4 |
5 | def setup
6 | @controller = <%= controller_class_name %>.new
7 | @request = ActionController::TestRequest.new
8 | @response = ActionController::TestResponse.new
9 | end
10 |
11 |
12 | def test_should_have_minimal_feedback_form
13 | get :new
14 | assert_select "form#feedback_form", true do
15 | assert_select "[action=?]", "/feedbacks"
16 | assert_select "[method=?]", /post/i
17 | assert_select "textarea[name=?]", "feedback[comment]"
18 | end
19 | end
20 |
21 | def test_should_post_create
22 | post :create, :feedback => {:comment => "Great website!"}
23 | assert :success # Doesn't test much
24 | assert_nil @error_message
25 | end
26 |
27 |
28 | def test_should_set_error_message_when_not_valid
29 | post :create, :feedback => {:comment => ""}
30 | assert !assigns(:error_message).blank?
31 | end
32 |
33 |
34 | protected
35 |
36 | def create_feedback(params = {})
37 | valid_feedback = {
38 | :subject => "Test",
39 | :email => "test@yoursite.com",
40 | :comment => "i like the site"
41 | }
42 | <%= model_class_name %>.new(valid_feedback.merge(params))
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/feedback.css.scss:
--------------------------------------------------------------------------------
1 | div#feedback {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | z-index: 100001; }
8 |
9 | a {
10 | feedback_link {
11 | position: fixed;
12 | z-index: 99999;
13 | background-color: #CC0000;
14 | width: 26px;
15 | height: 96px;
16 | overflow: hidden;
17 | background: #c00 image-url('feedback/feedback_tab.png');
18 | _position: absolute;
19 | /*_background-image: image-url('feedback/feedback_tab.png');*/
20 | &:hover {
21 | background-color: #999; }
22 | &.left {
23 | left: 0;
24 | top: 25%;
25 | width: 26px;
26 | height: 96px;
27 | background-image: image-url('feedback/feedback_tab.png');
28 | _background-image: image-url('feedback/feedback_tab.png'); }
29 | &.right {
30 | right: 0;
31 | top: 25%;
32 | width: 26px;
33 | height: 96px;
34 | background-image: image-url('feedback/feedback_tab.png');
35 | _background-image: image-url('feedback/feedback_tab.png'); }
36 | &.top {
37 | right: 10%;
38 | top: 0;
39 | width: 96px;
40 | height: 26px;
41 | background-image: image-url('feedback/feedback_tab_h.png');
42 | _background-image: image-url('feedback/feedback_tab_h.png'); }
43 | &.bottom {
44 | right: 10%;
45 | bottom: 0;
46 | width: 96px;
47 | height: 26px;
48 | background-image: image-url('feedback/feedback_tab_h.png');
49 | _background-image: image-url('feedback/feedback_tab_h.png'); } }
50 | feedback_close_link {
51 | right: 5pt;
52 | top: 3pt;
53 | position: absolute;
54 | height: 22px;
55 | width: 66px;
56 | background-image: image-url('feedback/closelabel.gif');
57 | _background-image: image-url('feedback/closelabel.gif'); } }
58 |
59 | div {
60 | feedback_modal_window {
61 | width: 300px;
62 | margin: 0 auto;
63 | position: relative;
64 | background-color: #FFFFFF; }
65 | feedback_modal_content, feedback_loading {
66 | padding: 12px; } }
67 |
68 | #feedback_overlay {
69 | position: fixed;
70 | top: 0px;
71 | left: 0px;
72 | height: 100%;
73 | width: 100%;
74 | background-color: #000;
75 | opacity: 0.5;
76 | -moz-opacity: 0.5;
77 | filter: alpha(opacity = 50); }
78 |
79 | .feedback_hide {
80 | z-index: -100; }
81 |
82 | .feedback_overlayBG {
83 | background-color: #000;
84 | z-index: 100000; }
85 |
86 | * html #feedback_overlay {
87 | /* ie6 hack */
88 | position: absolute;
89 | height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); }
90 |
91 | form#feedback_form .error {
92 | color: red;
93 | font-weight: bold; }
94 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/feedback_form_generator.rb:
--------------------------------------------------------------------------------
1 | class FeedbackFormGenerator < Rails::Generators::Base
2 | source_root File.expand_path('../templates', __FILE__)
3 |
4 | argument :name, :type => :string, :default => "feedback"
5 | class_option :jquery, :type => :boolean, :default => true, :desc => "Use jQuery Javascript framework"
6 |
7 | attr_accessor :model_class_name,
8 | :controller_class_name,
9 | :helper_class_name,
10 | :mailer_class_name
11 |
12 | def initialize(*runtime_args, &runtime_options)
13 | super
14 | @name ||= "feedback"
15 | @model_class_name = name.classify
16 | @mailer_class_name = "#{@model_class_name}Mailer"
17 | @controller_class_name = "#{@model_class_name.pluralize}Controller"
18 | @helper_class_name = "#{@model_class_name.pluralize}Helper"
19 | end
20 |
21 | def add_stylesheet
22 | copy_file 'feedback.css.scss', 'app/assets/stylesheets/feedback.css.scss'
23 | end
24 |
25 | def add_javascript
26 | file_name = options[:jquery] ? 'jquery.feedback.js.coffee' : 'prototype.feedback.js.coffee'
27 | copy_file file_name, "app/assets/javascripts/#{file_name}"
28 | end
29 |
30 | def add_images
31 | copy_file "images/feedback_tab.png", "app/assets/images/feedback/feedback_tab.png"
32 | copy_file "images/feedback_tab_h.png", "app/assets/images/feedback/feedback_tab_h.png"
33 | copy_file "images/closelabel.gif", "app/assets/images/feedback/closelabel.gif"
34 | copy_file "images/loading.gif", "app/assets/images/feedback/loading.gif"
35 | end
36 |
37 | def add_model
38 | template 'feedback_model.rb.erb', "app/models/#{name}.rb"
39 | end
40 |
41 | def add_mailer
42 | template 'feedback_mailer.rb.erb', "app/mailers/#{name}_mailer.rb"
43 | copy_file 'views/feedback_mailer/feedback.html.erb', "app/views/#{name}_mailer/feedback.html.erb"
44 | end
45 |
46 | def add_controller
47 | template 'feedbacks_controller.rb.erb', "app/controllers/#{name.pluralize}_controller.rb"
48 | end
49 |
50 | def add_helper
51 | template_name = options[:jquery] ? 'feedbacks_helper.rb.jquery.erb' : 'feedbacks_helper.rb.prototype.erb'
52 | template template_name, "app/helpers/#{name.pluralize}_helper.rb"
53 | end
54 |
55 | def add_views
56 | copy_file 'views/feedbacks/new.html.erb', "app/views/#{name.pluralize}/new.html.erb"
57 | end
58 |
59 | def add_routes
60 | route "match 'feedbacks/new' => '#{name.pluralize}#new', :as => :new_feedback"
61 | route "match 'feedbacks' => '#{name.pluralize}#create', :as => :feedback"
62 | end
63 |
64 | def add_unit_test
65 | template 'feedback_test.rb.erb', "test/unit/#{name}_test.rb"
66 | template 'feedback_mailer_test.rb.erb', "test/unit/#{name}_mailer_test.rb"
67 | end
68 |
69 | def add_functional_test
70 | template 'feedbacks_controller_test.rb.erb', "test/functional/#{name.pluralize}_controller_test.rb"
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/feedback.css:
--------------------------------------------------------------------------------
1 |
2 | div#feedback {
3 | position: absolute;
4 | top: 0;
5 | left: 0;
6 | width: 100%;
7 | height: 100%;
8 | z-index: 100001;
9 | }
10 |
11 | a#feedback_link {
12 | position: fixed;
13 | z-index: 99999;
14 | background-color: #CC0000;
15 | width: 26px;
16 | height: 96px;
17 | overflow: hidden;
18 | background: #C00 url(/images/feedback/feedback_tab.png);
19 | _position: absolute;
20 | /*_background-image: url(/images/feedback/feedback_tab.png);*/
21 | }
22 |
23 | a#feedback_link:hover {
24 | background-color: #999;
25 | }
26 |
27 | a#feedback_link.left {
28 | left: 0;
29 | top: 25%;
30 | width: 26px;
31 | height: 96px;
32 | background-image: url(/images/feedback/feedback_tab.png);
33 | _background-image: url(/images/feedback/feedback_tab.png);
34 | }
35 |
36 | a#feedback_link.right {
37 | right: 0;
38 | top: 25%;
39 | width: 26px;
40 | height: 96px;
41 | background-image: url(/images/feedback/feedback_tab.png);
42 | _background-image: url(/images/feedback/feedback_tab.png);
43 | }
44 |
45 | a#feedback_link.top {
46 | right: 10%;
47 | top: 0;
48 | width: 96px;
49 | height: 26px;
50 | background-image: url(/images/feedback/feedback_tab_h.png);
51 | _background-image: url(/images/feedback/feedback_tab_h.png);
52 | }
53 |
54 | a#feedback_link.bottom {
55 | right: 10%;
56 | bottom: 0;
57 | width: 96px;
58 | height: 26px;
59 | background-image: url(/images/feedback/feedback_tab_h.png);
60 | _background-image: url(/images/feedback/feedback_tab_h.png);
61 | }
62 |
63 | a#feedback_close_link {
64 | right: 5pt;
65 | top: 3pt;
66 | position: absolute;
67 | height: 22px;
68 | width: 66px;
69 | background-image: url(/images/feedback/closelabel.gif);
70 | _background-image: url(/images/feedback/closelabel.gif);
71 | }
72 |
73 | div#feedback_modal_window {
74 | width: 300px;
75 | margin: 0 auto;
76 | position: relative;
77 | background-color: #FFFFFF;
78 | }
79 |
80 | div#feedback_modal_content {
81 | padding: 12px;
82 | }
83 |
84 | div#feedback_loading {
85 | padding: 12px;
86 | }
87 |
88 | #feedback_overlay {
89 | position: fixed;
90 | top: 0px;
91 | left: 0px;
92 | height: 100%;
93 | width: 100%;
94 | background-color: #000;
95 | opacity: 0.5;
96 | -moz-opacity: 0.5;
97 | filter:alpha(opacity=50);
98 | }
99 |
100 | .feedback_hide {
101 | z-index:-100;
102 | }
103 |
104 | .feedback_overlayBG {
105 | background-color: #000;
106 | z-index: 100000;
107 | }
108 |
109 | * html #feedback_overlay { /* ie6 hack */
110 | position: absolute;
111 | height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');
112 | }
113 |
114 | form#feedback_form .error {
115 | color: red;
116 | font-weight: bold;
117 | }
118 |
--------------------------------------------------------------------------------
/generators/feedback_form/feedback_form_generator.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + "/lib/insert_routes.rb")
2 |
3 | class FeedbackFormGenerator < Rails::Generator::Base
4 |
5 | attr_accessor :name,
6 | :model_class_name,
7 | :controller_class_name,
8 | :helper_class_name,
9 | :mailer_class_name
10 |
11 |
12 | def initialize(runtime_args, runtime_options = {})
13 | super
14 | @name = (runtime_args[0] || "feedback").downcase
15 | @model_class_name = name.classify
16 | @mailer_class_name = "#{@model_class_name}Mailer"
17 | @controller_class_name = "#{@model_class_name.pluralize}Controller"
18 | @helper_class_name = "#{@model_class_name.pluralize}Helper"
19 | #@js_framework = (runtime_options[''])
20 |
21 | end
22 |
23 | def manifest
24 | record do |m|
25 |
26 | puts "hello"
27 | add_model(m)
28 | add_mailer(m)
29 | add_controller(m)
30 | add_helper(m)
31 | add_views(m)
32 | add_routes(m)
33 | add_unit_test(m)
34 | add_functional_test(m)
35 | add_stylesheet(m)
36 | add_javascript(m)
37 | add_images(m)
38 | end
39 | end
40 |
41 |
42 | def add_stylesheet(m)
43 | m.directory 'public/stylesheets'
44 | m.file 'feedback.css', 'public/stylesheets/feedback.css'
45 |
46 | end
47 |
48 | def add_javascript(m)
49 | m.directory 'public/javascripts'
50 | file_name = options[:jquery] ? 'jquery.feedback.js' : 'prototype.feedback.js'
51 | m.file file_name, "public/javascripts/#{file_name}"
52 | end
53 |
54 | def add_images(m)
55 | m.directory 'public/images/feedback'
56 | m.file "images/feedback_tab.png", "public/images/feedback/feedback_tab.png"
57 | m.file "images/feedback_tab_h.png", "public/images/feedback/feedback_tab_h.png"
58 | m.file "images/closelabel.gif", "public/images/feedback/closelabel.gif"
59 | m.file "images/loading.gif", "public/images/feedback/loading.gif"
60 | end
61 |
62 | def add_model(m)
63 | m.template 'feedback_model.rb.erb', "app/models/#{name}.rb"
64 | end
65 |
66 | def add_mailer(m)
67 | m.template 'feedback_mailer.rb.erb', "app/models/#{name}_mailer.rb"
68 | m.directory "app/views/#{name}_mailer"
69 | m.file 'views/feedback_mailer/feedback.html.erb', "app/views/#{name}_mailer/feedback.html.erb"
70 |
71 | end
72 |
73 | def add_controller(m)
74 | m.template 'feedbacks_controller.rb.erb', "app/controllers/#{name.pluralize}_controller.rb"
75 | end
76 |
77 | def add_helper(m)
78 | template_name = options[:jquery] ? 'feedbacks_helper.rb.jquery.erb' : 'feedbacks_helper.rb.prototype.erb'
79 | m.template template_name, "app/helpers/#{name.pluralize}_helper.rb"
80 | end
81 |
82 | def add_views(m)
83 | m.directory "app/views/#{name.pluralize}"
84 | m.file 'views/feedbacks/new.html.erb', "app/views/#{name.pluralize}/new.html.erb"
85 | end
86 |
87 | def add_routes(m)
88 | m.route_name "new_feedback", "feedbacks/new", {:controller => name.pluralize, :action => "new"}
89 | m.route_name "feedback", "feedbacks", {:controller => name.pluralize, :action => "create"}
90 | end
91 |
92 | def add_unit_test(m)
93 | m.template 'feedback_test.rb.erb', "test/unit/#{name}_test.rb"
94 | m.template 'feedback_mailer_test.rb.erb', "test/unit/#{name}_mailer_test.rb"
95 | end
96 |
97 | def add_functional_test(m)
98 | m.template 'feedbacks_controller_test.rb.erb', "test/functional/#{name.pluralize}_controller_test.rb"
99 | end
100 |
101 | protected
102 |
103 | def add_options!(opt)
104 | opt.separator ''
105 | opt.separator 'Options:'
106 | opt.on("--jquery",
107 | "Use jquery Javascript framework, default is Prototyp") { |v| options[:jquery] = true }
108 | end
109 | end
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/jquery.feedback.js.coffee:
--------------------------------------------------------------------------------
1 | (($) ->
2 | settings = undefined
3 | $.fn.feedback = (callerSettings) ->
4 | settings = $.extend(
5 | main: "feedback"
6 | closeLink: "feedback_close_link"
7 | modalWindow: "feedback_modal_window"
8 | modalContent: "feedback_modal_content"
9 | form: "feedback_form"
10 | formUrl: "/feedbacks/new"
11 | overlay: "feedback_overlay"
12 | loadingImage: "/assets/feedback/loading.gif"
13 | loadingText: "Loading..."
14 | sendingText: "Sending..."
15 | tabPosition: "left"
16 | , callerSettings or {})
17 | settings.feedbackHtml = ""
18 | settings.overlayHtml = ""
19 | settings.tabHtml = ""
20 | settings.main = "#" + settings.main
21 | settings.closeLink = "#" + settings.closeLink
22 | settings.modalWindow = "#" + settings.modalWindow
23 | settings.modalContent = "#" + settings.modalContent
24 | settings.form = "#" + settings.form
25 | settings.overlay = "#" + settings.overlay
26 | settings.tabControls = this
27 | if settings.tabPosition? and $("#feedback_link").length is 0
28 | $("body").append settings.tabHtml
29 | settings.tabControls = $(settings.tabControls).add($("#feedback_link"))
30 | $(settings.tabControls).click ->
31 | loading()
32 | $(settings.modalContent).load settings.formUrl, null, ->
33 | $(settings.form).submit submitFeedback
34 |
35 | false
36 |
37 | submitFeedback = ->
38 | $("input[name=feedback\\[page\\]]").val location.href
39 | data = $(settings.form).serialize()
40 | url = $.trim($(settings.form).attr("action"))
41 | loading settings.sendingText
42 | $.ajax
43 | type: "POST"
44 | url: url
45 | data: data
46 | success: (msg, status) ->
47 | $(settings.modalContent).html msg
48 | $(settings.modalWindow).fadeOut 2000, ->
49 | hideFeedback()
50 |
51 | error: (xhr, status, a) ->
52 | $(settings.modalContent).html xhr.responseText
53 | $(settings.form).submit submitFeedback
54 |
55 | false
56 |
57 | initOverlay = ->
58 | $("body").append settings.overlayHtml if $(settings.overlay).length is 0
59 | $(settings.overlay).hide().addClass "feedback_overlayBG"
60 |
61 | showOverlay = ->
62 | initOverlay().show()
63 |
64 | hideOverlay = ->
65 | return false if $(settings.overlay).length is 0
66 | $(settings.overlay).remove()
67 |
68 | initFeedback = ->
69 | if $(settings.main).length is 0
70 | $("body").append settings.feedbackHtml
71 | $(settings.closeLink).click ->
72 | hideFeedback()
73 | false
74 |
75 | setBoxPosition()
76 | $ settings.main
77 |
78 | showFeedback = ->
79 | initFeedback().show()
80 |
81 | hideFeedback = ->
82 | $(settings.main).hide()
83 | $(settings.main).remove()
84 | hideOverlay()
85 |
86 | setBoxPosition = ->
87 | scrollTop = undefined
88 | clientHeight = undefined
89 | if self.pageYOffset
90 | scrollTop = self.pageYOffset
91 | else if document.documentElement and document.documentElement.scrollTop
92 | scrollTop = document.documentElement.scrollTop
93 | else scrollTop = document.body.scrollTop if document.body
94 | if self.innerHeight
95 | clientHeight = self.innerHeight
96 | else if document.documentElement and document.documentElement.clientHeight
97 | clientHeight = document.documentElement.clientHeight
98 | else clientHeight = document.body.clientHeight if document.body
99 | $(settings.modalWindow).css top: scrollTop + (clientHeight / 10) + "px"
100 |
101 | loading = (text) ->
102 | showOverlay()
103 | initFeedback()
104 | text = settings.loadingText unless text?
105 | $(settings.modalContent).html "" + text + "
"
106 | showFeedback()
107 | ) jQuery
108 |
--------------------------------------------------------------------------------
/lib/generators/feedback_form/templates/prototype.feedback.js.coffee:
--------------------------------------------------------------------------------
1 | Feedback = undefined
2 | if Feedback is `undefined`
3 | Feedback = {}
4 | Feedback.init = (callerSettings) ->
5 | @settings = Object.extend(
6 | tabControl: "feedback_link"
7 | main: "feedback"
8 | closeLink: "feedback_close_link"
9 | modalWindow: "feedback_modal_window"
10 | modalContent: "feedback_modal_content"
11 | form: "feedback_form"
12 | formUrl: "/feedbacks/new"
13 | overlay: "feedback_overlay"
14 | loadingImage: "/assets/feedback/loading.gif"
15 | loadingText: "Loading..."
16 | sendingText: "Sending..."
17 | tabPosition: "left"
18 | , callerSettings or {})
19 | @settings.feedbackHtml = ""
20 | @settings.overlayHtml = ""
21 | @settings.tabHtml = ""
22 | $$("body").first().insert @settings.tabHtml if @settings.tabPosition? and $$("#" + @settings.tabControl).length is 0
23 | $$("." + @settings.tabControl).each (e) ->
24 | $(e).observe "click", ->
25 | Feedback.loading()
26 | new Ajax.Updater(Feedback.settings.modalContent, Feedback.settings.formUrl,
27 | method: "get"
28 | onComplete: (transport) ->
29 | $(Feedback.settings.form).observe "submit", Feedback.submitFeedback
30 | )
31 | false
32 |
33 | Feedback.submitFeedback = (event) ->
34 | $("feedback_page").value = location.href
35 | data = Form.serialize($(Feedback.settings.form))
36 | url = $(Feedback.settings.form).action
37 | Feedback.loading Feedback.settings.sendingText
38 | new Ajax.Updater(Feedback.settings.modalContent, url,
39 | method: "POST"
40 | parameters: data
41 | onComplete: (transport) ->
42 | if transport.status >= 200 and transport.status < 300
43 | $(Feedback.settings.modalWindow).fade
44 | duration: 2.0
45 | afterFinish: ->
46 | Feedback.hideFeedback()
47 | else
48 | $(Feedback.settings.form).observe "submit", Feedback.submitFeedback
49 | )
50 | Event.stop event
51 |
52 | Feedback.initOverlay = ->
53 | $$("body").first().insert @settings.overlayHtml if $$("#" + @settings.overlay).length is 0
54 | $(@settings.overlay).addClassName "feedback_overlayBG"
55 |
56 | Feedback.showOverlay = ->
57 | Feedback.initOverlay()
58 | $(@settings.overlay).show()
59 |
60 | Feedback.hideOverlay = ->
61 | return false if $$("#" + @settings.overlay).length is 0
62 | $(@settings.overlay).remove()
63 |
64 | Feedback.initFeedback = ->
65 | if $$("#" + @settings.main).length is 0
66 | $$("body").first().insert @settings.feedbackHtml
67 | $(@settings.closeLink).observe "click", ->
68 | Feedback.hideFeedback()
69 | false
70 |
71 | Feedback.setWindowPosition()
72 |
73 | Feedback.showFeedback = ->
74 | Feedback.initFeedback()
75 | $(@settings.main).show()
76 |
77 | Feedback.hideFeedback = ->
78 | $(@settings.main).hide()
79 | $(@settings.main).remove()
80 | Feedback.hideOverlay()
81 |
82 | Feedback.loading = (text) ->
83 | Feedback.showOverlay()
84 | Feedback.initFeedback()
85 | text = @settings.loadingText unless text?
86 | $(@settings.modalContent).update "" + text + "
"
87 | $(@settings.main).show()
88 |
89 | Feedback.setWindowPosition = ->
90 | scrollTop = undefined
91 | clientHeight = undefined
92 | if self.pageYOffset
93 | scrollTop = self.pageYOffset
94 | else if document.documentElement and document.documentElement.scrollTop
95 | scrollTop = document.documentElement.scrollTop
96 | else scrollTop = document.body.scrollTop if document.body
97 | if self.innerHeight
98 | clientHeight = self.innerHeight
99 | else if document.documentElement and document.documentElement.clientHeight
100 | clientHeight = document.documentElement.clientHeight
101 | else clientHeight = document.body.clientHeight if document.body
102 | $(@settings.modalWindow).setStyle top: scrollTop + (clientHeight / 10) + "px"
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Feedback Plugin for Rails
2 |
3 | Feedback is a Ruby on Rails plugin that adds an ajax-based
4 | overlay feedback form triggered by a side-screen tab or a link.
5 | The result of the feedback form is sent through a ActionMailer.
6 |
7 | The plugin was inspired from the "feedback" widget of
8 | GetSatisfaction.com and UserVoice.com.
9 |
10 | The plugin uses a generator to generate boiler-plate Javascript,
11 | CSS, HTML, and ruby code into your application.
12 | It sets up a ready to use feedback form (with minimal styling)
13 | that you can further customize.
14 |
15 |
16 | ## Features
17 |
18 | * Helper to display a sticky "feedback" tab.
19 | * Display feedback form as an overlay with Ajax.
20 | * Sends feedback result by email through an ActionMailer.
21 | * Uses a generator such that you can customize the code and style to fit your app.
22 | * Generates unit and functional tests.
23 | * Works with Prototype and JQuery, choose through a generators flag.
24 | * Currently supports IE6, IE7, IE8, FF2, FF3, Chrome2 and SafariX
25 |
26 |
27 | ## Motivation
28 |
29 | Collecting feedback from users is really important. Third-party services
30 | such as GetSatisfaction.com and UserVoice.com are awesome tools to manage your feedback.
31 | However, we found out that in many instances such as for small sites or back-ends, we wanted
32 | a similar feedback form integration/experience but simpler, local, and customized.
33 |
34 |
35 | ## Install and Getting Started
36 |
37 | ### for Rails 2.x
38 |
39 | Install the plugin:
40 |
41 | ruby script/plugin install git://github.com/jsboulanger/feedback.git
42 |
43 | Run the generator:
44 |
45 | ruby script/generate feedback_form
46 |
47 | By default it will generate the Prototype code, you can use jQuery if you prefer:
48 |
49 | ruby script/generate feedback_form --jquery
50 |
51 | Include the feedback.css and the jquery.feedback.js/prototype.feedback.js files into your header.
52 | There is a helper to do that:
53 |
54 | <%= feedback_includes %>
55 |
56 | ### for Rails 3.x
57 |
58 | Install the generator:
59 |
60 | git clone git://github.com/dohkoos/feedback.git
61 | cp feedback/lib/generators/feedback_form [YOUR_PROJECT_NAME]/lib/generators
62 |
63 | Run the generator:
64 |
65 | rails generate feedback_form
66 |
67 | By default it will generate the jQuery code, you can use Prototype if you prefer:
68 |
69 | rails generate feedback_form --skip-jquery
70 |
71 | To add a sticky 'feedback' tab to your site, in the header place:
72 |
73 | <%= feedback_tab(:position => 'top') %>
74 |
75 | Configure the action mailer in app/models/feedback_mailer.rb
76 |
77 | def feedback(feedback)
78 | @recipients = 'you@yoursite.com'
79 | @from = 'system@yoursite.com'
80 | @subject = "[Feedback for YourSite.com] #{feedback.subject}"
81 | ...
82 |
83 |
84 | ## How-to's
85 |
86 | ### How to customize the position of the feedback tab?
87 |
88 | The feedback_tab helper takes the option "position" which has four different values (top|bottom|left|right)
89 | that corresponds to class in the feedback.css file. You can fine tune the position by changing the left/right, top/bottom
90 | attributes of those css classes.
91 |
92 | ### How to trigger the feedback form from a custom link?
93 |
94 | The feedback_link(text, options={}) helper allows you the create a link that will trigger the feedback form.
95 |
96 | <%= feedback_link "Leave us feedback!" %>
97 |
98 | Your layout header must also have the includes:
99 |
100 | <%= feedback_includes %>
101 |
102 |
103 | ### How to customize the email message?
104 |
105 | Edit the /app/views/feedback_mailer/feedback.html.erb generated file in your app
106 |
107 |
108 | ### How to store the feedback results in the database?
109 |
110 | Feedback generates a model that is not an active record model. In order to store the feedbacks in your database, make it
111 | an active record model:
112 |
113 | class Feedback < ActiveRecord::Base
114 | attr_accessible :comment, :email, :subject, :page
115 |
116 | validates :subject, :presence => true
117 | validates :comment, :presence => true
118 | end
119 |
120 | You must also write a migration to create your table:
121 |
122 | class CreateFeedbacks < ActiveRecord::Migration
123 | def self.up
124 | create_table :feedbacks do |t|
125 | t.string :subject
126 | t.string :email
127 | t.text :comment
128 | t.timestamps
129 | end
130 | end
131 |
132 | def self.down
133 | drop_table :feedbacks
134 | end
135 | end
136 |
137 |
138 | ## Limitations (TODOs)
139 |
140 | * No unit tests for Javascript
141 | * Feedback PNG with transparency supported by IE6
142 | * Graceful fallback if Javascript is not enabled/supported
143 | * More customization options through helpers: (overlay style, tab color, etc...)
144 |
145 |
146 | ## Acknowledgements
147 |
148 | Thanks to GetSatisfaction.com for the
149 | idea of the "feedback" tab.
150 |
151 | Part of this plugin was inspired from other software.
152 | Thanks to their respective authors.
153 | * Facebox: http://famspam.com/facebox
154 | * restful_authentication: http://github.com/technoweenie/restful-authentication/tree/master
155 |
156 | Copyright (c) 2009 Jean-Sebastien Boulanger , released under the MIT license
157 |
158 | http://jsboulanger.com
159 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/jquery.feedback.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Feedback (for jQuery)
3 | * version: 0.1 (2009-07-21)
4 | * @requires jQuery v1.3 or later
5 | *
6 | * This script is part of the Feedback Ruby on Rails Plugin:
7 | * http://
8 | *
9 | * Licensed under the MIT:
10 | * http://www.opensource.org/licenses/mit-license.php
11 | *
12 | * Copyright 2009 Jean-Sebastien Boulanger [ jsboulanger@gmail.com ]
13 | *
14 | * Usage:
15 | *
16 | * jQuery(document).ready(function() {
17 | * jQuery('#feedback_tab_link').feedback({
18 | * // options
19 | * });
20 | * })
21 | *
22 | */
23 | (function($) {
24 | var settings;
25 |
26 | $.fn.feedback = function(callerSettings) {
27 | settings = $.extend({
28 | main: 'feedback',
29 | closeLink: 'feedback_close_link',
30 | modalWindow: 'feedback_modal_window',
31 | modalContent: 'feedback_modal_content',
32 | form: 'feedback_form',
33 | formUrl: '/feedbacks/new',
34 | overlay: 'feedback_overlay',
35 | loadingImage: '/images/feedback/loading.gif',
36 | loadingText: 'Loading...',
37 | sendingText: 'Sending...',
38 | tabPosition: 'left'
39 | }, callerSettings || {});
40 |
41 | settings.feedbackHtml = '' +
42 | '
' +
43 | '
' +
44 | '
' +
45 | '
' +
46 | '
'
47 | settings.overlayHtml = '';
48 | settings.tabHtml = '';
49 | settings.main = '#' + settings.main;
50 | settings.closeLink = '#' + settings.closeLink;
51 | settings.modalWindow = '#' + settings.modalWindow;
52 | settings.modalContent = '#' + settings.modalContent;
53 | settings.form = '#' + settings.form;
54 | settings.overlay = '#' + settings.overlay;
55 | settings.tabControls = this;
56 |
57 | if (settings.tabPosition != null && $("#feedback_link").length == 0) {
58 | $("body").append(settings.tabHtml);
59 | settings.tabControls = $(settings.tabControls).add($('#feedback_link'));
60 | }
61 |
62 | $(settings.tabControls).click(function() {
63 | loading();
64 | $(settings.modalContent).load(settings.formUrl, null, function() {
65 | $(settings.form).submit(submitFeedback);
66 | });
67 | return false;
68 | });
69 | };
70 |
71 | var submitFeedback = function() {
72 | $('input[name=feedback\\[page\\]]').val(location.href);
73 | var data = $(settings.form).serialize();
74 | var url = $.trim($(settings.form).attr('action'));
75 | loading(settings.sendingText);
76 | $.ajax({
77 | type: "POST",
78 | url: url,
79 | data: data,
80 | success: function(msg, status) {
81 | $(settings.modalContent).html(msg);
82 | $(settings.modalWindow).fadeOut(2000, function() {
83 | hideFeedback();
84 | });
85 | },
86 | error: function(xhr, status, a) {
87 | $(settings.modalContent).html(xhr.responseText);
88 | $(settings.form).submit(submitFeedback);
89 | }
90 | });
91 | return false;
92 | }
93 |
94 |
95 | var initOverlay = function() {
96 | if ($(settings.overlay).length == 0)
97 | $("body").append(settings.overlayHtml)
98 | return $(settings.overlay).hide().addClass("feedback_overlayBG")
99 | }
100 |
101 | var showOverlay = function() {
102 | initOverlay().show();
103 | }
104 |
105 | var hideOverlay = function() {
106 | if ($(settings.overlay).length == 0) return false;
107 | $(settings.overlay).remove();
108 | }
109 |
110 | var initFeedback = function() {
111 | if ($(settings.main).length == 0) {
112 | $("body").append(settings.feedbackHtml);
113 | $(settings.closeLink).click(function() {
114 | hideFeedback();
115 | return false;
116 | });
117 | setBoxPosition();
118 | }
119 | return $(settings.main);
120 | }
121 |
122 | var showFeedback = function() {
123 | initFeedback().show();
124 | }
125 |
126 | var hideFeedback = function() {
127 | $(settings.main).hide();
128 | $(settings.main).remove();
129 | hideOverlay();
130 | }
131 |
132 | var setBoxPosition = function() {
133 | var scrollTop, clientHeight;
134 | if (self.pageYOffset) {
135 | scrollTop = self.pageYOffset;
136 | } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
137 | scrollTop = document.documentElement.scrollTop;
138 | } else if (document.body) {// all other Explorers
139 | scrollTop = document.body.scrollTop;
140 | }
141 | if (self.innerHeight) { // all except Explorer
142 | clientHeight = self.innerHeight;
143 | } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
144 | clientHeight = document.documentElement.clientHeight;
145 | } else if (document.body) { // other Explorers
146 | clientHeight = document.body.clientHeight;
147 | }
148 | $(settings.modalWindow).css({
149 | top: scrollTop + (clientHeight / 10) + 'px'
150 | });
151 | }
152 |
153 | var loading = function(text) {
154 | showOverlay();
155 | initFeedback();
156 |
157 | if(text == null)
158 | text = settings.loadingText;
159 |
160 | $(settings.modalContent).html(
161 | '' + text + '
');
162 |
163 | showFeedback();
164 |
165 | }
166 | }) (jQuery);
167 |
168 |
--------------------------------------------------------------------------------
/generators/feedback_form/templates/prototype.feedback.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Feedback (for Prototype)
3 | * version: 0.1 (2009-07-21)
4 | * @requires Prototype v1.6 or later
5 | *
6 | * This script is part of the Feedback Ruby on Rails Plugin:
7 | * http://
8 | *
9 | * Licensed under the MIT:
10 | * http://www.opensource.org/licenses/mit-license.php
11 | *
12 | * Copyright 2009 Jean-Sebastien Boulanger [ jsboulanger@gmail.com ]
13 | *
14 | * Usage:
15 | *
16 | * Feedback.init('feedback_link_tab', {
17 | * // options
18 | * });
19 | *
20 | */
21 |
22 | var Feedback;
23 |
24 | if(Feedback == undefined) {
25 | Feedback = {};
26 | Feedback.init = function(callerSettings) {
// Changes to those default settings might need adjustment in feedback.css
27 | this.settings = Object.extend({
28 | tabControl: 'feedback_link',
29 | main: 'feedback',
30 | closeLink: 'feedback_close_link',
31 | modalWindow: 'feedback_modal_window',
32 | modalContent: 'feedback_modal_content',
33 | form: 'feedback_form',
34 | formUrl: '/feedbacks/new',
35 | overlay: 'feedback_overlay',
36 | loadingImage: '/images/feedback/loading.gif',
37 | loadingText: 'Loading...',
38 | sendingText: 'Sending...',
39 | tabPosition: 'left'
40 | }, callerSettings || {});
41 |
42 | this.settings.feedbackHtml = '' +
43 | '
' +
44 | '
' +
45 | '
' +
46 | '
' +
47 | '
'
48 | this.settings.overlayHtml = '';
49 | this.settings.tabHtml = '';
50 |
51 | if (this.settings.tabPosition != null && $$('#' + this.settings.tabControl).length == 0)
52 | $$("body").first().insert(this.settings.tabHtml);
53 |
54 | $$('.' + this.settings.tabControl).each(function(e) {
55 | $(e).observe('click', function() {
56 | Feedback.loading();
57 | new Ajax.Updater(Feedback.settings.modalContent, Feedback.settings.formUrl, {
58 | method: 'get',
59 | onComplete: function(transport) {
60 | $(Feedback.settings.form).observe('submit', Feedback.submitFeedback);
61 | }
62 | });
63 | return false;
64 | });
65 | });
66 | }
67 |
68 | Feedback.submitFeedback = function(event){
69 | $('feedback_page').value = location.href;
70 | var data = Form.serialize($(Feedback.settings.form));
71 | var url = $(Feedback.settings.form).action;
72 | Feedback.loading(Feedback.settings.sendingText);
73 | new Ajax.Updater(Feedback.settings.modalContent, url, {
74 | method: 'POST',
75 | parameters: data,
76 | onComplete: function(transport){
77 | if (transport.status >= 200 && transport.status < 300) {
78 | $(Feedback.settings.modalWindow).fade({
79 | duration: 2.0,
80 | afterFinish: function() {
81 | Feedback.hideFeedback();
82 | }
83 | });
84 | }
85 | else {
86 | $(Feedback.settings.form).observe('submit', Feedback.submitFeedback);
87 | }
88 | }
89 | });
Event.stop(event);
90 | }
91 |
92 | Feedback.initOverlay = function() {
93 | if ($$('#' + this.settings.overlay).length == 0)
94 | $$("body").first().insert(this.settings.overlayHtml)
95 |
96 | $(this.settings.overlay).addClassName('feedback_overlayBG');
97 | }
98 |
99 | Feedback.showOverlay = function() {
100 | Feedback.initOverlay();
101 | $(this.settings.overlay).show();
102 | }
103 |
104 | Feedback.hideOverlay = function() {
105 | if ($$('#' + this.settings.overlay).length == 0) return false;
106 | $(this.settings.overlay).remove();
107 | }
108 |
109 | Feedback.initFeedback = function() {
110 | if ($$('#' + this.settings.main).length == 0) {
111 | $$("body").first().insert(this.settings.feedbackHtml);
112 | $(this.settings.closeLink).observe('click', function(){
113 | Feedback.hideFeedback();
114 | return false;
115 | });
116 | Feedback.setWindowPosition();
117 | }
118 | }
119 |
120 | Feedback.showFeedback = function() {
121 | Feedback.initFeedback();
122 | $(this.settings.main).show();
123 | }
124 |
125 | Feedback.hideFeedback = function() {
126 | $(this.settings.main).hide();
127 | $(this.settings.main).remove();
128 | Feedback.hideOverlay();
129 | }
130 |
131 |
132 | Feedback.loading = function(text){
133 | Feedback.showOverlay();
134 | Feedback.initFeedback();
135 | if (text == null)
136 | text = this.settings.loadingText;
137 |
138 | $(this.settings.modalContent).update('' + text + '
');
139 | $(this.settings.main).show();
140 | }
141 |
142 | Feedback.setWindowPosition = function() {
143 | var scrollTop, clientHeight;
144 | if (self.pageYOffset) {
145 | scrollTop = self.pageYOffset;
146 | } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
147 | scrollTop = document.documentElement.scrollTop;
148 | } else if (document.body) {// all other Explorers
149 | scrollTop = document.body.scrollTop;
150 | }
151 | if (self.innerHeight) { // all except Explorer
152 | clientHeight = self.innerHeight;
153 | } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
154 | clientHeight = document.documentElement.clientHeight;
155 | } else if (document.body) { // other Explorers
156 | clientHeight = document.body.clientHeight;
157 | }
158 | $(this.settings.modalWindow).setStyle({
159 | top: scrollTop + (clientHeight / 10) + 'px'
160 | });
161 | }
162 | }
163 |
--------------------------------------------------------------------------------