padrino-admin 0.6.3 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/README.rdoc +16 -0
  2. data/VERSION +1 -1
  3. data/lib/padrino-admin.rb +24 -5
  4. data/lib/padrino-admin/access_control.rb +75 -29
  5. data/lib/padrino-admin/{ext_js/column_store.rb → column_store.rb} +31 -15
  6. data/lib/padrino-admin/config.rb +36 -0
  7. data/lib/padrino-admin/generators/actions.rb +62 -7
  8. data/lib/padrino-admin/generators/admin_app.rb +27 -26
  9. data/lib/padrino-admin/generators/admin_page.rb +20 -18
  10. data/lib/padrino-admin/generators/admin_uploader.rb +83 -0
  11. data/lib/padrino-admin/generators/app/app.rb.tt +1 -3
  12. data/lib/padrino-admin/generators/app/controllers/accounts.rb +1 -1
  13. data/lib/padrino-admin/generators/app/public/flash/swfupload.swf +0 -0
  14. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/back.gif +0 -0
  15. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/background.png +0 -0
  16. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-content.png +0 -0
  17. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-hd-slate.png +0 -0
  18. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-hd.png +0 -0
  19. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-intro.png +0 -0
  20. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-login.png +0 -0
  21. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-menu-slate.png +0 -0
  22. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-menu.png +0 -0
  23. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg.png +0 -0
  24. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/btn-login.png +0 -0
  25. data/lib/padrino-admin/generators/app/public/images/admin/cancel.gif +0 -0
  26. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/categories.gif +0 -0
  27. data/lib/padrino-admin/generators/app/public/images/admin/close.gif +0 -0
  28. data/lib/padrino-admin/generators/app/public/images/admin/close.png +0 -0
  29. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/delete.gif +0 -0
  30. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/download.gif +0 -0
  31. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/duplicate.gif +0 -0
  32. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/edit.gif +0 -0
  33. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/export.gif +0 -0
  34. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/hd-bg.gif +0 -0
  35. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/image.gif +0 -0
  36. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/loader.gif +0 -0
  37. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/logo-loader.png +0 -0
  38. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/logo-small.png +0 -0
  39. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/new.gif +0 -0
  40. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/no-image.png +0 -0
  41. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/preview.gif +0 -0
  42. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/print.gif +0 -0
  43. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/save.gif +0 -0
  44. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/support.gif +0 -0
  45. data/lib/padrino-admin/generators/app/public/images/admin/up.gif +0 -0
  46. data/lib/padrino-admin/generators/app/public/javascripts/ext.js +4 -1
  47. data/lib/padrino-admin/generators/app/public/javascripts/swfupload.js +4 -0
  48. data/lib/padrino-admin/generators/app/public/stylesheets/admin.css +25 -43
  49. data/lib/padrino-admin/generators/app/public/stylesheets/login.css +3 -3
  50. data/lib/padrino-admin/generators/app/public/stylesheets/standard.css +53 -52
  51. data/lib/padrino-admin/generators/app/views/accounts/_form.haml +1 -0
  52. data/lib/padrino-admin/generators/app/views/base/index.haml +5 -7
  53. data/lib/padrino-admin/generators/app/views/javascripts/admin.js.erb +217 -297
  54. data/lib/padrino-admin/generators/app/views/sessions/new.haml +1 -1
  55. data/lib/padrino-admin/generators/templates/{controller.rb.tt → page/controller.rb.tt} +1 -1
  56. data/lib/padrino-admin/generators/templates/{db → page/db}/seeds.rb.tt +0 -0
  57. data/lib/padrino-admin/generators/templates/{views → page/views}/_form.haml.tt +0 -0
  58. data/lib/padrino-admin/generators/templates/{views → page/views}/edit.haml.tt +0 -0
  59. data/lib/padrino-admin/generators/templates/{views → page/views}/grid.js.erb.tt +1 -1
  60. data/lib/padrino-admin/generators/templates/{views → page/views}/new.haml.tt +0 -0
  61. data/lib/padrino-admin/generators/templates/{views → page/views}/store.jml.tt +0 -0
  62. data/lib/padrino-admin/generators/templates/uploader/controller.rb +24 -0
  63. data/lib/padrino-admin/generators/templates/uploader/lib/uploader.rb +54 -0
  64. data/lib/padrino-admin/generators/templates/uploader/views/grid.js.erb +56 -0
  65. data/lib/padrino-admin/generators/templates/uploader/views/store.jml +10 -0
  66. data/lib/padrino-admin/helpers/authentication.rb +30 -19
  67. data/lib/padrino-admin/helpers/view.rb +172 -35
  68. data/lib/padrino-admin/locale/admin/en.yml +1 -0
  69. data/lib/padrino-admin/middleware/flash_middleware.rb +36 -0
  70. data/lib/padrino-admin/orm.rb +33 -0
  71. data/lib/padrino-admin/orm/abstract.rb +94 -0
  72. data/lib/padrino-admin/{adapters/ar.rb → orm/activerecord.rb} +69 -36
  73. data/lib/padrino-admin/orm/datamapper.rb +214 -0
  74. data/lib/padrino-admin/{adapters/mm.rb → orm/mongomapper.rb} +36 -20
  75. data/lib/padrino-admin/utils/literal.rb +1 -1
  76. data/padrino-admin.gemspec +62 -51
  77. data/test/fixtures/active_record.rb +14 -2
  78. data/test/fixtures/data_mapper.rb +2 -1
  79. data/test/fixtures/mongo_mapper.rb +1 -1
  80. data/test/fixtures/test_column_store.jml +1 -0
  81. data/test/helper.rb +3 -2
  82. data/test/test_access_control.rb +1 -1
  83. data/test/test_active_record.rb +56 -1
  84. data/test/test_column_store.rb +56 -10
  85. data/test/test_data_mapper.rb +67 -1
  86. metadata +58 -47
  87. data/lib/padrino-admin/adapters.rb +0 -108
  88. data/lib/padrino-admin/adapters/dm.rb +0 -147
  89. data/lib/padrino-admin/ext_js/config.rb +0 -186
  90. data/test/fixtures/test_generic.jml +0 -7
  91. data/test/fixtures/test_javascript.jml +0 -81
