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 CHANGED
@@ -13,7 +13,7 @@ Ajax Uploads:: You can upload file, manage them and attach them to any model in
13
13
 
14
14
  Create a project:
15
15
 
16
- $ padrino-gen app fun-test
16
+ $ padrino-gen project fun-test
17
17
  $ cd fun-test
18
18
 
19
19
  For create the admin application:
data/Rakefile CHANGED
@@ -42,17 +42,15 @@ end
42
42
 
43
43
  begin
44
44
  require 'rcov/rcovtask'
45
- Rcov::RcovTask.new do |test|
46
- Dir[File.dirname(__FILE__) + "/../padrino-*/lib"].each do |padrino_gem|
47
- test.libs << padrino_gem
48
- end
49
- test.libs << 'test'
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 spicycode-rcov"
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.7.9
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 true if the requested path (like request.path_info) is +not+ allowed.
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", :aliases => '-r', :default => ".", :type => :string
20
- class_option :path, :aliases => '-p', :type => :string, :default => "admin"
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(options[:path]))
36
- directory("assets/", destination_root("public", options[:path]))
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(\"/#{@app_path}\")"
43
+ append_file destination_root("config/apps.rb"), "\nPadrino.mount(\"Admin\").to(\"/admin\")"
46
44
 
47
- unless options[:destroy]
48
- say (<<-TEXT).gsub(/ {12}/,'')
45
+ return if self.behavior == :revoke
46
+ say (<<-TEXT).gsub(/ {10}/,'')
49
47
 
50
- -----------------------------------------------------------------
51
- Your admin now is installed, now follow this steps:
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
- 1) Run migrations
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 :admin_path, :aliases => '-p', :type => :string, :default => "admin"
20
- class_option :root, :desc => "The root destination", :aliases => '-r', :type => :string
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(options[:admin_path], "/controllers/#{@model_plural}.rb")
41
- template "templates/page/views/_form.haml.tt", destination_root(options[:admin_path], "/views/#{@model_plural}/_form.haml")
42
- template "templates/page/views/edit.haml.tt", destination_root(options[:admin_path], "/views/#{@model_plural}/edit.haml")
43
- template "templates/page/views/grid.js.erb.tt", destination_root(options[:admin_path], "/views/#{@model_plural}/grid.js.erb")
44
- template "templates/page/views/new.haml.tt", destination_root(options[:admin_path], "/views/#{@model_plural}/new.haml")
45
- template "templates/page/views/store.jml.tt", destination_root(options[:admin_path], "/views/#{@model_plural}/store.jml")
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(options[:admin_path], @model_plural)
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 :admin_path, :desc => "Path where is stored your admin app", :aliases => '-p', :type => :string, :default => "admin"
19
- class_option :root, :desc => "The root destination", :aliases => '-r', :type => :string, :default => "."
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
- copy_file "templates/uploader/controller.rb", destination_root(options[:admin_path], "/controllers/uploads.rb")
30
- copy_file "templates/uploader/views/grid.js.erb", destination_root(options[:admin_path], "/views/uploads/grid.js.erb")
31
- copy_file "templates/uploader/views/store.jml", destination_root(options[:admin_path], "/views/uploads/store.jml")
32
- copy_file "templates/uploader/lib/uploader.rb", destination_root("lib", "uploader.rb")
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(options[:admin_path], "role.project_module :uploads, \"/admin/uploads.js\"")
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, "/<%= @app_path %>/sessions/new"
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, "/<%= @app_path %>/accounts.js"
34
- project.menu :new, "/<%= @app_path %>/accounts/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">&nbsp;</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\">&nbsp;</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\">&nbsp;</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">&nbsp;</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">&nbsp;</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