refinerycms 0.9.5.31 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/.gitignore +1 -0
  2. data/VERSION +1 -1
  3. data/bin/refinery +2 -1
  4. data/config/environment.rb +1 -1
  5. data/config/environments/development.rb +3 -0
  6. data/contributors.md +2 -0
  7. data/db/migrate/20100202034802_remove_custom_title_image_id_and_image_id_from_pages.rb +13 -0
  8. data/db/migrate/20100204011654_change_part_titles_to_titleized_version_for_new_format.rb +13 -0
  9. data/db/schema.rb +6 -10
  10. data/public/javascripts/admin.js +1 -1
  11. data/public/javascripts/jquery-ui-1.8rc1.min.js +375 -0
  12. data/public/javascripts/jquery.js +4231 -2529
  13. data/public/javascripts/refinery/admin.js +609 -38
  14. data/public/javascripts/refinery/boot_wym.js +3 -3
  15. data/public/javascripts/thickbox.js +116 -129
  16. data/public/javascripts/wymeditor/jquery.refinery.wymeditor.js +3674 -3732
  17. data/public/javascripts/wymeditor/skins/refinery/skin.js +8 -8
  18. data/public/stylesheets/refinery/refinery.css +142 -32
  19. data/public/stylesheets/wymeditor/skins/refinery/skin.css +8 -1
  20. data/readme.md +35 -21
  21. data/test/fixtures/page_parts.yml +9 -0
  22. data/test/fixtures/pages.yml +88 -0
  23. data/test/performance/browsing_test.rb +9 -0
  24. data/test/test_helper.rb +38 -0
  25. data/test/unit/image_test.rb +14 -0
  26. data/test/unit/page_part_test.rb +19 -0
  27. data/test/unit/page_test.rb +130 -0
  28. data/vendor/plugins/authentication/app/controllers/sessions_controller.rb +1 -1
  29. data/vendor/plugins/authentication/app/views/admin/users/index.html.erb +1 -2
  30. data/vendor/plugins/authentication/authentication.md +9 -1
  31. data/vendor/plugins/dashboard/dashboard.md +3 -3
  32. data/vendor/plugins/images/app/controllers/admin/images_controller.rb +17 -13
  33. data/vendor/plugins/images/app/helpers/admin/images_helper.rb +6 -0
  34. data/vendor/plugins/images/app/models/image.rb +24 -39
  35. data/vendor/plugins/images/app/views/admin/images/_form.html.erb +11 -4
  36. data/vendor/plugins/images/app/views/admin/images/_grid_view.html.erb +1 -2
  37. data/vendor/plugins/images/app/views/admin/images/_list_view_image.html.erb +1 -2
  38. data/vendor/plugins/images/app/views/admin/images/index.html.erb +1 -1
  39. data/vendor/plugins/images/app/views/admin/images/insert.html.erb +8 -71
  40. data/vendor/plugins/images/images.md +12 -7
  41. data/vendor/plugins/inquiries/app/views/admin/inquiries/_inquiry.html.erb +6 -13
  42. data/vendor/plugins/inquiries/app/views/admin/inquiry_settings/index.html.erb +1 -2
  43. data/vendor/plugins/inquiries/inquiries.md +8 -8
  44. data/vendor/plugins/news/app/views/admin/news_items/_news_item.html.erb +1 -2
  45. data/vendor/plugins/news/news.md +4 -4
  46. data/vendor/plugins/pages/app/controllers/admin/page_dialogs_controller.rb +11 -10
  47. data/vendor/plugins/pages/app/controllers/admin/page_parts_controller.rb +5 -1
  48. data/vendor/plugins/pages/app/controllers/admin/pages_controller.rb +2 -2
  49. data/vendor/plugins/pages/app/models/page.rb +75 -29
  50. data/vendor/plugins/pages/app/models/page_part.rb +1 -1
  51. data/vendor/plugins/pages/app/views/admin/page_dialogs/_page_link.html.erb +2 -2
  52. data/vendor/plugins/pages/app/views/admin/page_dialogs/link_to.html.erb +8 -112
  53. data/vendor/plugins/pages/app/views/admin/pages/_form.html.erb +50 -154
  54. data/vendor/plugins/pages/app/views/admin/pages/_list.html.erb +10 -10
  55. data/vendor/plugins/pages/app/views/admin/pages/_page_part_field.html.erb +4 -4
  56. data/vendor/plugins/pages/app/views/admin/pages/_sortable_list.html.erb +1 -1
  57. data/vendor/plugins/pages/pages.md +30 -25
  58. data/vendor/plugins/refinery/app/views/admin/_head.html.erb +5 -14
  59. data/vendor/plugins/refinery/app/views/admin/_menu.html.erb +2 -64
  60. data/vendor/plugins/refinery/app/views/shared/_message.html.erb +0 -6
  61. data/vendor/plugins/refinery/app/views/shared/admin/_error_messages_for.html.erb +1 -6
  62. data/vendor/plugins/refinery/app/views/shared/admin/_form_actions.html.erb +8 -10
  63. data/vendor/plugins/refinery/app/views/shared/admin/_image_picker.html.erb +22 -38
  64. data/vendor/plugins/refinery/app/views/shared/admin/_make_sortable.html.erb +6 -65
  65. data/vendor/plugins/refinery/app/views/shared/admin/_resource_picker.html.erb +16 -16
  66. data/vendor/plugins/refinery/app/views/shared/admin/_sortable_list.html.erb +3 -4
  67. data/vendor/plugins/refinery/lib/crud.rb +1 -1
  68. data/vendor/plugins/refinery/lib/generators/refinery/templates/views/admin/_singular_name.html.erb +1 -1
  69. data/vendor/plugins/refinery/lib/generators/refinery/templates/views/admin/_sortable_list.html.erb +1 -1
  70. data/vendor/plugins/refinery/plugins.md +19 -12
  71. data/vendor/plugins/refinery_dialogs/app/controllers/admin/dialogs_controller.rb +6 -4
  72. data/vendor/plugins/refinery_dialogs/app/views/admin/dialogs/show.html.erb +3 -3
  73. data/vendor/plugins/refinery_dialogs/app/views/layouts/admin_dialog.html.erb +7 -14
  74. data/vendor/plugins/refinery_settings/app/views/admin/refinery_settings/_refinery_setting.html.erb +1 -1
  75. data/vendor/plugins/refinery_settings/settings.md +51 -1
  76. data/vendor/plugins/resources/app/controllers/admin/resources_controller.rb +16 -11
  77. data/vendor/plugins/resources/app/views/admin/resources/_form.html.erb +9 -2
  78. data/vendor/plugins/resources/app/views/admin/resources/_resource.html.erb +1 -3
  79. data/vendor/plugins/resources/app/views/admin/resources/index.html.erb +2 -2
  80. data/vendor/plugins/resources/app/views/admin/resources/insert.html.erb +14 -60
  81. data/vendor/plugins/resources/resources.md +1 -1
  82. data/vendor/plugins/themes/app/controllers/admin/themes_controller.rb +6 -6
  83. data/vendor/plugins/themes/app/models/theme.rb +3 -3
  84. data/vendor/plugins/themes/app/views/admin/themes/_theme.html.erb +14 -20
  85. data/vendor/plugins/themes/themes.md +21 -19
  86. metadata +18 -19
  87. data/public/javascripts/builder.js +0 -136
  88. data/public/javascripts/controls.js +0 -963
  89. data/public/javascripts/dragdrop.js +0 -973
  90. data/public/javascripts/effects.js +0 -1128
  91. data/public/javascripts/fastinit.js +0 -84
  92. data/public/javascripts/livepipe.js +0 -180
  93. data/public/javascripts/prototype.js +0 -4874
  94. data/public/javascripts/refinery/dialog.js +0 -52
  95. data/public/javascripts/refinery/parse_url.js +0 -38
  96. data/public/javascripts/refinery/prototype.enhancements.js +0 -24
  97. data/public/javascripts/refinery/tooltips.js +0 -173
  98. data/public/javascripts/scriptaculous.js +0 -47
  99. data/public/javascripts/slider.js +0 -275
  100. data/public/javascripts/tabs.js +0 -149
  101. data/vendor/plugins/refinery_settings/app/views/admin/refinery_settings/_make_sortable.html.erb +0 -7