@@ -17,7 +17,7 @@
17
17
  .ln=password_field_tag :password, :value => params[:password], :style => "width:250px"
18
18
  .padding-top.bottom
19
19
  .left=flash[:notice]
20
- .right=image_submit_tag("../images/backend/btn-login.png")
20
+ .right=image_submit_tag("../images/admin/btn-login.png", :style => "padding:none;border:none;width:72px;height:25px")
21
21
  .clear
22
22
  #copyright
23
23
  Powered by
@@ -32,6 +32,6 @@ Admin.controllers :<%= @model_plural %> do
32
32
  delete :destroy, :respond_to => :json do
33
33
  <%= @model_plural %> = <%= @model_name %>.all(:conditions => { :id => params[:ids].split(",") })
34
34
  errors = <%= @model_plural %>.map { |<%= @model_singular %>| I18n.t("admin.general.cantDelete", :record => <%= @model_singular %>.id) unless <%= @model_singular %>.destroy }.compact
35
- { :success => errors.empty?, :msg => errors.join("<br />") }.to_json
35
+ render :success => errors.empty?, :msg => errors.join("<br />")
36
36
  end
37
37
  end
@@ -6,4 +6,4 @@ var grid = new Admin.grid({
6
6
  store_fields: <%%= @store.store_fields %>
7
7
  });
8
8
 
