padrino-admin 0.7.9 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +1 -1
- data/Rakefile +6 -8
- data/VERSION +1 -1
- data/lib/padrino-admin.rb +1 -1
- data/lib/padrino-admin/access_control.rb +7 -13
- data/lib/padrino-admin/generators/admin_app.rb +14 -17
- data/lib/padrino-admin/generators/admin_page.rb +10 -12
- data/lib/padrino-admin/generators/admin_uploader.rb +17 -14
- data/lib/padrino-admin/generators/app/app.rb.tt +3 -3
- data/lib/padrino-admin/generators/app/views/javascripts/admin.js.erb +1 -1
- data/lib/padrino-admin/helpers/authentication_helpers.rb +103 -0
- data/lib/padrino-admin/helpers/view_helpers.rb +367 -0
- data/lib/padrino-admin/locale/extjs/it.yml +5 -5
- data/lib/padrino-admin/middleware/flash_middleware.rb +5 -12
- data/lib/padrino-admin/orm.rb +7 -0
- data/padrino-admin.gemspec +17 -13
- data/test/generators/test_admin_app_generator.rb +67 -0
- data/test/generators/test_admin_page_generator.rb +59 -0
- data/test/generators/test_admin_uploader_generator.rb +56 -0
- data/test/helper.rb +23 -0
- data/test/test_access_control.rb +3 -2
- data/test/test_active_record.rb +2 -0
- data/test/test_admin_application.rb +61 -10
- data/test/test_column_store.rb +1 -1
- data/test/test_config.rb +13 -0
- data/test/test_data_mapper.rb +5 -0
- metadata +11 -7
- data/lib/padrino-admin/helpers/authentication.rb +0 -102
- data/lib/padrino-admin/helpers/view.rb +0 -286
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
|
@@ -42,17 +42,15 @@ end
|
|
|
42
42
|
|
|
43
43
|
begin
|
|
44
44
|
require 'rcov/rcovtask'
|
|
45
|
-
Rcov::RcovTask.new do |
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
test.pattern = 'test/**/test_*.rb'
|
|
51
|
-
test.verbose = true
|
|
45
|
+
Rcov::RcovTask.new do |rcov|
|
|
46
|
+
rcov.libs << 'test'
|
|
47
|
+
rcov.pattern = 'test/**/test_*.rb'
|
|
48
|
+
rcov.verbose = true
|
|
49
|
+
rcov.rcov_opts << ['--exclude /Gems/1.8/gems/,padrino-core,padrino-cache,padrino-gen,padrino-helpers,padrino-mailer']
|
|
52
50
|
end
|
|
53
51
|
rescue LoadError
|
|
54
52
|
task :rcov do
|
|
55
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install
|
|
53
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install relevance-rcov"
|
|
56
54
|
end
|
|
57
55
|
end
|
|
58
56
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.8.0
|
data/lib/padrino-admin.rb
CHANGED
|
@@ -40,7 +40,7 @@ CarrierWave.root = Padrino.root("public") if defined?(CarrierWave)
|
|
|
40
40
|
##
|
|
41
41
|
# Extend Abastract Form builder
|
|
42
42
|
#
|
|
43
|
-
Padrino::Helpers::FormBuilder::AbstractFormBuilder.send(:include, Padrino::Admin::Helpers::AbstractFormBuilder)
|
|
43
|
+
Padrino::Helpers::FormBuilder::AbstractFormBuilder.send(:include, Padrino::Admin::Helpers::ViewHelpers::AbstractFormBuilder)
|
|
44
44
|
|
|
45
45
|
##
|
|
46
46
|
# Load our Padrino::Admin locales
|
|
@@ -73,7 +73,6 @@ module Padrino
|
|
|
73
73
|
#
|
|
74
74
|
# class AdminDemo < Padrino::Application
|
|
75
75
|
# enable :authentication
|
|
76
|
-
# set :redirect_to_default, "/" # or your page
|
|
77
76
|
#
|
|
78
77
|
# access_control.roles_for :any do |role|
|
|
79
78
|
# role.allow "/sessions"
|
|
@@ -129,7 +128,8 @@ module Padrino
|
|
|
129
128
|
#
|
|
130
129
|
def self.registered(app)
|
|
131
130
|
app.set :session_id, "_padrino_#{File.basename(Padrino.root)}_#{app.app_name}".to_sym
|
|
132
|
-
app.helpers Padrino::Admin::Helpers
|
|
131
|
+
app.helpers Padrino::Admin::Helpers::ViewHelpers
|
|
132
|
+
app.helpers Padrino::Admin::Helpers::AuthenticationHelpers
|
|
133
133
|
app.before { login_required }
|
|
134
134
|
app.use Padrino::Admin::Middleware::FlashMiddleware, app.session_id # make sure that is the same of session_name in helpers
|
|
135
135
|
Padrino::Admin::Orm.extend_account!
|
|
@@ -176,10 +176,10 @@ module Padrino
|
|
|
176
176
|
end # Base
|
|
177
177
|
|
|
178
178
|
class Auths #:nodoc:
|
|
179
|
-
attr_reader :allowed, :denied, :project_modules
|
|
179
|
+
attr_reader :account, :allowed, :denied, :project_modules
|
|
180
180
|
|
|
181
181
|
def initialize(authorizations, mappers=nil, account=nil) #:nodoc:
|
|
182
|
-
@allowed, @denied = [], []
|
|
182
|
+
@allowed, @denied, @account = [], [], account
|
|
183
183
|
unless authorizations.empty?
|
|
184
184
|
@allowed = authorizations.collect(&:allowed).flatten
|
|
185
185
|
@denied = authorizations.collect(&:denied).flatten
|
|
@@ -206,10 +206,11 @@ module Padrino
|
|
|
206
206
|
end
|
|
207
207
|
|
|
208
208
|
##
|
|
209
|
-
# Return
|
|
209
|
+
# Return false if we don't have +denied+ path +or+ if we have a logged account and empty project modules.
|
|
210
|
+
# Return true if the requested path (like request.path_info) is +not+ allowed
|
|
210
211
|
#
|
|
211
212
|
def cannot?(request_path)
|
|
212
|
-
return false if @denied.empty?
|
|
213
|
+
return false if @denied.empty? || (@project_modules.empty? && @account)
|
|
213
214
|
request_path = "/" if request_path.blank?
|
|
214
215
|
@denied.any? { |path| request_path =~ /^#{path}/ }
|
|
215
216
|
end
|
|
@@ -377,13 +378,6 @@ module Padrino
|
|
|
377
378
|
@name.is_a?(Symbol) ? I18n.t("admin.menus.#{@name}", :default => @name.to_s.humanize) : @name
|
|
378
379
|
end
|
|
379
380
|
|
|
380
|
-
##
|
|
381
|
-
# Return a unique id for the given project module
|
|
382
|
-
#
|
|
383
|
-
def uid
|
|
384
|
-
@name.to_s.downcase.gsub(/[^a-z0-9]+/, '').gsub(/-+$/, '').gsub(/^-+$/, '').to_sym
|
|
385
|
-
end
|
|
386
|
-
|
|
387
381
|
##
|
|
388
382
|
# Return ExtJs Config for this menu
|
|
389
383
|
#
|
|
@@ -16,15 +16,13 @@ module Padrino
|
|
|
16
16
|
|
|
17
17
|
desc "Description:\n\n\tpadrino-gen admin generates a new Padrino Admin"
|
|
18
18
|
|
|
19
|
-
class_option :root, :desc => "The root destination",
|
|
20
|
-
class_option :
|
|
21
|
-
class_option :destroy, :aliases => '-d', :default => false, :type => :boolean
|
|
19
|
+
class_option :root, :desc => "The root destination", :aliases => '-r', :default => ".", :type => :string
|
|
20
|
+
class_option :destroy, :aliases => '-d', :default => false, :type => :boolean
|
|
22
21
|
|
|
23
22
|
# Copies over the Padrino base admin application
|
|
24
23
|
def create_admin
|
|
25
24
|
self.destination_root = options[:root]
|
|
26
25
|
if in_app_root?
|
|
27
|
-
@app_path = options[:path]
|
|
28
26
|
|
|
29
27
|
unless supported_orm.include?(orm)
|
|
30
28
|
say "<= A the moment we only support #{supported_orm.join(" or ")}. Sorry!"
|
|
@@ -32,8 +30,8 @@ module Padrino
|
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
self.behavior = :revoke if options[:destroy]
|
|
35
|
-
directory("app/", destination_root(
|
|
36
|
-
directory("assets/", destination_root("public",
|
|
33
|
+
directory("app/", destination_root("admin"))
|
|
34
|
+
directory("assets/", destination_root("public", "admin"))
|
|
37
35
|
|
|
38
36
|
Padrino::Generators::Model.dup.start([
|
|
39
37
|
"account", "name:string", "surname:string", "email:string", "crypted_password:string", "salt:string", "role:string",
|
|
@@ -42,20 +40,19 @@ module Padrino
|
|
|
42
40
|
|
|
43
41
|
insert_into_gemfile("haml")
|
|
44
42
|
template "templates/page/db/seeds.rb.tt", destination_root("/db/seeds.rb")
|
|
45
|
-
append_file destination_root("config/apps.rb"), "\nPadrino.mount(\"Admin\").to(\"
|
|
43
|
+
append_file destination_root("config/apps.rb"), "\nPadrino.mount(\"Admin\").to(\"/admin\")"
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
return if self.behavior == :revoke
|
|
46
|
+
say (<<-TEXT).gsub(/ {10}/,'')
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
=================================================================
|
|
49
|
+
Your admin now is installed, now follow this steps:
|
|
50
|
+
=================================================================
|
|
51
|
+
1) Run migrations
|
|
52
|
+
2) That's all!!
|
|
53
|
+
=================================================================
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
2) That's all!!
|
|
55
|
-
-----------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
TEXT
|
|
58
|
-
end
|
|
55
|
+
TEXT
|
|
59
56
|
else
|
|
60
57
|
say "You are not at the root of a Padrino application! (config/boot.rb not found)" and exit unless in_app_root?
|
|
61
58
|
end
|
|
@@ -16,9 +16,8 @@ module Padrino
|
|
|
16
16
|
|
|
17
17
|
desc "Description:\n\n\tpadrino-gen admin_page YourModel"
|
|
18
18
|
argument :model, :desc => "The name of your model"
|
|
19
|
-
class_option :
|
|
20
|
-
class_option :
|
|
21
|
-
class_option :destroy, :aliases => '-d', :default => false, :type => :boolean
|
|
19
|
+
class_option :root, :desc => "The root destination", :aliases => '-r', :type => :string
|
|
20
|
+
class_option :destroy, :aliases => '-d', :default => false, :type => :boolean
|
|
22
21
|
|
|
23
22
|
# Show help if no argv given
|
|
24
23
|
def self.start(given_args=ARGV, config={})
|
|
@@ -34,18 +33,17 @@ module Padrino
|
|
|
34
33
|
@model_klass = model.classify.constantize
|
|
35
34
|
@model_plural = model.underscore.pluralize
|
|
36
35
|
@model_singular = model.underscore
|
|
37
|
-
@app_root = File.join(options[:root] || '.', options[:admin_path])
|
|
38
36
|
self.behavior = :revoke if options[:destroy]
|
|
39
37
|
|
|
40
|
-
template "templates/page/controller.rb.tt", destination_root(
|
|
41
|
-
template "templates/page/views/_form.haml.tt", destination_root(
|
|
42
|
-
template "templates/page/views/edit.haml.tt", destination_root(
|
|
43
|
-
template "templates/page/views/grid.js.erb.tt", destination_root(
|
|
44
|
-
template "templates/page/views/new.haml.tt", destination_root(
|
|
45
|
-
template "templates/page/views/store.jml.tt", destination_root(
|
|
38
|
+
template "templates/page/controller.rb.tt", destination_root("/admin/controllers/#{@model_plural}.rb")
|
|
39
|
+
template "templates/page/views/_form.haml.tt", destination_root("/admin/views/#{@model_plural}/_form.haml")
|
|
40
|
+
template "templates/page/views/edit.haml.tt", destination_root("/admin/views/#{@model_plural}/edit.haml")
|
|
41
|
+
template "templates/page/views/grid.js.erb.tt", destination_root("/admin/views/#{@model_plural}/grid.js.erb")
|
|
42
|
+
template "templates/page/views/new.haml.tt", destination_root("/admin/views/#{@model_plural}/new.haml")
|
|
43
|
+
template "templates/page/views/store.jml.tt", destination_root("/admin/views/#{@model_plural}/store.jml")
|
|
46
44
|
|
|
47
|
-
add_access_control_permission(
|
|
48
|
-
include_component_module_for(:test)
|
|
45
|
+
add_access_control_permission("/admin", @model_plural)
|
|
46
|
+
include_component_module_for(:test) if test?
|
|
49
47
|
generate_controller_test(model.downcase.pluralize)
|
|
50
48
|
else
|
|
51
49
|
say "You are not at the root of a Padrino application! (config/boot.rb not found)" and return unless in_app_root?
|
|
@@ -15,21 +15,25 @@ module Padrino
|
|
|
15
15
|
include Padrino::Generators::Actions
|
|
16
16
|
|
|
17
17
|
desc "Description:\n\n\tpadrino-gen admin_uploader Name"
|
|
18
|
-
class_option :
|
|
19
|
-
class_option :
|
|
20
|
-
class_option :destroy, :desc => "Destroy the uploader", :aliases => '-d', :default => false, :type => :boolean
|
|
18
|
+
class_option :root, :desc => "The root destination", :aliases => '-r', :type => :string, :default => "."
|
|
19
|
+
class_option :destroy, :desc => "Destroy the uploader", :aliases => '-d', :default => false, :type => :boolean
|
|
21
20
|
|
|
22
21
|
# Create controller for admin
|
|
23
22
|
def create_controller
|
|
24
23
|
self.destination_root = options[:root]
|
|
25
24
|
if in_app_root?
|
|
26
|
-
@app_root = File.join(options[:root], options[:admin_path])
|
|
27
|
-
self.behavior = :revoke if options[:destroy]
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
unless File.exist?(destination_root("admin"))
|
|
27
|
+
say "<= You need to create an admin application first!"
|
|
28
|
+
raise SystemExit
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
self.behavior = :revoke if options[:destroy]
|
|
32
|
+
|
|
33
|
+
copy_file "templates/uploader/controller.rb", destination_root("/admin/controllers/uploads.rb")
|
|
34
|
+
copy_file "templates/uploader/views/grid.js.erb", destination_root("/admin/views/uploads/grid.js.erb")
|
|
35
|
+
copy_file "templates/uploader/views/store.jml", destination_root("/admin/views/uploads/store.jml")
|
|
36
|
+
copy_file "templates/uploader/lib/uploader.rb", destination_root("lib", "uploader.rb")
|
|
33
37
|
|
|
34
38
|
Padrino::Generators::Model.dup.start([
|
|
35
39
|
"upload", "file:string", "created_at:datetime",
|
|
@@ -60,18 +64,17 @@ module Padrino
|
|
|
60
64
|
end
|
|
61
65
|
end
|
|
62
66
|
|
|
63
|
-
add_permission(
|
|
67
|
+
add_permission("/admin", "role.project_module :uploads, \"/admin/uploads.js\"")
|
|
64
68
|
|
|
65
69
|
return if self.behavior == :revoke
|
|
66
|
-
|
|
67
70
|
say (<<-TEXT).gsub(/ {10}/,'')
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
=================================================================
|
|
70
73
|
Your admin uploader is installed, now follow this steps:
|
|
71
|
-
|
|
74
|
+
=================================================================
|
|
72
75
|
1) Run migrations
|
|
73
76
|
2) That's all!!
|
|
74
|
-
|
|
77
|
+
=================================================================
|
|
75
78
|
|
|
76
79
|
TEXT
|
|
77
80
|
else
|
|
@@ -19,7 +19,7 @@ class Admin < Padrino::Application
|
|
|
19
19
|
layout false
|
|
20
20
|
enable :authentication
|
|
21
21
|
disable :store_location
|
|
22
|
-
set :login_page, "
|
|
22
|
+
set :login_page, "/admin/sessions/new"
|
|
23
23
|
|
|
24
24
|
access_control.roles_for :any do |role|
|
|
25
25
|
role.allow "/sessions"
|
|
@@ -30,8 +30,8 @@ class Admin < Padrino::Application
|
|
|
30
30
|
role.allow "/"
|
|
31
31
|
|
|
32
32
|
role.project_module :accounts do |project|
|
|
33
|
-
project.menu :list, "
|
|
34
|
-
project.menu :new, "
|
|
33
|
+
project.menu :list, "/admin/accounts.js"
|
|
34
|
+
project.menu :new, "/admin/accounts/new"
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -182,7 +182,7 @@ Admin.app = function(){
|
|
|
182
182
|
submitForm: function(id){
|
|
183
183
|
var form = Ext.fly(id);
|
|
184
184
|
form.mask(Admin.locale.messages.wait.message);
|
|
185
|
-
Ext.Ajax.request({ form: form, callback: function(){ Ext.fly(id).unmask() } });
|
|
185
|
+
Ext.Ajax.request({ form: form, callback: function(){ Ext.fly(id).unmask() }, method: form.dom.method });
|
|
186
186
|
}, // submitForms
|
|
187
187
|
|
|
188
188
|
setTitle: function(title){
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module Padrino
|
|
2
|
+
module Admin
|
|
3
|
+
module Helpers
|
|
4
|
+
module AuthenticationHelpers
|
|
5
|
+
##
|
|
6
|
+
# Returns true if +current_account+ is logged and active.
|
|
7
|
+
#
|
|
8
|
+
def logged_in?
|
|
9
|
+
!current_account.nil?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Returns the current_account, it's an instance of <tt>Account</tt> model
|
|
14
|
+
#
|
|
15
|
+
def current_account
|
|
16
|
+
@current_account ||= login_from_session
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Return the admin menu
|
|
21
|
+
#
|
|
22
|
+
def admin_menu
|
|
23
|
+
return "[]" unless current_account
|
|
24
|
+
access_control.auths(current_account).project_modules.collect(&:config).to_json
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Ovverride the current_account, you must provide an instance of Account Model
|
|
29
|
+
#
|
|
30
|
+
# ==== Examples:
|
|
31
|
+
#
|
|
32
|
+
# set_current_account(Account.authenticate(params[:email], params[:password])
|
|
33
|
+
#
|
|
34
|
+
def set_current_account(account=nil)
|
|
35
|
+
session[options.session_id] = account ? account.id : nil
|
|
36
|
+
@current_account = account
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Returns true if the +current_account+ is allowed to see the requested path
|
|
41
|
+
#
|
|
42
|
+
# For configure this role please refer to: +Padrino::Admin::AccessControl::Base+
|
|
43
|
+
#
|
|
44
|
+
def allowed?
|
|
45
|
+
access_control.auths(current_account).can?(request.path_info)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Returns a helper useful in a +before_filter+ for check if
|
|
50
|
+
# an account are: +logged_in?+ and +allowed?+
|
|
51
|
+
#
|
|
52
|
+
# By default this method is used in Admin Apps.
|
|
53
|
+
#
|
|
54
|
+
def login_required
|
|
55
|
+
store_location! if store_location
|
|
56
|
+
return access_denied unless allowed?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Store in session[:return_to] the env['HTTP_REFERER']
|
|
61
|
+
#
|
|
62
|
+
def store_location!
|
|
63
|
+
session[:return_to] = env['HTTP_REFERER']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Redirect the account to the page that requested an authentication or
|
|
68
|
+
# if the account is not allowed/logged return it to a default page
|
|
69
|
+
#
|
|
70
|
+
def redirect_back_or_default(default)
|
|
71
|
+
return_to = session.delete(:return_to)
|
|
72
|
+
redirect(return_to || default)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
def access_denied
|
|
77
|
+
# If request a javascript we alert the user
|
|
78
|
+
if request.xhr? || content_type == :js
|
|
79
|
+
halt 401, "alert('Protected resource')"
|
|
80
|
+
# If we have a login_page we redirect the user
|
|
81
|
+
elsif login_page
|
|
82
|
+
redirect(login_page)
|
|
83
|
+
# If no match we halt with 401
|
|
84
|
+
else
|
|
85
|
+
halt 401, "You don't have permission for this resource"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def login_page
|
|
90
|
+
options.login_page rescue nil # on sinatra 9.4.x respond_to?(:login_page) didn't work
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def store_location
|
|
94
|
+
options.store_location rescue false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def login_from_session
|
|
98
|
+
Account.first(:conditions => { :id => session[options.session_id] }) if defined?(Account)
|
|
99
|
+
end
|
|
100
|
+
end # AuthenticationHelpers
|
|
101
|
+
end # Helpers
|
|
102
|
+
end # Admin
|
|
103
|
+
end # Padrino
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
module Padrino
|
|
2
|
+
module Admin
|
|
3
|
+
module Helpers
|
|
4
|
+
module ViewHelpers
|
|
5
|
+
##
|
|
6
|
+
# Set the title of the page.
|
|
7
|
+
#
|
|
8
|
+
# An interesting thing wose that this helper
|
|
9
|
+
# try to translate itself to your current locale ex:
|
|
10
|
+
#
|
|
11
|
+
# ==== Examples
|
|
12
|
+
#
|
|
13
|
+
# # Look for: I18n.t("admin.titles.welcome_here", :default => "Welcome Here")
|
|
14
|
+
# title :welcome_here
|
|
15
|
+
# # In this case we provide a +String+ so we don't try to translate them.
|
|
16
|
+
# title "This is my page title"
|
|
17
|
+
#
|
|
18
|
+
def title(title)
|
|
19
|
+
title = I18n.t("admin.titles.#{title}", :default => title.to_s) if title.is_a?(Symbol)
|
|
20
|
+
tag(:span, :title => title)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# This method work like error_message_for but use an Ext.Message.show({..}).
|
|
25
|
+
#
|
|
26
|
+
# It can return a list of errors like error_messages_for do but also (if no errors occours)
|
|
27
|
+
# a great message box that indicate the success of the action.
|
|
28
|
+
#
|
|
29
|
+
# ==== Options
|
|
30
|
+
#
|
|
31
|
+
# :url:: Used for change the <form.action>
|
|
32
|
+
# :method:: Used for change the <form.method>
|
|
33
|
+
# :header_tag:: Used for the header of the error div (default: "h2").
|
|
34
|
+
# :id:: The id of the error div (default: "errorExplanation").
|
|
35
|
+
# :class:: The class of the error div (default: "errorExplanation").
|
|
36
|
+
# :object:: The object (or array of objects) for which to display errors,
|
|
37
|
+
# if you need to escape the instance variable convention.
|
|
38
|
+
# :object_name:: The object name to use in the header, or any text that you prefer.
|
|
39
|
+
# If +:object_name+ is not set, the name of the first object will be used.
|
|
40
|
+
# :header_message:: The message in the header of the error div. Pass +nil+
|
|
41
|
+
# or an empty string to avoid the header message altogether. (Default: "X errors
|
|
42
|
+
# prohibited this object from being saved").
|
|
43
|
+
# :message:: The explanation message after the header message and before
|
|
44
|
+
# the error list. Pass +nil+ or an empty string to avoid the explanation message
|
|
45
|
+
# altogether. (Default: "There were problems with the following fields:").
|
|
46
|
+
#
|
|
47
|
+
# ==== Examples
|
|
48
|
+
#
|
|
49
|
+
# # Show an window with errors or "congratulations" and point form.action to:
|
|
50
|
+
# # <form action="/admin/accounts/update/123.js" method="put">
|
|
51
|
+
# show_messages_for :account
|
|
52
|
+
#
|
|
53
|
+
# # Show an window with errors or "congratulations" and point form.action to:
|
|
54
|
+
# # <form action="/admin/accounts/create.js" method="create">
|
|
55
|
+
# show_messages_for :account, :url => url(:accounts_create), :method => :create
|
|
56
|
+
#
|
|
57
|
+
def show_messages_for(object, options={})
|
|
58
|
+
object = object.is_a?(Symbol) ? instance_variable_get("@#{object}") : object
|
|
59
|
+
count = object.errors.count
|
|
60
|
+
error_cleaner = object.class.properties.map { |field|
|
|
61
|
+
(<<-JAVASCRIPT).gsub(/ {12}/, '')
|
|
62
|
+
parent.body.select('*[id=#{object.class.name.underscore}_#{field.name}]').each(function(field){
|
|
63
|
+
field.removeClass('x-form-invalid');
|
|
64
|
+
});
|
|
65
|
+
JAVASCRIPT
|
|
66
|
+
}.join("\n")
|
|
67
|
+
|
|
68
|
+
unless count.zero?
|
|
69
|
+
html = {}
|
|
70
|
+
options[:object_name] ||= object.class
|
|
71
|
+
|
|
72
|
+
I18n.with_options :locale => options[:locale], :scope => [:models, :errors, :template] do |locale|
|
|
73
|
+
header_message = if options.include?(:header_message)
|
|
74
|
+
escape_javascript(options[:header_message])
|
|
75
|
+
else
|
|
76
|
+
object_name = options[:object_name].to_s.gsub('_', ' ')
|
|
77
|
+
object_name = I18n.t(object_name, :default => object_name, :scope => [:models], :count => 1)
|
|
78
|
+
escape_javascript(locale.t :header, :count => count, :model => object_name)
|
|
79
|
+
end
|
|
80
|
+
message = escape_javascript(options.include?(:message) ? options[:message] : locale.t(:body))
|
|
81
|
+
error_messages = object.errors.full_messages.map { |msg| content_tag(:li, escape_javascript(msg)) }.join
|
|
82
|
+
error_highlighter = object.errors_keys.map { |column|
|
|
83
|
+
(<<-JAVASCRIPT).gsub(/ {16}/, '')
|
|
84
|
+
parent.body.select('*[id=#{object.class.name.underscore}_#{column}]').each(function(field){
|
|
85
|
+
field.addClass('x-form-invalid');
|
|
86
|
+
});
|
|
87
|
+
JAVASCRIPT
|
|
88
|
+
}.join("\n")
|
|
89
|
+
contents = ''
|
|
90
|
+
contents << content_tag(:p, message) if message.present?
|
|
91
|
+
contents << content_tag(:ul, error_messages, :class => :list)
|
|
92
|
+
|
|
93
|
+
(<<-JAVASCRIPT).gsub(/ {14}/, '')
|
|
94
|
+
var parent = Ext.WindowMgr.getActive();
|
|
95
|
+
#{error_cleaner}
|
|
96
|
+
Ext.Msg.show({
|
|
97
|
+
title: '#{header_message}',
|
|
98
|
+
msg: '#{contents}',
|
|
99
|
+
buttons: Ext.Msg.OK,
|
|
100
|
+
minWidth: 400
|
|
101
|
+
});
|
|
102
|
+
#{error_highlighter}
|
|
103
|
+
JAVASCRIPT
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
options[:url] ||= url("#{object.class.name.underscore.pluralize}_update".to_sym, :id => object.id, :format => :js)
|
|
107
|
+
options[:method] ||= "put"
|
|
108
|
+
(<<-JAVASCRIPT).gsub(/ {12}/, '')
|
|
109
|
+
var parent = Ext.WindowMgr.getActive();
|
|
110
|
+
var form = parent.body.select('form').first().dom;
|
|
111
|
+
form.action = '#{options[:url]}';
|
|
112
|
+
form.method = '#{options[:method]}';
|
|
113
|
+
#{error_cleaner}
|
|
114
|
+
Ext.Msg.alert(Admin.locale.messages.compliments.title, Admin.locale.messages.compliments.message);
|
|
115
|
+
JAVASCRIPT
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# This method add tab for in your view.
|
|
121
|
+
#
|
|
122
|
+
# First argument is the name and title of the tab, an interesting thing wose that this helper
|
|
123
|
+
# try to translate itself to your current locale ex:
|
|
124
|
+
#
|
|
125
|
+
# === Examples
|
|
126
|
+
#
|
|
127
|
+
# # Look for: I18n.t("admin.tabs.settings", :default => "Settings")
|
|
128
|
+
# tab :settings do
|
|
129
|
+
# ...
|
|
130
|
+
# # In this case we provide a +String+ so we don't try to translate them.
|
|
131
|
+
# tab "This is my tab title"
|
|
132
|
+
#
|
|
133
|
+
def tab(name, options={}, &block)
|
|
134
|
+
options[:id] ||= name.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/-+$/, '').gsub(/^-+$/, '')
|
|
135
|
+
options[:style] = "padding:10px;#{options[:style]}"
|
|
136
|
+
options[:title] = name.is_a?(Symbol) ? I18n.t("admin.tabs.#{name.to_s.downcase}", :default => name.to_s.humanize) : name
|
|
137
|
+
options[:tabbed] = true
|
|
138
|
+
options[:class] = "x-hide-display"
|
|
139
|
+
container = content_tag(:div, capture_html(&block), :class => :full) # Is necessary for IE6+
|
|
140
|
+
concat_content content_tag(:div, container, options)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# This method generates a new ExtJs BoxComponent.
|
|
145
|
+
#
|
|
146
|
+
# Our box can be collapsible so you can minimize them.
|
|
147
|
+
# When you click on a box it will be expanded while others will be minimized (if all are collapsible)
|
|
148
|
+
#
|
|
149
|
+
# ==== Options
|
|
150
|
+
#
|
|
151
|
+
# :collapsible:: Indicate if the box can be minimized (clicking on the header). Default +false+
|
|
152
|
+
# :start:: Indicate if the box on rendering is minimized or expanded. Can be: +open+ or +close+. Default +open+.
|
|
153
|
+
# :style:: Stylesheet of the box container.
|
|
154
|
+
# :class:: CSS Class of the box container.
|
|
155
|
+
#
|
|
156
|
+
# ==== Examples
|
|
157
|
+
#
|
|
158
|
+
# # Create an expanded box that is not collapsible.
|
|
159
|
+
# -box "My Box 1", "My Subtitle" do
|
|
160
|
+
# my content
|
|
161
|
+
#
|
|
162
|
+
# # Create an expanded box that is collapsible.
|
|
163
|
+
# -box "My Box 2", "My Subtitle", :collapsible => true do
|
|
164
|
+
# my content
|
|
165
|
+
#
|
|
166
|
+
# # Create a minimized box that is collapsible.
|
|
167
|
+
# -box "My Box 3", "My Subtitle", :collapsible => true, :start => :close do
|
|
168
|
+
# my content
|
|
169
|
+
#
|
|
170
|
+
# In this example when you click on "My Box 3", "My Box 2" will be minimized and nothing will happen on "My Box 1".
|
|
171
|
+
# When you click on "My Box 2" it will be expanded and "My Box 3" will be minimized and nothing will happen on "My Box 1"
|
|
172
|
+
#
|
|
173
|
+
# Also box title can be translated if you provide a +Symbol+ so if you create a box like:
|
|
174
|
+
#
|
|
175
|
+
# # Look for: I18n.t("admin.boxs.foo_bar", :default => "Foo Bar")
|
|
176
|
+
# -box :foo_bar do
|
|
177
|
+
# ...
|
|
178
|
+
#
|
|
179
|
+
def box(title=nil, options={}, &block)
|
|
180
|
+
title = I18n.t("admin.boxs.#{title}", :default => title.to_s.humanize) if title.is_a?(Symbol)
|
|
181
|
+
subtitle = options.delete(:subtitle)
|
|
182
|
+
options[:style] ||= "width:100%;"
|
|
183
|
+
options[:start] ||= :open
|
|
184
|
+
concat_content (<<-HTML).gsub(/ {10}/, '')
|
|
185
|
+
<div class="#{options[:class]}" style="options[:style]">
|
|
186
|
+
<div class="x-box">
|
|
187
|
+
<div class="x-box-tl">
|
|
188
|
+
<div class="x-box-tr">
|
|
189
|
+
<div class="x-box-tc"> </div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="x-box-ml">
|
|
193
|
+
<div class="x-box-mr">
|
|
194
|
+
<div class="x-box-mc">
|
|
195
|
+
<div id="x-body-title" style="#{"cursor:pointer" if options[:collapsible]}" onclick="#{"Admin.app.collapseBoxes(this);" if options[:collapsible]}">
|
|
196
|
+
#{"<h3 style=\"margin-bottom:0px;padding-bottom:0px;float:left;\">"+title+"</h3>" if title.present?}
|
|
197
|
+
#{"<div style=\"float:right\"><em>"+subtitle+"</em></div>" if subtitle.present?}
|
|
198
|
+
#{"<br class=\"clear\" />" if title.present? || subtitle.present?}
|
|
199
|
+
#{"<div style=\"font-size:0px\"> </div>" if title.present? || subtitle.present?}
|
|
200
|
+
</div>
|
|
201
|
+
<div class="#{"x-box-collapsible" if options[:collapsible]}" style="width:99%;#{"display:none" if options[:collapsible] && options[:start] == :close}">
|
|
202
|
+
#{"<div style=\"font-size:10px\"> </div>" if title.present? || subtitle.present?}
|
|
203
|
+
#{capture_html(&block)}
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="x-box-bl">
|
|
209
|
+
<div class="x-box-br">
|
|
210
|
+
<div class="x-box-bc"> </div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
HTML
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# Open a Standard window that can contain a grid. Works like a select_tag.
|
|
220
|
+
#
|
|
221
|
+
# ==== Options
|
|
222
|
+
#
|
|
223
|
+
# :with:: name of the association. Ex: :images.
|
|
224
|
+
# :url:: the url of the gird, by default we autogenerate them using +:with+ ex: url(:images_index, :format => :js)
|
|
225
|
+
# :controller:: the name of the controller that contains the gird, by default is +:with+
|
|
226
|
+
# :show:: value to display, if it's a symbol we tranform them using +with+ ex: :name, will be: data["images.name"]
|
|
227
|
+
# :get:: value to store in our db, Default: +:id+
|
|
228
|
+
# :grid:: the name of the var of Admin.grid. Default :grid
|
|
229
|
+
# :item:: the name of container. This necessary in cases where our grid it's in another container.
|
|
230
|
+
# :update:: javascript function to handle when the user close grid with some selections. Default autogenerated.
|
|
231
|
+
# :prompt:: text or html tag to prompt. Default is autogenerated.
|
|
232
|
+
# :prompt_destroy:: text or html tag to prompt for clear current selections. Default is autogenerated.
|
|
233
|
+
# :function:: name of the function. Default is autogenerated.
|
|
234
|
+
# :multiple:: if true returns a collections of +:value+. Default false.
|
|
235
|
+
#
|
|
236
|
+
# ==== Examples
|
|
237
|
+
#
|
|
238
|
+
# # Generate:
|
|
239
|
+
# # <script type="text/javascript">
|
|
240
|
+
# # function showAccountImages(){
|
|
241
|
+
# # Ext.Ajax.request({
|
|
242
|
+
# # url: '/admin/images.js',
|
|
243
|
+
# # scripts: false,
|
|
244
|
+
# # success: function(response){
|
|
245
|
+
# # try { eval(response.responseText) } catch(e) { Admin.app.error(e) };
|
|
246
|
+
# # var win = new Admin.window({ grid: grid, width: 800, height: 600 });
|
|
247
|
+
# # win.on('selected', function(win, selections){ ... });
|
|
248
|
+
# # win.show();
|
|
249
|
+
# # }
|
|
250
|
+
# # });
|
|
251
|
+
# # }
|
|
252
|
+
# # </script>
|
|
253
|
+
# # <ul id="account_images">
|
|
254
|
+
# # <li>
|
|
255
|
+
# # <span id="display">Foo</span>
|
|
256
|
+
# # <input type="hidden" name="account[image_ids][]" value = "1" />
|
|
257
|
+
# # <a href="#" onclick="this.up('li').destroy(); return false">Remove</a>
|
|
258
|
+
# # </li>
|
|
259
|
+
# # <li>
|
|
260
|
+
# # <span id="display">Bar</span>
|
|
261
|
+
# # <input type="hidden" name="account[image_ids][]" value = "2" />
|
|
262
|
+
# # <a href="#" onclick="this.up('li').destroy(); return false">Remove</a>
|
|
263
|
+
# # </li>
|
|
264
|
+
# # </ul>
|
|
265
|
+
# # <a href="#" onclick="showAccountImages(); return false">Show Images</a>
|
|
266
|
+
# open_grid :account, :image_ids, :with => :images, :show => :name, :get => :id, :multiple => true
|
|
267
|
+
#
|
|
268
|
+
def open_window_grid(object_name, method, options={})
|
|
269
|
+
|
|
270
|
+
# We need plural version of our association
|
|
271
|
+
options[:controller] ||= options[:with]
|
|
272
|
+
controller = options[:multiple] ? options[:controller] : "#{options[:controller].to_s.pluralize}".to_sym
|
|
273
|
+
|
|
274
|
+
# Now we need our association
|
|
275
|
+
association = instance_variable_get("@#{object_name}").send(options[:with])
|
|
276
|
+
|
|
277
|
+
# Parsing not mandatory options
|
|
278
|
+
options[:url] ||= url("#{controller}_index".to_sym, :format => :js, :small => true)
|
|
279
|
+
options[:grid] ||= :grid
|
|
280
|
+
options[:item] ||= :undefined
|
|
281
|
+
options[:get] ||= :id
|
|
282
|
+
options[:prompt] ||= image_tag("admin/new.gif", :style => "vertical-align:bottom;padding:2px")
|
|
283
|
+
options[:function] ||= "show#{object_name.to_s.capitalize}#{options[:with].to_s.capitalize}"
|
|
284
|
+
|
|
285
|
+
# Here we build our html template
|
|
286
|
+
input_name = "#{object_name}[#{method}]"
|
|
287
|
+
input_name += "[]" if options[:multiple]
|
|
288
|
+
|
|
289
|
+
# We need always some defaults values
|
|
290
|
+
defaults = hidden_field_tag(input_name)
|
|
291
|
+
|
|
292
|
+
# Now a reusable (also from extjs) template
|
|
293
|
+
template = (<<-HTML).gsub(/ {10}/, "")
|
|
294
|
+
<li>
|
|
295
|
+
<span class="display">{0}</span>
|
|
296
|
+
<input type="hidden" name="#{input_name}" value="{1}">
|
|
297
|
+
<a href="#" onclick="this.up(\\'li\\').remove(); return false" class="closebutton"> </a>
|
|
298
|
+
</li>
|
|
299
|
+
HTML
|
|
300
|
+
|
|
301
|
+
# We create a collection of <li>
|
|
302
|
+
collection = Array(association).map do |item|
|
|
303
|
+
template.gsub("{0}", item.send(options[:show]).to_s).
|
|
304
|
+
gsub("{1}", item.send(options[:get]).to_s)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# And we add our link for add new records
|
|
308
|
+
li_link = content_tag(:li, link_to(options[:prompt], "#", :onclick => "#{options[:function]}(); return false;"), :class => :clean)
|
|
309
|
+
collection << li_link
|
|
310
|
+
|
|
311
|
+
# Now we have the final container
|
|
312
|
+
container = content_tag(:ul, defaults + collection.join.gsub("\\",""), :id => "#{object_name}_#{method}", :class => "open-window-grid")
|
|
313
|
+
|
|
314
|
+
# We need to refactor some values
|
|
315
|
+
show = "data['#{controller}.#{options[:show]}']" if options[:show].is_a?(Symbol)
|
|
316
|
+
get = options[:get].is_a?(Symbol) && options[:get] != :id ? "data['#{controller}.#{options[:get]}']" : options[:get]
|
|
317
|
+
|
|
318
|
+
# Updater handler
|
|
319
|
+
update_function = if options[:multiple]
|
|
320
|
+
(<<-JAVASCRIPT).gsub(/ {12}/, "")
|
|
321
|
+
var parent = win.parent.body.select("*[id=#{object_name}_#{method}]").first();
|
|
322
|
+
Ext.each(selections, function(selection){
|
|
323
|
+
var template = String.format('#{template.gsub(/\n/,"")}', selection.#{show}, selection.#{get});
|
|
324
|
+
parent.insertHtml('afterBegin', template);
|
|
325
|
+
});
|
|
326
|
+
JAVASCRIPT
|
|
327
|
+
else
|
|
328
|
+
(<<-JAVASCRIPT).gsub(/ {12}/, "")
|
|
329
|
+
var selection = selections.first();
|
|
330
|
+
var template = String.format('#{template.gsub(/\n/,"")}', selection.#{show}, selection.#{get}) + '#{li_link}';
|
|
331
|
+
win.parent.body.select("*[id=#{object_name}_#{method}]").first().update(template);
|
|
332
|
+
JAVASCRIPT
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Now we build the update function (if not present)
|
|
336
|
+
javascript = (<<-JAVASCRIPT).gsub(/ {10}/, '')
|
|
337
|
+
function #{options[:function]}(){
|
|
338
|
+
var me = Ext.WindowMgr.getActive();
|
|
339
|
+
Ext.Ajax.request({
|
|
340
|
+
url: '#{options[:url]}',
|
|
341
|
+
scripts: false,
|
|
342
|
+
success: function(response){
|
|
343
|
+
try { eval(response.responseText) } catch(e) { Admin.app.error(e) };
|
|
344
|
+
var win = new Admin.window({ grid: #{options[:grid]}, item: #{options[:item]}, width: 800, height:600, modal: true, parent:me });
|
|
345
|
+
win.on('selected', function(win, selections){
|
|
346
|
+
#{update_function}
|
|
347
|
+
});
|
|
348
|
+
win.show();
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
JAVASCRIPT
|
|
353
|
+
|
|
354
|
+
# Now we return our html code
|
|
355
|
+
[content_tag(:script, javascript, :type => 'text/javascript'), container, tag(:div, :class => :clear)].join("\n")
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
module AbstractFormBuilder #:nodoc:
|
|
359
|
+
# f.open_window_grid :upload_ids, :brand_ids, :with => :brands, :get => :id, :show => :name
|
|
360
|
+
def open_window_grid(field, options={})
|
|
361
|
+
@template.open_window_grid object_name, field, options
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end # ViewHelpers
|
|
365
|
+
end # Helpers
|
|
366
|
+
end # Admin
|
|
367
|
+
end # Padrino
|