@@ -6,4 +6,4 @@ The Resources plugin allows you to upload files such as PDFs and other files you
6
6
 
7
7
  ## How it works
8
8
 
9
- The resources plugins is made up of attachment_fu and the Crudfiy mixin (see the crud.md file for details)
9
+ The resources plugins is made up of [attachment_fu](http://github.com/technoweenie/attachment_fu) for handling the store and upload of the images and the [Crudfiy mixin](http://github.com/resolve/refinerycms/blob/master/vendor/plugins/refinery/crud.md).
@@ -1,9 +1,9 @@
1
1
  class Admin::ThemesController < Admin::BaseController
2
2
 
3
3
  crudify :theme, :order => "updated_at DESC"
4
-
4
+
5
5
  before_filter :find_theme, :only => [:update, :destroy, :edit, :preview_image, :activate]
6
-
6
+
7
7
  # accessor method for theme preview image
8
8
  def preview_image
9
9
  if File.exists? @theme.preview_image
@@ -14,12 +14,12 @@ class Admin::ThemesController < Admin::BaseController
14
14
  return error_404
15
15
  end
16
16
  end
17
-
17
+
18
18
  def activate
19
- RefinerySetting[:theme] = @theme.title
20
-
19
+ RefinerySetting[:theme] = @theme.folder_title
20
+
21
21
  flash[:notice] = "'#{@theme.title}' applied to live site."
22
22
  redirect_to admin_themes_url
23
23
  end
24
-
24
+
25
25
  end
@@ -31,12 +31,12 @@ class Theme < ActiveRecord::Base
31
31
  end
32
32
  end
33
33
 
34
- def theme_folder_title
34
+ def folder_title
35
35
  File.basename(self.full_filename).split(".").first
36
36
  end
37
37
 
38
38
  def theme_path
39
- File.join(RAILS_ROOT, "themes", theme_folder_title)
39
+ File.join(RAILS_ROOT, "themes", folder_title)
40
40
  end
41
41
 
42
42
  def preview_image
@@ -50,7 +50,7 @@ class Theme < ActiveRecord::Base
50
50
  end
51
51
 
52
52
  def self.directory_is_writable?
53
- File.writable? File.join(RAILS_ROOT, "themes") # Heroku users will receive false here
53
+ File.writable? File.join(RAILS_ROOT, "themes") # Heroku users (or users with read-only filesystem) will receive false here
54
54
  end
55
55
 
56
56
  end
@@ -1,24 +1,18 @@
1
- <li class=''>
2
-
3
- <p>
4
- <%=h theme.title %>
5
- </p>
6
- <%= image_tag(preview_image_admin_theme_url(theme), :width => 135, :height => 135) %>
7
- <p>
8
-
9
- <span class='actions'>
10
- <%= link_to refinery_icon_tag('star.png'), activate_admin_theme_url(theme) %>
11
- <%= link_to refinery_icon_tag('page_white_put.png'), theme.public_filename,
1
+ <li>
2
+ <p>
3
+ <%=h theme.title %>
4
+ </p>
5
+ <%= image_tag(preview_image_admin_theme_url(theme), :width => 135, :height => 135) %>
6
+ <p>
7
+ <span class='actions'>
8
+ <%= link_to refinery_icon_tag('star.png'), activate_admin_theme_url(theme),
9
+ :title => "Activate this theme" %>
10
+ <%= link_to refinery_icon_tag('page_white_put.png'), theme.public_filename,
12
11
  :title => "Download this theme (#{number_to_human_size(theme.size)})" %>
13
- <% if Theme::directory_is_writable? %>
14
- <%= link_to refinery_icon_tag('application_edit.png'), edit_admin_theme_url(theme),
15
- :title => "Edit this theme" %>
16
- <% end %>
17
- <%= link_to refinery_icon_tag('delete.png'), admin_theme_path(theme),
18
- :confirm => "Are you sure you want to delete '#{theme.title}'?",
19
- :class => "cancel", :method => :delete,
20
- :title => "Remove this theme forever" %>
12
+ <%= link_to refinery_icon_tag('application_edit.png'), edit_admin_theme_url(theme),
13
+ :title => "Edit this theme" if Theme::directory_is_writable? %>
14
+ <%= link_to refinery_icon_tag('delete.png'), admin_theme_path(theme), :class => "cancel confirm-delete",
15
+ :title => "Remove this theme forever" %>
21
16
  </span>
22
17
  </p>
23
-
24
18
  </li>
@@ -1,7 +1,5 @@
1
1
  # Themes
2
2
 
3
- ![Refinery Dashboard](http://refinerycms.com/system/images/0000/0576/dashboard.png)
4
-
5
3
  ## Introduction
6
4
 
7
5
  __Themes allow you to wrap up the design of your Refinery site into a single folder that is portable.__
@@ -11,9 +9,9 @@ ERB views you're used to in Rails. This means creating a theme from your existin
11
9
 
12
10
  Think of a theme as your ``app/views`` directory with a few extra things like images, css and javascript.
13
11
 
14
- It's worth noting you don't need to use a theme if you don't want to. Placing files in the ``app/views`` directory like any other Rails app will work just fine. It's only if you want to wrap your design up into a single location that you would use a theme or allow your client to easily change designs.
12
+ It's worth noting you don't need to use a theme if you don't want to. Placing files in the ``app/views`` directory like any other Rails app will work just fine. It's only if you want to wrap your design up into a single location that you would use a theme or allow your client to easily change between designs.
15
13
 
16
- ## The structure of a theme
14
+ ## The Structure of a Theme
17
15
 
18
16
  Themes sit in your Rails app like this
19
17
 
@@ -28,7 +26,7 @@ Themes sit in your Rails app like this
28
26
  plugins/
29
27
  tests/
30
28
 
31
- So let's take the ``mytheme`` example theme shown above. This is how the theme is structured:
29
+ Let's take the ``mytheme`` example theme shown above. This is how the theme is structured:
32
30
 
33
31
  mytheme/
34
32
  |- images
@@ -55,7 +53,7 @@ Usually this would be just what you have in ``public/images`` except we move tha
55
53
 
56
54
  ### Javascripts
57
55
 
58
- Same with javascripts, just what you normally have in ``public/javascripts`` just in this theme directory instead.
56
+ Same with javascripts, just what you normally have in ``public/javascripts`` but in this theme directory instead.
59
57
 
60
58
  ### Readme
61
59
 
@@ -63,21 +61,21 @@ The ``README`` file is just a description of your theme.
63
61
 
64
62
  ### Views
65
63
 
66
- This is exactly the same as how you lay your views out in app/views/ just instead of putting them in ``app/views/`` you put them into ``themes/mytheme/views/``
64
+ This is exactly the same as how you lay your views out in ``app/views/`` just instead of putting them in ``app/views/`` you put them into ``themes/mytheme/views/``
67
65
 
68
66
  ### Preview Image
69
67
 
70
- The ``preview.png`` image is used when selecting the theme in the backend. It must be a png and ideally is 135x135 pixels.
68
+ The ``preview.png`` image is used when selecting the theme in the backend. It must be a png file and is ideally 135 x 135 pixels.
71
69
 
72
- ## How do I make my own theme?
70
+ ## How do I make my own Theme?
73
71
 
74
- Create a folder with the name if your theme inside ``/themes`` e.g. ``/themes/mytheme`` and follow the directory structure outlined in 'The structure of a theme'.
72
+ Create a folder with the name if your theme inside ``/themes`` e.g. ``/themes/mytheme`` and follow the directory structure outlined in 'The structure of a Theme'.
75
73
 
76
- ## How do I select which theme Refinery should use?
74
+ ## How do I select which Theme Refinery should use?
77
75
 
78
76
  In the admin area of Refinery go to the "Settings" area, locate the setting called "theme" and edit it.
79
77
 
80
- Set the value of that setting to the name of your themes folder. For example if your theme is sitting in:
78
+ Set the value of that setting to the name of your themes folder. For example, if your theme is sitting in:
81
79
 
82
80
  themes/my_theme
83
81
 
@@ -87,7 +85,7 @@ set it to ``my_theme`` and hit save.
87
85
 
88
86
  If you want to share a theme and install it on another site you have to zip it first.
89
87
 
90
- It's important to note you don't zip the theme's directory itself just the contents.
88
+ It's important to note you don't zip the theme's directory itself, just the contents.
91
89
 
92
90
  If I had a theme sitting in:
93
91
 
@@ -98,15 +96,15 @@ The zip file would look like this
98
96
  mytheme.zip
99
97
  |- [theme files here]
100
98
 
101
- Read 'How do I install someone else's theme?' to take that zip file and install the theme.
99
+ Read 'How do I install someone else's Theme?' to take that zip file and install the theme.
102
100
 
103
- ## How do I install someone else's theme?
101
+ ## How do I install someone else's Theme?
104
102
 
105
103
  If you have the themes plugin added to your admin user, you should see in the admin area of Refinery a "Themes" tab in the main navigation. Click on that, then click "Upload new theme". Upload the theme zip file and then click on the "star" below the preview image for the theme to activate that theme as the one to use right now.
106
104
 
107
- ## How can I convert my current views into a theme?
105
+ ## How can I Convert my Current Views into a Theme?
108
106
 
109
- This should be fairly straightforward just following the directory structure outlined in 'The structure of a theme'.
107
+ This should be fairly straightforward, just follow the directory structure outlined in 'The structure of a Theme'.
110
108
 
111
109
  But there is one important difference that need to be addressed to convert your current site into a theme.
112
110
 
@@ -116,7 +114,7 @@ If you have some CSS which refers to an image or URL:
116
114
  background: url('/images/footer_background.png') repeat-x;
117
115
  }
118
116
 
119
- You need to update the URL so it requests /images/themes instead of /images. This tells Refinery we need to actually load this image from the theme and not just the public directory.
117
+ You need to update the URL so it requests ``/images/themes`` instead of just ``/images``. This tells Refinery we need to actually load this image from the theme and not just the public directory.
120
118
 
121
119
  So the result is simply:
122
120
 
@@ -130,4 +128,8 @@ This is the same with linking to Javascript and Stylesheets in your view. Say ou
130
128
 
131
129
  You just need to change that to:
132
130
 
133
- <%= stylesheet_link_tag 'theme/application' %>
131
+ <%= stylesheet_link_tag 'theme/application' %>
132
+
133
+ ## I'm Stuck, is there an Example Theme?
134
+
135
+ Yep, there is an example theme called "demolicious" that comes with Refinery located in ``/themes/demolicious``. If you find yourself getting stuck, just check out that theme and get a feel for how it works.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: refinerycms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5.31
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Resolve Digital
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2010-01-28 00:00:00 +13:00
14
+ date: 2010-02-05 00:00:00 +13:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
@@ -60,6 +60,8 @@ files:
60
60
  - db/migrate/20091207033335_add_superuser_to_users.rb
61
61
  - db/migrate/20100114092849_add_themes_table.rb
62
62
  - db/migrate/20100127004649_add_reset_code_to_users.rb
63
+ - db/migrate/20100202034802_remove_custom_title_image_id_and_image_id_from_pages.rb
64
+ - db/migrate/20100204011654_change_part_titles_to_titleized_version_for_new_format.rb
63
65
  - db/schema.rb
64
66
  - db/seeds.rb
65
67
  - doc/.yardoc/checksums
@@ -147,25 +149,12 @@ files:
147
149
  - public/images/wymeditor/skins/wymeditor_icon.png
148
150
  - public/javascripts/admin.js
149
151
  - public/javascripts/application.js
150
- - public/javascripts/builder.js
151
- - public/javascripts/controls.js
152
- - public/javascripts/dragdrop.js
153
- - public/javascripts/effects.js
154
- - public/javascripts/fastinit.js
152
+ - public/javascripts/jquery-ui-1.8rc1.min.js
155
153
  - public/javascripts/jquery.js
156
154
  - public/javascripts/jquery/GPL-LICENSE.txt
157
155
  - public/javascripts/jquery/MIT-LICENSE.txt
158
- - public/javascripts/livepipe.js
159
- - public/javascripts/prototype.js
160
156
  - public/javascripts/refinery/admin.js
161
157
  - public/javascripts/refinery/boot_wym.js
162
- - public/javascripts/refinery/dialog.js
163
- - public/javascripts/refinery/parse_url.js
164
- - public/javascripts/refinery/prototype.enhancements.js
165
- - public/javascripts/refinery/tooltips.js
166
- - public/javascripts/scriptaculous.js
167
- - public/javascripts/slider.js
168
- - public/javascripts/tabs.js
169
158
  - public/javascripts/thickbox.js
170
159
  - public/javascripts/wymeditor/jquery.refinery.wymeditor.js
171
160
  - public/javascripts/wymeditor/lang/ca.js
@@ -223,6 +212,13 @@ files:
223
212
  - script/process/spawner
224
213
  - script/runner
225
214
  - script/server
215
+ - test/fixtures/page_parts.yml
216
+ - test/fixtures/pages.yml
217
+ - test/performance/browsing_test.rb
218
+ - test/test_helper.rb
219
+ - test/unit/image_test.rb
220
+ - test/unit/page_part_test.rb
221
+ - test/unit/page_test.rb
226
222
  - themes/demolicious.zip
227
223
  - themes/hemingway.zip
228
224
  - vendor/plugins/acts_as_indexed/CHANGELOG
@@ -500,7 +496,6 @@ files:
500
496
  - vendor/plugins/refinery_settings/app/controllers/admin/refinery_settings_controller.rb
501
497
  - vendor/plugins/refinery_settings/app/models/refinery_setting.rb
502
498
  - vendor/plugins/refinery_settings/app/views/admin/refinery_settings/_form.html.erb
503
- - vendor/plugins/refinery_settings/app/views/admin/refinery_settings/_make_sortable.html.erb
504
499
  - vendor/plugins/refinery_settings/app/views/admin/refinery_settings/_refinery_setting.html.erb
505
500
  - vendor/plugins/refinery_settings/app/views/admin/refinery_settings/edit.html.erb
506
501
  - vendor/plugins/refinery_settings/app/views/admin/refinery_settings/index.html.erb
@@ -559,5 +554,9 @@ rubygems_version: 1.3.5
559
554
  signing_key:
560
555
  specification_version: 3
561
556
  summary: A beautiful open source Ruby on Rails content manager for small business.
562
- test_files: []
563
-
557
+ test_files:
558
+ - test/performance/browsing_test.rb
559
+ - test/test_helper.rb
560
+ - test/unit/image_test.rb
561
+ - test/unit/page_part_test.rb
562
+ - test/unit/page_test.rb
@@ -1,136 +0,0 @@
1
- // script.aculo.us builder.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
2
-
3
- // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
- //
5
- // script.aculo.us is freely distributable under the terms of an MIT-style license.
6
- // For details, see the script.aculo.us web site: http://script.aculo.us/
7
-
8
- var Builder = {
9
- NODEMAP: {
10
- AREA: 'map',
11
- CAPTION: 'table',
12
- COL: 'table',
13
- COLGROUP: 'table',
14
- LEGEND: 'fieldset',
15
- OPTGROUP: 'select',
16
- OPTION: 'select',
17
- PARAM: 'object',
18
- TBODY: 'table',
19
- TD: 'table',
20
- TFOOT: 'table',
21
- TH: 'table',
22
- THEAD: 'table',
23
- TR: 'table'
24
- },
25
- // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
- // due to a Firefox bug
27
- node: function(elementName) {
28
- elementName = elementName.toUpperCase();
29
-
30
- // try innerHTML approach
31
- var parentTag = this.NODEMAP[elementName] || 'div';
32
- var parentElement = document.createElement(parentTag);
33
- try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
- parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
- } catch(e) {}
36
- var element = parentElement.firstChild || null;
37
-
38
- // see if browser added wrapping tags
39
- if(element && (element.tagName.toUpperCase() != elementName))
40
- element = element.getElementsByTagName(elementName)[0];
41
-
42
- // fallback to createElement approach
43
- if(!element) element = document.createElement(elementName);
44
-
45
- // abort if nothing could be created
46
- if(!element) return;
47
-
48
- // attributes (or text)
49
- if(arguments[1])
50
- if(this._isStringOrNumber(arguments[1]) ||
51
- (arguments[1] instanceof Array) ||
52
- arguments[1].tagName) {
53
- this._children(element, arguments[1]);
54
- } else {
55
- var attrs = this._attributes(arguments[1]);
56
- if(attrs.length) {
57
- try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58
- parentElement.innerHTML = "<" +elementName + " " +
59
- attrs + "></" + elementName + ">";
60
- } catch(e) {}
61
- element = parentElement.firstChild || null;
62
- // workaround firefox 1.0.X bug
63
- if(!element) {
64
- element = document.createElement(elementName);
65
- for(attr in arguments[1])
66
- element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67
- }
68
- if(element.tagName.toUpperCase() != elementName)
69
- element = parentElement.getElementsByTagName(elementName)[0];
70
- }
71
- }
72
-
73
- // text, or array of children
74
- if(arguments[2])
75
- this._children(element, arguments[2]);
76
-
77
- return $(element);
78
- },
79
- _text: function(text) {
80
- return document.createTextNode(text);
81
- },
82
-
83
- ATTR_MAP: {
84
- 'className': 'class',
85
- 'htmlFor': 'for'
86
- },
87
-
88
- _attributes: function(attributes) {
89
- var attrs = [];
90
- for(attribute in attributes)
91
- attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92
- '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93
- return attrs.join(" ");
94
- },
95
- _children: function(element, children) {
96
- if(children.tagName) {
97
- element.appendChild(children);
98
- return;
99
- }
100
- if(typeof children=='object') { // array can hold nodes and text
101
- children.flatten().each( function(e) {
102
- if(typeof e=='object')
103
- element.appendChild(e);
104
- else
105
- if(Builder._isStringOrNumber(e))
106
- element.appendChild(Builder._text(e));
107
- });
108
- } else
109
- if(Builder._isStringOrNumber(children))
110
- element.appendChild(Builder._text(children));
111
- },
112
- _isStringOrNumber: function(param) {
113
- return(typeof param=='string' || typeof param=='number');
114
- },
115
- build: function(html) {
116
- var element = this.node('div');
117
- $(element).update(html.strip());
118
- return element.down();
119
- },
120
- dump: function(scope) {
121
- if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
122
-
123
- var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124
- "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125
- "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126
- "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127
- "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128
- "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
-
130
- tags.each( function(tag){
131
- scope[tag] = function() {
132
- return Builder.node.apply(Builder, [tag].concat($A(arguments)));
133
- };
134
- });
135
- }
136
- };
@@ -1,963 +0,0 @@
1
- // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
- // (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
- // (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
4
- // Contributors:
5
- // Richard Livsey
6
- // Rahul Bhargava
7
- // Rob Wills
8
- //
9
- // script.aculo.us is freely distributable under the terms of an MIT-style license.
10
- // For details, see the script.aculo.us web site: http://script.aculo.us/
11
-
12
- // Autocompleter.Base handles all the autocompletion functionality
13
- // that's independent of the data source for autocompletion. This
14
- // includes drawing the autocompletion menu, observing keyboard
15
- // and mouse events, and similar.
16
- //
17
- // Specific autocompleters need to provide, at the very least,
18
- // a getUpdatedChoices function that will be invoked every time
19
- // the text inside the monitored textbox changes. This method
20
- // should get the text for which to provide autocompletion by
21
- // invoking this.getToken(), NOT by directly accessing
22
- // this.element.value. This is to allow incremental tokenized
23
- // autocompletion. Specific auto-completion logic (AJAX, etc)
24
- // belongs in getUpdatedChoices.
25
- //
26
- // Tokenized incremental autocompletion is enabled automatically
27
- // when an autocompleter is instantiated with the 'tokens' option
28
- // in the options parameter, e.g.:
29
- // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
30
- // will incrementally autocomplete with a comma as the token.
31
- // Additionally, ',' in the above example can be replaced with
32
- // a token array, e.g. { tokens: [',', '\n'] } which
33
- // enables autocompletion on multiple tokens. This is most
34
- // useful when one of the tokens is \n (a newline), as it
35
- // allows smart autocompletion after linebreaks.
36
-
37
- if(typeof Effect == 'undefined')
38
- throw("controls.js requires including script.aculo.us' effects.js library");
39
-
40
- var Autocompleter = { };
41
- Autocompleter.Base = Class.create({
42
- baseInitialize: function(element, update, options) {
43
- element = $(element);
44
- this.element = element;
45
- this.update = $(update);
46
- this.hasFocus = false;
47
- this.changed = false;
48
- this.active = false;
49
- this.index = 0;
50
- this.entryCount = 0;
51
- this.oldElementValue = this.element.value;
52
-
53
- if(this.setOptions)
54
- this.setOptions(options);
55
- else
56
- this.options = options || { };
57
-
58
- this.options.paramName = this.options.paramName || this.element.name;
59
- this.options.tokens = this.options.tokens || [];
60
- this.options.frequency = this.options.frequency || 0.4;
61
- this.options.minChars = this.options.minChars || 1;
62
- this.options.onShow = this.options.onShow ||
63
- function(element, update){
64
- if(!update.style.position || update.style.position=='absolute') {
65
- update.style.position = 'absolute';
66
- Position.clone(element, update, {
67
- setHeight: false,
68
- offsetTop: element.offsetHeight
69
- });
70
- }
71
- Effect.Appear(update,{duration:0.15});
72
- };
73
- this.options.onHide = this.options.onHide ||
74
- function(element, update){ new Effect.Fade(update,{duration:0.15}) };
75
-
76
- if(typeof(this.options.tokens) == 'string')
77
- this.options.tokens = new Array(this.options.tokens);
78
- // Force carriage returns as token delimiters anyway
79
- if (!this.options.tokens.include('\n'))
80
- this.options.tokens.push('\n');
81
-
82
- this.observer = null;
83
-
84
- this.element.setAttribute('autocomplete','off');
85
-
86
- Element.hide(this.update);
87
-
88
- Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
89
- Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
90
- },
91
-
92
- show: function() {
93
- if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
94
- if(!this.iefix &&
95
- (Prototype.Browser.IE) &&
96
- (Element.getStyle(this.update, 'position')=='absolute')) {
97
- new Insertion.After(this.update,
98
- '<iframe id="' + this.update.id + '_iefix" '+
99
- 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
100
- 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
101
- this.iefix = $(this.update.id+'_iefix');
102
- }
103
- if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
104
- },
105
-
106
- fixIEOverlapping: function() {
107
- Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
108
- this.iefix.style.zIndex = 1;
109
- this.update.style.zIndex = 2;
110
- Element.show(this.iefix);
111
- },
112
-
113
- hide: function() {
114
- this.stopIndicator();
115
- if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
116
- if(this.iefix) Element.hide(this.iefix);
117
- },
118
-
119
- startIndicator: function() {
120
- if(this.options.indicator) Element.show(this.options.indicator);
121
- },
122
-
123
- stopIndicator: function() {
124
- if(this.options.indicator) Element.hide(this.options.indicator);
125
- },
126
-
127
- onKeyPress: function(event) {
128
- if(this.active)
129
- switch(event.keyCode) {
130
- case Event.KEY_TAB:
131
- case Event.KEY_RETURN:
132
- this.selectEntry();
133
- Event.stop(event);
134
- case Event.KEY_ESC:
135
- this.hide();
136
- this.active = false;
137
- Event.stop(event);
138
- return;
139
- case Event.KEY_LEFT:
140
- case Event.KEY_RIGHT:
141
- return;
142
- case Event.KEY_UP:
143
- this.markPrevious();
144
- this.render();
145
- Event.stop(event);
146
- return;
147
- case Event.KEY_DOWN:
148
- this.markNext();
149
- this.render();
150
- Event.stop(event);
151
- return;
152
- }
153
- else
154
- if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
155
- (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
156
-
157
- this.changed = true;
158
- this.hasFocus = true;
159
-
160
- if(this.observer) clearTimeout(this.observer);
161
- this.observer =
162
- setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
163
- },
164
-
165
- activate: function() {
166
- this.changed = false;
167
- this.hasFocus = true;
168
- this.getUpdatedChoices();
169
- },
170
-
171
- onHover: function(event) {
172
- var element = Event.findElement(event, 'LI');
173
- if(this.index != element.autocompleteIndex)
174
- {
175
- this.index = element.autocompleteIndex;
176
- this.render();
177
- }
178
- Event.stop(event);
179
- },
180
-
181
- onClick: function(event) {
182
- var element = Event.findElement(event, 'LI');
183
- this.index = element.autocompleteIndex;
184
- this.selectEntry();
185
- this.hide();
186
- },
187
-
188
- onBlur: function(event) {
189
- // needed to make click events working
190
- setTimeout(this.hide.bind(this), 250);
191
- this.hasFocus = false;
192
- this.active = false;
193
- },
194
-
195
- render: function() {
196
- if(this.entryCount > 0) {
197
- for (var i = 0; i < this.entryCount; i++)
198
- this.index==i ?
199
- Element.addClassName(this.getEntry(i),"selected") :
200
- Element.removeClassName(this.getEntry(i),"selected");
201
- if(this.hasFocus) {
202
- this.show();
203
- this.active = true;
204
- }
205
- } else {
206
- this.active = false;
207
- this.hide();
208
- }
209
- },
210
-
211
- markPrevious: function() {
212
- if(this.index > 0) this.index--;
213
- else this.index = this.entryCount-1;
214
- this.getEntry(this.index).scrollIntoView(true);
215
- },
216
-
217
- markNext: function() {
218
- if(this.index < this.entryCount-1) this.index++;
219
- else this.index = 0;
220
- this.getEntry(this.index).scrollIntoView(false);
221
- },
222
-
223
- getEntry: function(index) {
224
- return this.update.firstChild.childNodes[index];
225
- },
226
-
227
- getCurrentEntry: function() {
228
- return this.getEntry(this.index);
229
- },
230
-
231
- selectEntry: function() {
232
- this.active = false;
233
- this.updateElement(this.getCurrentEntry());
234
- },
235
-
236
- updateElement: function(selectedElement) {
237
- if (this.options.updateElement) {
238
- this.options.updateElement(selectedElement);
239
- return;
240
- }
241
- var value = '';
242
- if (this.options.select) {
243
- var nodes = $(selectedElement).select('.' + this.options.select) || [];
244
- if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
245
- } else
246
- value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
247
-
248
- var bounds = this.getTokenBounds();
249
- if (bounds[0] != -1) {
250
- var newValue = this.element.value.substr(0, bounds[0]);
251
- var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
252
- if (whitespace)
253
- newValue += whitespace[0];
254
- this.element.value = newValue + value + this.element.value.substr(bounds[1]);
255
- } else {
256
- this.element.value = value;
257
- }
258
- this.oldElementValue = this.element.value;
259
- this.element.focus();
260
-
261
- if (this.options.afterUpdateElement)
262
- this.options.afterUpdateElement(this.element, selectedElement);
263
- },
264
-
265
- updateChoices: function(choices) {
266
- if(!this.changed && this.hasFocus) {
267
- this.update.innerHTML = choices;
268
- Element.cleanWhitespace(this.update);
269
- Element.cleanWhitespace(this.update.down());
270
-
271
- if(this.update.firstChild && this.update.down().childNodes) {
272
- this.entryCount =
273
- this.update.down().childNodes.length;
274
- for (var i = 0; i < this.entryCount; i++) {
275
- var entry = this.getEntry(i);
276
- entry.autocompleteIndex = i;
277
- this.addObservers(entry);
278
- }
279
- } else {
280
- this.entryCount = 0;
281
- }
282
-
283
- this.stopIndicator();
284
- this.index = 0;
285
-
286
- if(this.entryCount==1 && this.options.autoSelect) {
287
- this.selectEntry();
288
- this.hide();
289
- } else {
290
- this.render();
291
- }
292
- }
293
- },
294
-
295
- addObservers: function(element) {
296
- Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
297
- Event.observe(element, "click", this.onClick.bindAsEventListener(this));
298
- },
299
-
300
- onObserverEvent: function() {
301
- this.changed = false;
302
- this.tokenBounds = null;
303
- if(this.getToken().length>=this.options.minChars) {
304
- this.getUpdatedChoices();
305
- } else {
306
- this.active = false;
307
- this.hide();
308
- }
309
- this.oldElementValue = this.element.value;
310
- },
311
-
312
- getToken: function() {
313
- var bounds = this.getTokenBounds();
314
- return this.element.value.substring(bounds[0], bounds[1]).strip();
315
- },
316
-
317
- getTokenBounds: function() {
318
- if (null != this.tokenBounds) return this.tokenBounds;
319
- var value = this.element.value;
320
- if (value.strip().empty()) return [-1, 0];
321
- var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
322
- var offset = (diff == this.oldElementValue.length ? 1 : 0);
323
- var prevTokenPos = -1, nextTokenPos = value.length;
324
- var tp;
325
- for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
326
- tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
327
- if (tp > prevTokenPos) prevTokenPos = tp;
328
- tp = value.indexOf(this.options.tokens[index], diff + offset);
329
- if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
330
- }
331
- return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
332
- }
333
- });
334
-
335
- Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
336
- var boundary = Math.min(newS.length, oldS.length);
337
- for (var index = 0; index < boundary; ++index)
338
- if (newS[index] != oldS[index])
339
- return index;
340
- return boundary;
341
- };
342
-
343
- Ajax.Autocompleter = Class.create(Autocompleter.Base, {
344
- initialize: function(element, update, url, options) {
345
- this.baseInitialize(element, update, options);
346
- this.options.asynchronous = true;
347
- this.options.onComplete = this.onComplete.bind(this);
348
- this.options.defaultParams = this.options.parameters || null;
349
- this.url = url;
350
- },
351
-
352
- getUpdatedChoices: function() {
353
- this.startIndicator();
354
-
355
- var entry = encodeURIComponent(this.options.paramName) + '=' +
356
- encodeURIComponent(this.getToken());
357
-
358
- this.options.parameters = this.options.callback ?
359
- this.options.callback(this.element, entry) : entry;
360
-
361
- if(this.options.defaultParams)
362
- this.options.parameters += '&' + this.options.defaultParams;
363
-
364
- new Ajax.Request(this.url, this.options);
365
- },
366
-
367
- onComplete: function(request) {
368
- this.updateChoices(request.responseText);
369
- }
370
- });
371
-
372
- // The local array autocompleter. Used when you'd prefer to
373
- // inject an array of autocompletion options into the page, rather
374
- // than sending out Ajax queries, which can be quite slow sometimes.
375
- //
376
- // The constructor takes four parameters. The first two are, as usual,
377
- // the id of the monitored textbox, and id of the autocompletion menu.
378
- // The third is the array you want to autocomplete from, and the fourth
379
- // is the options block.
380
- //
381
- // Extra local autocompletion options:
382
- // - choices - How many autocompletion choices to offer
383
- //
384
- // - partialSearch - If false, the autocompleter will match entered
385
- // text only at the beginning of strings in the
386
- // autocomplete array. Defaults to true, which will
387
- // match text at the beginning of any *word* in the
388
- // strings in the autocomplete array. If you want to
389
- // search anywhere in the string, additionally set
390
- // the option fullSearch to true (default: off).
391
- //
392
- // - fullSsearch - Search anywhere in autocomplete array strings.
393
- //
394
- // - partialChars - How many characters to enter before triggering
395
- // a partial match (unlike minChars, which defines
396
- // how many characters are required to do any match
397
- // at all). Defaults to 2.
398
- //
399
- // - ignoreCase - Whether to ignore case when autocompleting.
400
- // Defaults to true.
401
- //
402
- // It's possible to pass in a custom function as the 'selector'
403
- // option, if you prefer to write your own autocompletion logic.
404
- // In that case, the other options above will not apply unless
405
- // you support them.
406
-
407
- Autocompleter.Local = Class.create(Autocompleter.Base, {
408
- initialize: function(element, update, array, options) {
409
- this.baseInitialize(element, update, options);
410
- this.options.array = array;
411
- },
412
-
413
- getUpdatedChoices: function() {
414
- this.updateChoices(this.options.selector(this));
415
- },
416
-
417
- setOptions: function(options) {
418
- this.options = Object.extend({
419
- choices: 10,
420
- partialSearch: true,
421
- partialChars: 2,
422
- ignoreCase: true,
423
- fullSearch: false,
424
- selector: function(instance) {
425
- var ret = []; // Beginning matches
426
- var partial = []; // Inside matches
427
- var entry = instance.getToken();
428
- var count = 0;
429
-
430
- for (var i = 0; i < instance.options.array.length &&
431
- ret.length < instance.options.choices ; i++) {
432
-
433
- var elem = instance.options.array[i];
434
- var foundPos = instance.options.ignoreCase ?
435
- elem.toLowerCase().indexOf(entry.toLowerCase()) :
436
- elem.indexOf(entry);
437
-
438
- while (foundPos != -1) {
439
- if (foundPos == 0 && elem.length != entry.length) {
440
- ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
441
- elem.substr(entry.length) + "</li>");
442
- break;
443
- } else if (entry.length >= instance.options.partialChars &&
444
- instance.options.partialSearch && foundPos != -1) {
445
- if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
446
- partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
447
- elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
448
- foundPos + entry.length) + "</li>");
449
- break;
450
- }
451
- }
452
-
453
- foundPos = instance.options.ignoreCase ?
454
- elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
455
- elem.indexOf(entry, foundPos + 1);
456
-
457
- }
458
- }
459
- if (partial.length)
460
- ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
461
- return "<ul>" + ret.join('') + "</ul>";
462
- }
463
- }, options || { });
464
- }
465
- });
466
-
467
- // AJAX in-place editor and collection editor
468
- // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
469
-
470
- // Use this if you notice weird scrolling problems on some browsers,
471
- // the DOM might be a bit confused when this gets called so do this
472
- // waits 1 ms (with setTimeout) until it does the activation
473
- Field.scrollFreeActivate = function(field) {
474
- setTimeout(function() {
475
- Field.activate(field);
476
- }, 1);
477
- };
478
-
479
- Ajax.InPlaceEditor = Class.create({
480
- initialize: function(element, url, options) {
481
- this.url = url;
482
- this.element = element = $(element);
483
- this.prepareOptions();
484
- this._controls = { };
485
- arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
486
- Object.extend(this.options, options || { });
487
- if (!this.options.formId && this.element.id) {
488
- this.options.formId = this.element.id + '-inplaceeditor';
489
- if ($(this.options.formId))
490
- this.options.formId = '';
491
- }
492
- if (this.options.externalControl)
493
- this.options.externalControl = $(this.options.externalControl);
494
- if (!this.options.externalControl)
495
- this.options.externalControlOnly = false;
496
- this._originalBackground = this.element.getStyle('background-color') || 'transparent';
497
- this.element.title = this.options.clickToEditText;
498
- this._boundCancelHandler = this.handleFormCancellation.bind(this);
499
- this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
500
- this._boundFailureHandler = this.handleAJAXFailure.bind(this);
501
- this._boundSubmitHandler = this.handleFormSubmission.bind(this);
502
- this._boundWrapperHandler = this.wrapUp.bind(this);
503
- this.registerListeners();
504
- },
505
- checkForEscapeOrReturn: function(e) {
506
- if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
507
- if (Event.KEY_ESC == e.keyCode)
508
- this.handleFormCancellation(e);
509
- else if (Event.KEY_RETURN == e.keyCode)
510
- this.handleFormSubmission(e);
511
- },
512
- createControl: function(mode, handler, extraClasses) {
513
- var control = this.options[mode + 'Control'];
514
- var text = this.options[mode + 'Text'];
515
- if ('button' == control) {
516
- var btn = document.createElement('input');
517
- btn.type = 'submit';
518
- btn.value = text;
519
- btn.className = 'editor_' + mode + '_button';
520
- if ('cancel' == mode)
521
- btn.onclick = this._boundCancelHandler;
522
- this._form.appendChild(btn);
523
- this._controls[mode] = btn;
524
- } else if ('link' == control) {
525
- var link = document.createElement('a');
526
- link.href = '#';
527
- link.appendChild(document.createTextNode(text));
528
- link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
529
- link.className = 'editor_' + mode + '_link';
530
- if (extraClasses)
531
- link.className += ' ' + extraClasses;
532
- this._form.appendChild(link);
533
- this._controls[mode] = link;
534
- }
535
- },
536
- createEditField: function() {
537
- var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
538
- var fld;
539
- if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
540
- fld = document.createElement('input');
541
- fld.type = 'text';
542
- var size = this.options.size || this.options.cols || 0;
543
- if (0 < size) fld.size = size;
544
- } else {
545
- fld = document.createElement('textarea');
546
- fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
547
- fld.cols = this.options.cols || 40;
548
- }
549
- fld.name = this.options.paramName;
550
- fld.value = text; // No HTML breaks conversion anymore
551
- fld.className = 'editor_field';
552
- if (this.options.submitOnBlur)
553
- fld.onblur = this._boundSubmitHandler;
554
- this._controls.editor = fld;
555
- if (this.options.loadTextURL)
556
- this.loadExternalText();
557
- this._form.appendChild(this._controls.editor);
558
- },
559
- createForm: function() {
560
- var ipe = this;
561
- function addText(mode, condition) {
562
- var text = ipe.options['text' + mode + 'Controls'];
563
- if (!text || condition === false) return;
564
- ipe._form.appendChild(document.createTextNode(text));
565
- };
566
- this._form = $(document.createElement('form'));
567
- this._form.id = this.options.formId;
568
- this._form.addClassName(this.options.formClassName);
569
- this._form.onsubmit = this._boundSubmitHandler;
570
- this.createEditField();
571
- if ('textarea' == this._controls.editor.tagName.toLowerCase())
572
- this._form.appendChild(document.createElement('br'));
573
- if (this.options.onFormCustomization)
574
- this.options.onFormCustomization(this, this._form);
575
- addText('Before', this.options.okControl || this.options.cancelControl);
576
- this.createControl('ok', this._boundSubmitHandler);
577
- addText('Between', this.options.okControl && this.options.cancelControl);
578
- this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
579
- addText('After', this.options.okControl || this.options.cancelControl);
580
- },
581
- destroy: function() {
582
- if (this._oldInnerHTML)
583
- this.element.innerHTML = this._oldInnerHTML;
584
- this.leaveEditMode();
585
- this.unregisterListeners();
586
- },
587
- enterEditMode: function(e) {
588
- if (this._saving || this._editing) return;
589
- this._editing = true;
590
- this.triggerCallback('onEnterEditMode');
591
- if (this.options.externalControl)
592
- this.options.externalControl.hide();
593
- this.element.hide();
594
- this.createForm();
595
- this.element.parentNode.insertBefore(this._form, this.element);
596
- if (!this.options.loadTextURL)
597
- this.postProcessEditField();
598
- if (e) Event.stop(e);
599
- },
600
- enterHover: function(e) {
601
- if (this.options.hoverClassName)
602
- this.element.addClassName(this.options.hoverClassName);
603
- if (this._saving) return;
604
- this.triggerCallback('onEnterHover');
605
- },
606
- getText: function() {
607
- return this.element.innerHTML.unescapeHTML();
608
- },
609
- handleAJAXFailure: function(transport) {
610
- this.triggerCallback('onFailure', transport);
611
- if (this._oldInnerHTML) {
612
- this.element.innerHTML = this._oldInnerHTML;
613
- this._oldInnerHTML = null;
614
- }
615
- },
616
- handleFormCancellation: function(e) {
617
- this.wrapUp();
618
- if (e) Event.stop(e);
619
- },
620
- handleFormSubmission: function(e) {
621
- var form = this._form;
622
- var value = $F(this._controls.editor);
623
- this.prepareSubmission();
624
- var params = this.options.callback(form, value) || '';
625
- if (Object.isString(params))
626
- params = params.toQueryParams();
627
- params.editorId = this.element.id;
628
- if (this.options.htmlResponse) {
629
- var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
630
- Object.extend(options, {
631
- parameters: params,
632
- onComplete: this._boundWrapperHandler,
633
- onFailure: this._boundFailureHandler
634
- });
635
- new Ajax.Updater({ success: this.element }, this.url, options);
636
- } else {
637
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
638
- Object.extend(options, {
639
- parameters: params,
640
- onComplete: this._boundWrapperHandler,
641
- onFailure: this._boundFailureHandler
642
- });
643
- new Ajax.Request(this.url, options);
644
- }
645
- if (e) Event.stop(e);
646
- },
647
- leaveEditMode: function() {
648
- this.element.removeClassName(this.options.savingClassName);
649
- this.removeForm();
650
- this.leaveHover();
651
- this.element.style.backgroundColor = this._originalBackground;
652
- this.element.show();
653
- if (this.options.externalControl)
654
- this.options.externalControl.show();
655
- this._saving = false;
656
- this._editing = false;
657
- this._oldInnerHTML = null;
658
- this.triggerCallback('onLeaveEditMode');
659
- },
660
- leaveHover: function(e) {
661
- if (this.options.hoverClassName)
662
- this.element.removeClassName(this.options.hoverClassName);
663
- if (this._saving) return;
664
- this.triggerCallback('onLeaveHover');
665
- },
666
- loadExternalText: function() {
667
- this._form.addClassName(this.options.loadingClassName);
668
- this._controls.editor.disabled = true;
669
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
670
- Object.extend(options, {
671
- parameters: 'editorId=' + encodeURIComponent(this.element.id),
672
- onComplete: Prototype.emptyFunction,
673
- onSuccess: function(transport) {
674
- this._form.removeClassName(this.options.loadingClassName);
675
- var text = transport.responseText;
676
- if (this.options.stripLoadedTextTags)
677
- text = text.stripTags();
678
- this._controls.editor.value = text;
679
- this._controls.editor.disabled = false;
680
- this.postProcessEditField();
681
- }.bind(this),
682
- onFailure: this._boundFailureHandler
683
- });
684
- new Ajax.Request(this.options.loadTextURL, options);
685
- },
686
- postProcessEditField: function() {
687
- var fpc = this.options.fieldPostCreation;
688
- if (fpc)
689
- $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
690
- },
691
- prepareOptions: function() {
692
- this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
693
- Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
694
- [this._extraDefaultOptions].flatten().compact().each(function(defs) {
695
- Object.extend(this.options, defs);
696
- }.bind(this));
697
- },
698
- prepareSubmission: function() {
699
- this._saving = true;
700
- this.removeForm();
701
- this.leaveHover();
702
- this.showSaving();
703
- },
704
- registerListeners: function() {
705
- this._listeners = { };
706
- var listener;
707
- $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
708
- listener = this[pair.value].bind(this);
709
- this._listeners[pair.key] = listener;
710
- if (!this.options.externalControlOnly)
711
- this.element.observe(pair.key, listener);
712
- if (this.options.externalControl)
713
- this.options.externalControl.observe(pair.key, listener);
714
- }.bind(this));
715
- },
716
- removeForm: function() {
717
- if (!this._form) return;
718
- this._form.remove();
719
- this._form = null;
720
- this._controls = { };
721
- },
722
- showSaving: function() {
723
- this._oldInnerHTML = this.element.innerHTML;
724
- this.element.innerHTML = this.options.savingText;
725
- this.element.addClassName(this.options.savingClassName);
726
- this.element.style.backgroundColor = this._originalBackground;
727
- this.element.show();
728
- },
729
- triggerCallback: function(cbName, arg) {
730
- if ('function' == typeof this.options[cbName]) {
731
- this.options[cbName](this, arg);
732
- }
733
- },
734
- unregisterListeners: function() {
735
- $H(this._listeners).each(function(pair) {
736
- if (!this.options.externalControlOnly)
737
- this.element.stopObserving(pair.key, pair.value);
738
- if (this.options.externalControl)
739
- this.options.externalControl.stopObserving(pair.key, pair.value);
740
- }.bind(this));
741
- },
742
- wrapUp: function(transport) {
743
- this.leaveEditMode();
744
- // Can't use triggerCallback due to backward compatibility: requires
745
- // binding + direct element
746
- this._boundComplete(transport, this.element);
747
- }
748
- });
749
-
750
- Object.extend(Ajax.InPlaceEditor.prototype, {
751
- dispose: Ajax.InPlaceEditor.prototype.destroy
752
- });
753
-
754
- Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
755
- initialize: function($super, element, url, options) {
756
- this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
757
- $super(element, url, options);
758
- },
759
-
760
- createEditField: function() {
761
- var list = document.createElement('select');
762
- list.name = this.options.paramName;
763
- list.size = 1;
764
- this._controls.editor = list;
765
- this._collection = this.options.collection || [];
766
- if (this.options.loadCollectionURL)
767
- this.loadCollection();
768
- else
769
- this.checkForExternalText();
770
- this._form.appendChild(this._controls.editor);
771
- },
772
-
773
- loadCollection: function() {
774
- this._form.addClassName(this.options.loadingClassName);
775
- this.showLoadingText(this.options.loadingCollectionText);
776
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
777
- Object.extend(options, {
778
- parameters: 'editorId=' + encodeURIComponent(this.element.id),
779
- onComplete: Prototype.emptyFunction,
780
- onSuccess: function(transport) {
781
- var js = transport.responseText.strip();
782
- if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
783
- throw('Server returned an invalid collection representation.');
784
- this._collection = eval(js);
785
- this.checkForExternalText();
786
- }.bind(this),
787
- onFailure: this.onFailure
788
- });
789
- new Ajax.Request(this.options.loadCollectionURL, options);
790
- },
791
-
792
- showLoadingText: function(text) {
793
- this._controls.editor.disabled = true;
794
- var tempOption = this._controls.editor.firstChild;
795
- if (!tempOption) {
796
- tempOption = document.createElement('option');
797
- tempOption.value = '';
798
- this._controls.editor.appendChild(tempOption);
799
- tempOption.selected = true;
800
- }
801
- tempOption.update((text || '').stripScripts().stripTags());
802
- },
803
-
804
- checkForExternalText: function() {
805
- this._text = this.getText();
806
- if (this.options.loadTextURL)
807
- this.loadExternalText();
808
- else
809
- this.buildOptionList();
810
- },
811
-
812
- loadExternalText: function() {
813
- this.showLoadingText(this.options.loadingText);
814
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
815
- Object.extend(options, {
816
- parameters: 'editorId=' + encodeURIComponent(this.element.id),
817
- onComplete: Prototype.emptyFunction,
818
- onSuccess: function(transport) {
819
- this._text = transport.responseText.strip();
820
- this.buildOptionList();
821
- }.bind(this),
822
- onFailure: this.onFailure
823
- });
824
- new Ajax.Request(this.options.loadTextURL, options);
825
- },
826
-
827
- buildOptionList: function() {
828
- this._form.removeClassName(this.options.loadingClassName);
829
- this._collection = this._collection.map(function(entry) {
830
- return 2 === entry.length ? entry : [entry, entry].flatten();
831
- });
832
- var marker = ('value' in this.options) ? this.options.value : this._text;
833
- var textFound = this._collection.any(function(entry) {
834
- return entry[0] == marker;
835
- }.bind(this));
836
- this._controls.editor.update('');
837
- var option;
838
- this._collection.each(function(entry, index) {
839
- option = document.createElement('option');
840
- option.value = entry[0];
841
- option.selected = textFound ? entry[0] == marker : 0 == index;
842
- option.appendChild(document.createTextNode(entry[1]));
843
- this._controls.editor.appendChild(option);
844
- }.bind(this));
845
- this._controls.editor.disabled = false;
846
- Field.scrollFreeActivate(this._controls.editor);
847
- }
848
- });
849
-
850
- //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
851
- //**** This only exists for a while, in order to let ****
852
- //**** users adapt to the new API. Read up on the new ****
853
- //**** API and convert your code to it ASAP! ****
854
-
855
- Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
856
- if (!options) return;
857
- function fallback(name, expr) {
858
- if (name in options || expr === undefined) return;
859
- options[name] = expr;
860
- };
861
- fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
862
- options.cancelLink == options.cancelButton == false ? false : undefined)));
863
- fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
864
- options.okLink == options.okButton == false ? false : undefined)));
865
- fallback('highlightColor', options.highlightcolor);
866
- fallback('highlightEndColor', options.highlightendcolor);
867
- };
868
-
869
- Object.extend(Ajax.InPlaceEditor, {
870
- DefaultOptions: {
871
- ajaxOptions: { },
872
- autoRows: 3, // Use when multi-line w/ rows == 1
873
- cancelControl: 'link', // 'link'|'button'|false
874
- cancelText: 'cancel',
875
- clickToEditText: 'Click to edit',
876
- externalControl: null, // id|elt
877
- externalControlOnly: false,
878
- fieldPostCreation: 'activate', // 'activate'|'focus'|false
879
- formClassName: 'inplaceeditor-form',
880
- formId: null, // id|elt
881
- highlightColor: '#ffff99',
882
- highlightEndColor: '#ffffff',
883
- hoverClassName: '',
884
- htmlResponse: true,
885
- loadingClassName: 'inplaceeditor-loading',
886
- loadingText: 'Loading...',
887
- okControl: 'button', // 'link'|'button'|false
888
- okText: 'ok',
889
- paramName: 'value',
890
- rows: 1, // If 1 and multi-line, uses autoRows
891
- savingClassName: 'inplaceeditor-saving',
892
- savingText: 'Saving...',
893
- size: 0,
894
- stripLoadedTextTags: false,
895
- submitOnBlur: false,
896
- textAfterControls: '',
897
- textBeforeControls: '',
898
- textBetweenControls: ''
899
- },
900
- DefaultCallbacks: {
901
- callback: function(form) {
902
- return Form.serialize(form);
903
- },
904
- onComplete: function(transport, element) {
905
- // For backward compatibility, this one is bound to the IPE, and passes
906
- // the element directly. It was too often customized, so we don't break it.
907
- new Effect.Highlight(element, {
908
- startcolor: this.options.highlightColor, keepBackgroundImage: true });
909
- },
910
- onEnterEditMode: null,
911
- onEnterHover: function(ipe) {
912
- ipe.element.style.backgroundColor = ipe.options.highlightColor;
913
- if (ipe._effect)
914
- ipe._effect.cancel();
915
- },
916
- onFailure: function(transport, ipe) {
917
- alert('Error communication with the server: ' + transport.responseText.stripTags());
918
- },
919
- onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
920
- onLeaveEditMode: null,
921
- onLeaveHover: function(ipe) {
922
- ipe._effect = new Effect.Highlight(ipe.element, {
923
- startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
924
- restorecolor: ipe._originalBackground, keepBackgroundImage: true
925
- });
926
- }
927
- },
928
- Listeners: {
929
- click: 'enterEditMode',
930
- keydown: 'checkForEscapeOrReturn',
931
- mouseover: 'enterHover',
932
- mouseout: 'leaveHover'
933
- }
934
- });
935
-
936
- Ajax.InPlaceCollectionEditor.DefaultOptions = {
937
- loadingCollectionText: 'Loading options...'
938
- };
939
-
940
- // Delayed observer, like Form.Element.Observer,
941
- // but waits for delay after last key input
942
- // Ideal for live-search fields
943
-
944
- Form.Element.DelayedObserver = Class.create({
945
- initialize: function(element, delay, callback) {
946
- this.delay = delay || 0.5;
947
- this.element = $(element);
948
- this.callback = callback;
949
- this.timer = null;
950
- this.lastValue = $F(this.element);
951
- Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
952
- },
953
- delayedListener: function(event) {
954
- if(this.lastValue == $F(this.element)) return;
955
- if(this.timer) clearTimeout(this.timer);
956
- this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
957
- this.lastValue = $F(this.element);
958
- },
959
- onTimerEvent: function() {
960
- this.timer = null;
961
- this.callback(this.element, $F(this.element));
962
- }
963
- });