9
- Admin.app.addItem(grid);
9
+ Admin.app.addItem(grid, '<%%= params[:small] %>');
@@ -0,0 +1,24 @@
1
+ Admin.controllers :uploads do
2
+
3
+ get :index, :respond_to => [:js, :json] do
4
+ @store = Upload.column_store(options.views, "uploads/store")
5
+ @session_id = options.session_id
6
+ case content_type
7
+ when :js then render 'uploads/grid.js'
8
+ when :json then @store.store_data(params)
9
+ end
10
+ end
11
+
12
+ post :create do
13
+ @upload = Upload.new
14
+ @upload.file = params[:file]
15
+ @upload.save
16
+ render :success => true
17
+ end
18
+
19
+ delete :destroy, :respond_to => :json do
20
+ uploads = Upload.all(:conditions => { :id => params[:ids].split(",") })
21
+ errors = uploads.map { |upload| I18n.t("admin.general.cantDelete", :record => upload.id) unless upload.destroy }.compact
22
+ render :success => errors.empty?, :msg => errors.join("<br />")
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ class Uploader < CarrierWave::Uploader::Base
2
+
3
+ # Include RMagick or ImageScience support
4
+ # include CarrierWave::RMagick
5
+ # include CarrierWave::ImageScience
6
+ include CarrierWave::MiniMagick
7
+
8
+ # Choose what kind of storage to use for this uploader
9
+ storage :file
10
+ # storage :s3
11
+
12
+ # Override the directory where uploaded files will be stored
13
+ # This is a sensible default for uploaders that are meant to be mounted:
14
+
15
+ def root
16
+ "shared/public"
17
+ end
18
+
19
+ def store_dir
20
+ "uploads"
21
+ end
22
+
23
+ def cache_dir
24
+ "tmp"
25
+ end
26
+
27
+ # Provide a default URL as a default if there hasn't been a file uploaded
28
+ # def default_url
29
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
30
+ # end
31
+ # Process files as they are uploaded.
32
+ process :resize_to_fit => [640, 480]
33
+ #
34
+ # def scale(width, height)
35
+ # # do something
36
+ # end
37
+
38
+ # Create different versions of your uploaded files
39
+ version :thumb do
40
+ process :resize_to_fit => [128, 128]
41
+ end
42
+
43
+ # Add a white list of extensions which are allowed to be uploaded,
44
+ # for images you might use something like this:
45
+ # def extension_white_list
46
+ # %w(jpg jpeg gif png)
47
+ # end
48
+
49
+ # Override the filename of the uploaded files
50
+ # def filename
51
+ # "something.jpg" if original_filename
52
+ # end
53
+
54
+ end
@@ -0,0 +1,56 @@
1
+ var grid = new Admin.grid({
2
+ id: 'grid-uploads',
3
+ title: 'List Upload',
4
+ baseUrl: '/admin/uploads',
5
+ buttons: ['remove'],
6
+ column_fields: <%= @store.column_fields %>,
7
+ store_fields: <%= @store.store_fields %>
8
+ });
9
+
10
+ grid.on('dblclick', function(r){
11
+ var sel = grid.getSelectionModel().getSelected();
12
+ if (!sel) return;
13
+ Admin.app.mask();
14
+ var image = new Image();
15
+ image.src = sel.data['uploads.file'];
16
+ image.onerror = function(){ Admin.app.unmask() };
17
+ image.onload = function(){
18
+ if (image.width == 0 || image.height == 0) return;
19
+ var win = new Ext.Window({
20
+ resizable: false,
21
+ title: sel.data['uploads.file'],
22
+ html: '<img src="'+sel.data['uploads.file']+'" height="'+image.height+'" width="'+image.width+'" />'
23
+ });
24
+ Admin.app.unmask();
25
+ win.show();
26
+ }
27
+ });
28
+
29
+ var upload = new Ext.SwfUploadPanel({
30
+ id: 'grid-upload',
31
+ title: 'Upload a new file',
32
+ region: 'south',
33
+ collapsible: true,
34
+ collapseMode:'mini',
35
+ split: true,
36
+ single_select: this.single_select,
37
+ file_types: "*.*", // Default allow all file types
38
+ file_types_description: "All Files", // A text description that is displayed to the user in the File Browser dialog.
39
+ file_size_limit: "1MB", // Default size limit 1MB
40
+ file_upload_limit: "0", // Default no upload limit
41
+ file_queue_limit: "0", // Default no queue limit
42
+ upload_url: '/admin/uploads/create',
43
+ post_params: { '<%= @session_id %>': '<%= session[@session_id] %>' },
44
+ flash_url: '<%= uri_root_path("flash/swfupload.swf") %>',
45
+ labelWidth: 110,
46
+ height: 300
47
+ });
48
+
49
+ upload.on('fileUploadComplete', function(){ grid.store.reload() });
50
+
51
+ var panel = new Ext.Panel({
52
+ layout: 'border',
53
+ items: [grid, upload]
54
+ });
55
+
56
+ Admin.app.addItem(panel, '<%= params[:small] %>');
@@ -0,0 +1,10 @@
1
+ columns:
2
+ - method: file.to_s
3
+ dataIndex: uploads.file
4
+ header: File
5
+ - method: size
6
+ renderer: file_size
7
+ sortable: false
8
+ header: Size
9
+ - method: created_at
10
+ renderer: datetime
@@ -1,56 +1,71 @@
1
1
  module Padrino
