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 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