padrino-admin 0.7.9 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|