2
2
  module Admin
3
3
  module Helpers
4
- # Returns true if <tt>current_account</tt> is logged and active.
4
+ ##
5
+ # Returns true if +current_account+ is logged and active.
6
+ #
5
7
  def logged_in?
6
8
  !current_account.nil?
7
9
  end
8
10
 
11
+ ##
9
12
  # Returns the current_account, it's an instance of <tt>Account</tt> model
13
+ #
10
14
  def current_account
11
15
  @current_account ||= login_from_session
12
16
  end
13
17
 
14
- # Return the admin menu
18
+ ##
19
+ # Return the admin menu
20
+ #
15
21
  def admin_menu
16
22
  return "[]" unless current_account
17
23
  access_control.auths(current_account).project_modules.collect(&:config).to_json
18
24
  end
19
25
 
26
+ ##
20
27
  # Ovverride the current_account, you must provide an instance of Account Model
21
28
  #
22
29
  # Examples:
23
30
  #
24
31
  # current_account = Account.last
25
32
  #
26
- def set_current_account(account)
27
- session[session_name] = account.id rescue nil
28
- @current_account = account
33
+ def set_current_account(account=nil)
34
+ session[options.session_id] = account ? account.id : nil
35
+ @current_account = account
29
36
  end
30
37
 
31
- # Returns true if the <tt>current_account</tt> is allowed to see the requested path
38
+ ##
39
+ # Returns true if the +current_account+ is allowed to see the requested path
40
+ #
41
+ # For configure this role please refer to: +Padrino::AccessControl::Base+
32
42
  #
33
- # For configure this role please refer to: <tt>Padrino::AccessControl::Base</tt>
34
43
  def allowed?
35
44
  access_control.auths(current_account).can?(request.path_info)
36
45
  end
37
46
 
38
- # Returns a helper to pass in a <tt>before_filter</tt> for check if
39
- # an account are: <tt>logged_in?</tt> and <tt>allowed?</tt>
47
+ ##
48
+ # Returns a helper to pass in a +before_filter+ for check if
49
+ # an account are: +logged_in?+ and +allowed?+
40
50
  #
41
51
  # By default this method is used in BackendController so is not necessary
52
+ #
42
53
  def login_required
43
54
  store_location! if store_location
44
55
  return access_denied unless allowed?
45
56
  end
46
57
 
58
+ ##
47
59
  # Store in session[:return_to] the request.fullpath
60
+ #
48
61
  def store_location!
49
62
  session[:return_to] = request.fullpath
50
63
  end
51
64
 
65
+ ##
52
66
  # Redirect the account to the page that requested an authentication or
53
67
  # if the account is not allowed/logged return it to a default page
68
+ #
54
69
  def redirect_back_or_default(default)
55
70
  redirect_to(session[:return_to] || default)
56
71
  session[:return_to] = nil
@@ -72,20 +87,16 @@ module Padrino
72
87
  end
73
88
 
74
89
  def login_page
75
- options.login_page rescue nil # for some reason on sinatra 9.4.x respond_to?(:login_page) didn't work
90
+ options.login_page rescue nil # on sinatra 9.4.x respond_to?(:login_page) didn't work
76
91
  end
77
92
 
78
93
  def store_location
79
94
  options.store_location rescue false
80
95
  end
81
96
 
82
- def session_name
83
- options.app_name.to_sym
84
- end
85
-
86
- def login_from_session #:nodoc:
87
- Account.first(:conditions => { :id => session[session_name] }) if defined?(Account)
97
+ def login_from_session
98
+ Account.first(:conditions => { :id => session[options.session_id] }) if defined?(Account)
88
99
  end
89
- end
90
- end
91
- end
100
+ end # Helpers
101
+ end # Admin
102
+ end # Padrino
@@ -1,6 +1,7 @@
1
1
  module Padrino
2
2
  module Admin
3
3
  module Helpers
4
+ ##
4
5
  # Set the title of the page.
5
6
  #
6
7
  # An interesting thing wose that this helper
@@ -11,13 +12,17 @@ module Padrino
11
12
  #
12
13
  def title(title)
13
14
  title = I18n.t("admin.titles.#{title}", :default => title.to_s) if title.is_a?(Symbol)
14
- content_tag(:script, "Admin.app.setTitle(#{title.to_json})", :type => "text/javascript")
15
+ tag(:span, :title => title)
15
16
  end
16
17
 
18
+ ##
17
19
  # This method work like error_message_for but use an Ext.Message.show({..})
20
+ #
18
21
  def show_messages_for(*objects)
19
- options = objects.extract_options!.symbolize_keys
20
- count = objects.inject(0) {|sum, object| sum + object.errors.count }
22
+ options = objects.extract_options!.symbolize_keys
23
+ count = objects.inject(0) {|sum, object| sum + object.errors.count }
24
+ error_cleaner = objects.map{|object| object.class.properties.map{|k| "if ($('#{object.class.to_s.downcase}_#{k.name}')) $('#{object.class.to_s.downcase}_#{k.name}').removeClassName('x-form-invalid');" } }.join("\n")
25
+
21
26
  unless count.zero?
22
27
  html = {}
23
28
  options[:object_name] ||= objects.first.class
@@ -33,13 +38,12 @@ module Padrino
33
38
  message = escape_javascript(options.include?(:message) ? options[:message] : locale.t(:body))
34
39
  error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, escape_javascript(msg)) } }.join
35
40
  error_highlighter = objects.map {|object| object.errors_keys.map{ |k| "$('#{object.class.to_s.downcase}_#{k}').addClassName('x-form-invalid');" } }.join("\n")
36
-
37
41
  contents = ''
38
42
  contents << content_tag(:p, message) if message.present?
39
43
  contents << content_tag(:ul, error_messages, :class => :list)
40
44
 
41
45
  (<<-JAVASCRIPT).gsub(/ {14}/, '')
42
- Admin.app.unmask();
46
+ #{error_cleaner}
43
47
  Ext.Msg.show({
44
48
  title: '#{header_message}',
45
49
  msg: '<ul>#{contents}</ul>',
@@ -51,12 +55,13 @@ module Padrino
51
55
  end
52
56
  else
53
57
  (<<-JAVASCRIPT).gsub(/ {12}/, '')
54
- Admin.app.unmask();
58
+ #{error_cleaner}
55
59
  Ext.Msg.alert(Admin.locale.messages.compliments.title, Admin.locale.messages.compliments.message);
56
60
  JAVASCRIPT
57
61
  end
58
62
  end
59
63
 
64
+ ##
60
65
  # This method add tab for in your view.
61
66
  #
62
67
  # First argument is the name and title of the tab, an interesting thing wose that this helper
@@ -70,8 +75,8 @@ module Padrino
70
75
  #
71
76
  # Third argument is an hash that accepts:
72
77
  #
73
- # <tt>:id</tt>:: The id of the tab
74
- # <tt>:style</tt>:: Custom style of the tab
78
+ # :id:: The id of the tab
79
+ # :style:: Custom style of the tab
75
80
  #
76
81
  def tab(name, padding=true, options={}, &block)
77
82
  options[:id] ||= name.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/-+$/, '').gsub(/^-+$/, '')
@@ -83,6 +88,7 @@ module Padrino
83
88
  concat_content content_tag(:div, container, options)
84
89
  end
85
90
 
91
+ ##
86
92
  # This method generates a new ExtJs BoxComponent.
87
93
  #
88
94
  # Examples:
@@ -96,43 +102,174 @@ module Padrino
96
102
  # * :collapsible => false
97
103
  # * :start => :close
98
104
  #
99
- def box(title=nil, subtitle=nil, options={}, &block)
105
+ def box(title=nil, options={}, &block)
100
106
  title = I18n.t("admin.boxs.#{title.to_s.downcase}", :default => title.to_s.humanize) if title.is_a?(Symbol)
107
+ subtitle = options.delete(:subtitle)
101
108
  options[:style] ||= "width:100%;"
102
109
  options[:start] ||= :open
103
- concat_content <<-HTML
104
- <div class="x-box" style="#{options[:style]}">
105
- <div class="x-box-tl">
106
- <div class="x-box-tr">
107
- <div class="x-box-tc">&nbsp;</div>
110
+ concat_content (<<-HTML).gsub(/ {10}/, '')
111
+ <div class="#{options[:class]}" style="options[:margin]">
112
+ <div class="x-box">
113
+ <div class="x-box-tl">
114
+ <div class="x-box-tr">
115
+ <div class="x-box-tc">&nbsp;</div>
116
+ </div>
108
117
  </div>
109
- </div>
110
- <div class="x-box-ml">
111
- <div class="x-box-mr">
112
- <div class="x-box-mc">
113
- <div id="x-body-title" style="#{"cursor:pointer" if options[:collapsible]}" onclick="#{"Backend.app.collapseBoxes(this);" if options[:collapsible]}">
114
- #{"<h3 style=\"margin-bottom:0px;padding-bottom:0px;float:left;\">"+title+"</h3>" if title.present?}
115
- #{"<div style=\"float:right\"><em>"+subtitle+"</em></div>" if subtitle.present?}
116
- #{"<br class=\"clear\" />" if title.present? || subtitle.present?}
117
- #{"<div style=\"font-size:0px\">&nbsp;</div>" if title.present? || subtitle.present?}
118
- </div>
119
- <div class="#{"x-box-collapsible" if options[:collapsible]}" style="width:99%;#{"display:none" if options[:collapsible] && options[:start] == :close}">
120
- #{"<div style=\"font-size:10px\">&nbsp;</div>" if title.present? || subtitle.present?}
121
- #{capture_html(&block)}
122
- #{"<div style=\"text-align:right;margin-top:10px\">#{submit_tag(I18n.t("lipsiadmin.buttons.save"), :onclick=>"Backend.app.submitForm()")}</div>" if options[:submit]}
118
+ <div class="x-box-ml">
119
+ <div class="x-box-mr">
120
+ <div class="x-box-mc">
121
+ <div id="x-body-title" style="#{"cursor:pointer" if options[:collapsible]}" onclick="#{"Backend.app.collapseBoxes(this);" if options[:collapsible]}">
122
+ #{"<h3 style=\"margin-bottom:0px;padding-bottom:0px;float:left;\">"+title+"</h3>" if title.present?}
123
+ #{"<div style=\"float:right\"><em>"+subtitle+"</em></div>" if subtitle.present?}
124
+ #{"<br class=\"clear\" />" if title.present? || subtitle.present?}
125
+ #{"<div style=\"font-size:0px\">&nbsp;</div>" if title.present? || subtitle.present?}
126
+ </div>
127
+ <div class="#{"x-box-collapsible" if options[:collapsible]}" style="width:99%;#{"display:none" if options[:collapsible] && options[:start] == :close}">
128
+ #{"<div style=\"font-size:10px\">&nbsp;</div>" if title.present? || subtitle.present?}
129
+ #{capture_html(&block)}
130
+ #{"<div style=\"text-align:right;margin-top:10px\">#{submit_tag(I18n.t("lipsiadmin.buttons.save"), :onclick=>"Backend.app.submitForm()")}</div>" if options[:submit]}
131
+ </div>
123
132
  </div>
124
133
  </div>
125
134
  </div>
126
- </div>
127
- <div class="x-box-bl">
128
- <div class="x-box-br">
129
- <div class="x-box-bc">&nbsp;</div>
135
+ <div class="x-box-bl">
136
+ <div class="x-box-br">
137
+ <div class="x-box-bc">&nbsp;</div>
138
+ </div>
130
139
  </div>
131
140
  </div>
132
141
  </div>
133
142
  HTML
134
143
  end
135
144
 
136
- end
137
- end
138
- end
145
+ ##
146
+ # Open a Standard window that can contain a grid. Works like a select_tag.
147
+ #
148
+ # Options can be:
149
+ #
150
+ # :with:: name of the association. Ex: :images.
151
+ # :url:: the url of the gird, by default we autogenerate them using +:with+ ex: url(:images_index, :format => :js)
152
+ # :show:: value to display, if it's a symbol we tranform them using +with+ ex: :name, will be: data["images.name"]
153
+ # :get:: value to store in our db, Default: +:id+
154
+ # :grid:: the name of the var of Admin.grid. Default :grid
155
+ # :item:: the name of container. This necessary in cases where our grid it's in another container.
156
+ # :update:: javascript function to handle when the user close grid with some selections. Default autogenerated.
157
+ # :prompt:: text or html tag to prompt. Default is autogenerated.
158
+ # :prompt_destroy:: text or html tag to prompt for clear current selections. Default is autogenerated.
159
+ # :function:: name of the function. Default is autogenerated.
160
+ # :multiple:: if true returns a collections of +:value+. Default false.
161
+ #
162
+ # Example multiple values:
163
+ # # Generate:
164
+ # # <script type="text/javascript">
165
+ # # function showAccountImages(){
166
+ # # Ext.Ajax.request({
167
+ # # url: '/admin/images.js',
168
+ # # scripts: false,
169
+ # # success: function(response){
170
+ # # try { eval(response.responseText) } catch(e) { Admin.app.error(e) };
171
+ # # var win = new Admin.window({ grid: grid, width: 800, height: 600 });
172
+ # # win.on('selected', function(win, selections){ ... });
173
+ # # win.show();
174
+ # # }
175
+ # # });
176
+ # # }
177
+ # # </script>
178
+ # # <ul id="account_images">
179
+ # # <li>
180
+ # # <span id="display">Foo</span>
181
+ # # <input type="hidden" name="account[image_ids][]" value = "1" />
182
+ # # <a href="#" onclick="this.up('li').destroy(); return false">Remove</a>
183
+ # # </li>
184
+ # # <li>
185
+ # # <span id="display">Bar</span>
186
+ # # <input type="hidden" name="account[image_ids][]" value = "2" />
187
+ # # <a href="#" onclick="this.up('li').destroy(); return false">Remove</a>
188
+ # # </li>
189
+ # # </ul>
190
+ # # <a href="#" onclick="showAccountImages(); return false">Show Images</a>
191
+ # open_grid :account, :image_ids, :with => :images, :show => :name, :get => :id, :multiple => true
192
+ #
193
+ def open_window_grid(object_name, method, options={})
194
+
195
+ # We need plural version of our association
196
+ controller = options[:multiple] ? options[:with] : "#{options[:with].to_s.pluralize}".to_sym
197
+
198
+ # Now we need our association
199
+ association = instance_variable_get("@#{object_name}").send(options[:with])
200
+
201
+ # Parsing not mandatory options
202
+ options[:url] ||= url("#{controller}_index".to_sym, :format => :js, :small => true)
203
+ options[:grid] ||= :grid
204
+ options[:item] ||= :undefined
205
+ options[:get] ||= :id
206
+ options[:prompt] ||= image_tag("admin/new.gif", :style => "vertical-align:bottom;padding:2px")
207
+ options[:function] ||= "show#{object_name.to_s.capitalize}#{options[:with].to_s.capitalize}"
208
+
209
+ # Here we build our html template
210
+ input_name = "#{object_name}[#{method}]"
211
+ input_name += "[]" if options[:multiple]
212
+
213
+ template = (<<-HTML).gsub(/ {10}/, "")
214
+ <li>
215
+ <span class="display">{0}</span>
216
+ <input type="hidden" name="#{input_name}" value="{1}">
217
+ <a href="#" onclick="this.up(\\'li\\').remove(); return false" class="closebutton">&nbsp;</a>
218
+ </li>
219
+ HTML
220
+
221
+ # We create a collection of <li>
222
+ collection = Array(association).map do |item|
223
+ template.gsub("{0}", item.send(options[:show]).to_s).
224
+ gsub("{1}", item.send(options[:get]).to_s)
225
+ end
226
+
227
+ # And we add our link for add new records
228
+ collection << content_tag(:li, link_to(options[:prompt], "#", :onclick => "#{options[:function]}(); return false;"), :class => :clean)
229
+
230
+ # Now we have the final container
231
+ container = content_tag(:ul, collection.join.gsub("\\",""), :id => "#{object_name}_#{method}", :class => "open-window-grid")
232
+
233
+ # We need to refactor some values
234
+ show = "data['#{controller}.#{options[:show]}']" if options[:show].is_a?(Symbol)
235
+ get = options[:get].is_a?(Symbol) && options[:get] != :id ? "data['#{controller}.#{options[:get]}']" : options[:get]
236
+
237
+ update_function = if options[:multiple]
238
+ (<<-JAVASCRIPT).gsub(/ {12}/, "")
239
+ Ext.each(selections, function(selection){
240
+ var template = String.format('#{template.gsub(/\n/,"")}', selection.#{show}, selection.#{get});
241
+ Ext.fly('#{object_name}_#{method}').insertHtml('afterBegin', template);
242
+ });
243
+ JAVASCRIPT
244
+ else
245
+ (<<-JAVASCRIPT).gsub(/ {12}/, "")
246
+ var selection = selections.first();
247
+ var template = String.format('#{template.gsub(/\n/,"")}', selection.#{show}, selection.#{get});
248
+ Ext.fly('#{object_name}_#{method}').update(template);
249
+ JAVASCRIPT
250
+ end
251
+
252
+ # Now we build the update function (if not present)
253
+ javascript = (<<-JAVASCRIPT).gsub(/ {10}/, '')
254
+ function #{options[:function]}(){
255
+ Ext.Ajax.request({
256
+ url: '#{options[:url]}',
257
+ scripts: false,
258
+ success: function(response){
259
+ try { eval(response.responseText) } catch(e) { Admin.app.error(e) };
260
+ var win = new Admin.window({ grid: #{options[:grid]}, item: #{options[:item]}, width: 800, height: 600 });
261
+ win.on('selected', function(win, selections){
262
+ #{update_function}
263
+ });
264
+ win.show();
265
+ }
266
+ });
267
+ }
268
+ JAVASCRIPT
269
+
270
+ # Now we return our html code
271
+ [content_tag(:script, javascript, :type => 'text/javascript'), container, tag(:div, :class => :clear)].join("\n")
272
+ end
273
+ end # Helpers
274
+ end # Admin
275
+ end # Padrino