yodel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +63 -0
  5. data/LICENSE +1 -0
  6. data/README.rdoc +20 -0
  7. data/Rakefile +1 -0
  8. data/bin/yodel +4 -0
  9. data/lib/yodel.rb +22 -0
  10. data/lib/yodel/application/application.rb +44 -0
  11. data/lib/yodel/application/extension.rb +59 -0
  12. data/lib/yodel/application/request_handler.rb +48 -0
  13. data/lib/yodel/application/yodel.rb +25 -0
  14. data/lib/yodel/command/command.rb +94 -0
  15. data/lib/yodel/command/deploy.rb +67 -0
  16. data/lib/yodel/command/dns_server.rb +16 -0
  17. data/lib/yodel/command/installer.rb +229 -0
  18. data/lib/yodel/config/config.rb +30 -0
  19. data/lib/yodel/config/environment.rb +16 -0
  20. data/lib/yodel/config/yodel.rb +21 -0
  21. data/lib/yodel/exceptions/destroyed_record.rb +2 -0
  22. data/lib/yodel/exceptions/domain_not_found.rb +16 -0
  23. data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
  24. data/lib/yodel/exceptions/exceptions.rb +3 -0
  25. data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
  26. data/lib/yodel/exceptions/invalid_field.rb +2 -0
  27. data/lib/yodel/exceptions/invalid_index.rb +2 -0
  28. data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
  29. data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
  30. data/lib/yodel/exceptions/layout_not_found.rb +2 -0
  31. data/lib/yodel/exceptions/mass_assignment.rb +2 -0
  32. data/lib/yodel/exceptions/missing_migration.rb +2 -0
  33. data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
  34. data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
  35. data/lib/yodel/exceptions/unauthorised.rb +2 -0
  36. data/lib/yodel/exceptions/unknown_field.rb +2 -0
  37. data/lib/yodel/middleware/development_server.rb +180 -0
  38. data/lib/yodel/middleware/error_pages.rb +72 -0
  39. data/lib/yodel/middleware/public_assets.rb +78 -0
  40. data/lib/yodel/middleware/request.rb +16 -0
  41. data/lib/yodel/middleware/site_detector.rb +22 -0
  42. data/lib/yodel/mime_types/default_mime_set.rb +28 -0
  43. data/lib/yodel/mime_types/mime_type.rb +68 -0
  44. data/lib/yodel/mime_types/mime_type_set.rb +41 -0
  45. data/lib/yodel/mime_types/mime_types.rb +6 -0
  46. data/lib/yodel/mime_types/yodel.rb +15 -0
  47. data/lib/yodel/models/api/api.rb +1 -0
  48. data/lib/yodel/models/api/api_call.rb +87 -0
  49. data/lib/yodel/models/core/associations/association.rb +37 -0
  50. data/lib/yodel/models/core/associations/associations.rb +22 -0
  51. data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
  52. data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
  53. data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
  54. data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
  55. data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
  56. data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
  57. data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
  58. data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
  59. data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
  60. data/lib/yodel/models/core/associations/record_association.rb +38 -0
  61. data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
  62. data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
  63. data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
  64. data/lib/yodel/models/core/attachments/attachment.rb +73 -0
  65. data/lib/yodel/models/core/attachments/image.rb +38 -0
  66. data/lib/yodel/models/core/core.rb +15 -0
  67. data/lib/yodel/models/core/fields/alias_field.rb +32 -0
  68. data/lib/yodel/models/core/fields/array_field.rb +64 -0
  69. data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
  70. data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
  71. data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
  72. data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
  73. data/lib/yodel/models/core/fields/color_field.rb +4 -0
  74. data/lib/yodel/models/core/fields/date_field.rb +35 -0
  75. data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
  76. data/lib/yodel/models/core/fields/email_field.rb +10 -0
  77. data/lib/yodel/models/core/fields/enum_field.rb +33 -0
  78. data/lib/yodel/models/core/fields/field.rb +154 -0
  79. data/lib/yodel/models/core/fields/fields.rb +29 -0
  80. data/lib/yodel/models/core/fields/fields_field.rb +31 -0
  81. data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
  82. data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
  83. data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
  84. data/lib/yodel/models/core/fields/function_field.rb +28 -0
  85. data/lib/yodel/models/core/fields/hash_field.rb +54 -0
  86. data/lib/yodel/models/core/fields/html_field.rb +15 -0
  87. data/lib/yodel/models/core/fields/image_field.rb +11 -0
  88. data/lib/yodel/models/core/fields/integer_field.rb +25 -0
  89. data/lib/yodel/models/core/fields/password_field.rb +21 -0
  90. data/lib/yodel/models/core/fields/self_field.rb +27 -0
  91. data/lib/yodel/models/core/fields/string_field.rb +15 -0
  92. data/lib/yodel/models/core/fields/tags_field.rb +7 -0
  93. data/lib/yodel/models/core/fields/text_field.rb +7 -0
  94. data/lib/yodel/models/core/fields/time_field.rb +36 -0
  95. data/lib/yodel/models/core/functions/function.rb +471 -0
  96. data/lib/yodel/models/core/functions/functions.rb +2 -0
  97. data/lib/yodel/models/core/functions/trigger.rb +14 -0
  98. data/lib/yodel/models/core/log/log.rb +33 -0
  99. data/lib/yodel/models/core/log/log_entry.rb +12 -0
  100. data/lib/yodel/models/core/model/abstract_model.rb +59 -0
  101. data/lib/yodel/models/core/model/model.rb +460 -0
  102. data/lib/yodel/models/core/model/mongo_model.rb +25 -0
  103. data/lib/yodel/models/core/model/site_model.rb +17 -0
  104. data/lib/yodel/models/core/mongo/mongo.rb +3 -0
  105. data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
  106. data/lib/yodel/models/core/mongo/query.rb +68 -0
  107. data/lib/yodel/models/core/mongo/record_index.rb +89 -0
  108. data/lib/yodel/models/core/record/abstract_record.rb +411 -0
  109. data/lib/yodel/models/core/record/embedded_record.rb +47 -0
  110. data/lib/yodel/models/core/record/mongo_record.rb +83 -0
  111. data/lib/yodel/models/core/record/record.rb +386 -0
  112. data/lib/yodel/models/core/record/section.rb +21 -0
  113. data/lib/yodel/models/core/record/site_record.rb +31 -0
  114. data/lib/yodel/models/core/site/migration.rb +52 -0
  115. data/lib/yodel/models/core/site/remote.rb +61 -0
  116. data/lib/yodel/models/core/site/site.rb +202 -0
  117. data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
  118. data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
  119. data/lib/yodel/models/core/validations/errors.rb +51 -0
  120. data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
  121. data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
  122. data/lib/yodel/models/core/validations/format_validation.rb +10 -0
  123. data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
  124. data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
  125. data/lib/yodel/models/core/validations/length_validation.rb +28 -0
  126. data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
  127. data/lib/yodel/models/core/validations/required_validation.rb +9 -0
  128. data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
  129. data/lib/yodel/models/core/validations/validation.rb +39 -0
  130. data/lib/yodel/models/core/validations/validations.rb +15 -0
  131. data/lib/yodel/models/email/email.rb +79 -0
  132. data/lib/yodel/models/migrations/01_record_model.rb +29 -0
  133. data/lib/yodel/models/migrations/02_page_model.rb +45 -0
  134. data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
  135. data/lib/yodel/models/migrations/04_group_model.rb +61 -0
  136. data/lib/yodel/models/migrations/05_user_model.rb +24 -0
  137. data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
  138. data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
  139. data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
  140. data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
  141. data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
  142. data/lib/yodel/models/migrations/11_email_model.rb +28 -0
  143. data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
  144. data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
  145. data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
  146. data/lib/yodel/models/models.rb +8 -0
  147. data/lib/yodel/models/pages/form_builder.rb +379 -0
  148. data/lib/yodel/models/pages/html_decorator.rb +132 -0
  149. data/lib/yodel/models/pages/layout.rb +120 -0
  150. data/lib/yodel/models/pages/menu.rb +32 -0
  151. data/lib/yodel/models/pages/page.rb +378 -0
  152. data/lib/yodel/models/pages/pages.rb +7 -0
  153. data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
  154. data/lib/yodel/models/pages/redirect_page.rb +11 -0
  155. data/lib/yodel/models/search/search.rb +1 -0
  156. data/lib/yodel/models/search/search_page.rb +58 -0
  157. data/lib/yodel/models/security/facebook_login_page.rb +55 -0
  158. data/lib/yodel/models/security/group.rb +10 -0
  159. data/lib/yodel/models/security/guests_group.rb +5 -0
  160. data/lib/yodel/models/security/login_page.rb +20 -0
  161. data/lib/yodel/models/security/logout_page.rb +13 -0
  162. data/lib/yodel/models/security/noone_group.rb +5 -0
  163. data/lib/yodel/models/security/owner_group.rb +8 -0
  164. data/lib/yodel/models/security/password.rb +5 -0
  165. data/lib/yodel/models/security/password_reset_page.rb +47 -0
  166. data/lib/yodel/models/security/security.rb +10 -0
  167. data/lib/yodel/models/security/user.rb +33 -0
  168. data/lib/yodel/public/core/css/core.css +257 -0
  169. data/lib/yodel/public/core/css/reset.css +48 -0
  170. data/lib/yodel/public/core/images/cross.png +0 -0
  171. data/lib/yodel/public/core/images/spinner.gif +0 -0
  172. data/lib/yodel/public/core/images/tick.png +0 -0
  173. data/lib/yodel/public/core/images/yodel.png +0 -0
  174. data/lib/yodel/public/core/js/jquery.min.js +18 -0
  175. data/lib/yodel/public/core/js/json2.js +480 -0
  176. data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
  177. data/lib/yodel/request/authentication.rb +76 -0
  178. data/lib/yodel/request/flash.rb +28 -0
  179. data/lib/yodel/request/request.rb +4 -0
  180. data/lib/yodel/requires.rb +47 -0
  181. data/lib/yodel/task_queue/queue_daemon.rb +33 -0
  182. data/lib/yodel/task_queue/queue_worker.rb +32 -0
  183. data/lib/yodel/task_queue/stats_thread.rb +27 -0
  184. data/lib/yodel/task_queue/task.rb +62 -0
  185. data/lib/yodel/task_queue/task_queue.rb +40 -0
  186. data/lib/yodel/types/date.rb +5 -0
  187. data/lib/yodel/types/object_id.rb +11 -0
  188. data/lib/yodel/types/time.rb +5 -0
  189. data/lib/yodel/version.rb +3 -0
  190. data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
  191. data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
  192. data/system/etc/resolver/yodel +2 -0
  193. data/system/usr/local/bin/yodel_command_runner +2 -0
  194. data/system/usr/local/etc/yodel/development_settings.rb +28 -0
  195. data/system/usr/local/etc/yodel/production_settings.rb +27 -0
  196. data/system/var/log/yodel.log +0 -0
  197. data/test/helper.rb +18 -0
  198. data/test/test_yodel.rb +4 -0
  199. data/yodel.gemspec +47 -0
  200. metadata +501 -0
@@ -0,0 +1,24 @@
1
+ class UserModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :users do |users|
4
+ add_field :first_name, :string
5
+ add_field :last_name, :string
6
+ add_field :email, :email, validations: {required: {}, unique: {}}, searchable: false
7
+ add_field :oauth_id, :string, index: true, searchable: false
8
+ add_field :username, :string, index: true, validations: {required: {}, unique: {}}, searchable: false
9
+ add_field :password, :password, validations: {required: {}}, searchable: false
10
+ add_field :password_salt, :string, display: false, searchable: false
11
+ add_field :created_at, :time, display: false
12
+ add_many :groups, default: [site.groups['Users'].id]
13
+ add_field :owner, :self
14
+
15
+ add_field :name, :function, fn: 'format("{{first_name}} {{last_name}}").strip()'
16
+ users.icon = '/admin/images/user_icon.png'
17
+ users.record_class_name = 'User'
18
+ end
19
+ end
20
+
21
+ def self.down(site)
22
+ site.users.destroy
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ class SnippetModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :snippets do |snippets|
4
+ add_field :name, :string, validations: {required: {}}, index: true
5
+ add_field :content, :text
6
+ snippets.searchable = false
7
+ end
8
+ end
9
+
10
+ def self.down(site)
11
+ site.snippets.destroy
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ class SearchPageModelMigration < Migration
2
+ def self.up(site)
3
+ operators = [ 'Equals', 'Not Equal', 'Greater Than',
4
+ 'Less Than', 'Greater Than or Equal To',
5
+ 'Less Than or Equal To', 'In']
6
+
7
+ site.pages.create_model :search_pages do |search_pages|
8
+ add_field :sort, :string, searchable: false
9
+ add_field :limit, :integer
10
+ add_field :skip, :integer
11
+ add_one :type, model: :model
12
+
13
+ add_embed_many :conditions do
14
+ add_field :name, :string
15
+ add_field :value, :string
16
+ add_field :operator, :enum, options: operators
17
+ end
18
+
19
+ add_embed_many :user_conditions, default: [{name: 'search_keywords', as: 'query', operator: 'In'}] do
20
+ add_field :name, :string
21
+ add_field :as, :string
22
+ add_field :operator, :enum, options: operators
23
+ end
24
+
25
+ search_pages.record_class_name = 'SearchPage'
26
+ end
27
+ end
28
+
29
+ def self.down(site)
30
+ site.search_pages.destroy
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ class DefaultSiteOptionsMigration < Migration
2
+ def self.up(site)
3
+ site.options = {
4
+ pages: {
5
+ permalink_character: {
6
+ description: 'When Yodel creates a URL for a page by using the title of the page, there are sometimes characters (such as spaces) that need to be replaced. This character will be used in their place. e.g "About Us" would become "about-us".',
7
+ type: 'String',
8
+ default: '-',
9
+ value: '-'
10
+ }
11
+ },
12
+ icon: nil
13
+ }
14
+ site.save
15
+ end
16
+
17
+ def self.down(site)
18
+ site.options = {}
19
+ site.save
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ class SecurityPageModelsMigration < Migration
2
+ def self.up(site)
3
+ site.pages.create_model :login_pages do |login_pages|
4
+ add_field :username_field, :string, validations: {required: {}}, default: 'username'
5
+ add_field :password_field, :string, validations: {required: {}}, default: 'password'
6
+ add_one :redirect_to, model: :page
7
+ login_pages.record_class_name = 'LoginPage'
8
+ end
9
+
10
+ site.pages.create_model :logout_pages do |logout_pages|
11
+ add_one :redirect_to, model: :page
12
+ logout_pages.record_class_name = 'LogoutPage'
13
+ end
14
+
15
+ site.pages.create_model :password_reset_pages do |password_reset_pages|
16
+ add_field :success, :html, default: 'Thank you, your password has been emailed to your email address.'
17
+ add_field :email_field, :string, validations: {required: {}}, default: 'email'
18
+ password_reset_pages.record_class_name = 'PasswordResetPage'
19
+ end
20
+
21
+ site.pages.create_model :facebook_login_pages do |facebook_login_pages|
22
+ add_field :callback_uri, :string
23
+ add_field :app_id, :string
24
+ add_field :app_secret, :string
25
+ add_one :join_page, model: :page
26
+ add_one :after_login_page, model: :page
27
+ facebook_login_pages.record_class_name = 'FacebookLoginPage'
28
+ end
29
+ end
30
+
31
+ def self.down(site)
32
+ site.login_pages.destroy
33
+ site.logout_pages.destroy
34
+ site.password_reset_pages.destroy
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ class RecordProxyPageModelMigration < Migration
2
+ def self.up(site)
3
+ site.pages.create_model :record_proxy_pages do |record_proxy_pages|
4
+ add_one :record_model, model: :model
5
+ add_one :after_create_page, model: :page
6
+ add_one :after_delete_page, model: :page
7
+ add_one :after_update_page, model: :page
8
+ add_field :show_record_layout, :string
9
+ add_one :show_record_layout_record, model: :layout, display: false
10
+ record_proxy_pages.record_class_name = 'RecordProxyPage'
11
+ end
12
+ end
13
+
14
+ def self.down(site)
15
+ site.record_proxy_pages.destroy
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ class EmailModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :emails do |emails|
4
+ add_field :name, :string
5
+ add_field :from, :string
6
+ add_field :to, :string
7
+ add_field :cc, :string
8
+ add_field :bcc, :string
9
+ add_field :subject, :string
10
+ add_field :text_body, :text
11
+ add_field :html_body, :html
12
+ add_field :html_layout, :string
13
+ emails.record_class_name = 'Email'
14
+ end
15
+
16
+ # template password reset email
17
+ password_reset_email = site.emails.new
18
+ password_reset_email.name = 'password_reset'
19
+ password_reset_email.from = 'admin@site.com'
20
+ password_reset_email.subject = 'Password Reset'
21
+ password_reset_email.text_body = 'Hi <%= options["first_name"] %>, your password has been reset and is now: <%= options["new_password"] %>.'
22
+ password_reset_email.save
23
+ end
24
+
25
+ def self.down(site)
26
+ site.emails.destroy
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ class APICallModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :api_calls do |api_calls|
4
+ add_field :name, :string
5
+ add_field :http_method, :string
6
+ add_field :domain, :string
7
+ add_field :port, :integer, default: 80
8
+ add_field :path, :string
9
+ add_field :username, :string
10
+ add_field :password, :string
11
+ add_field :authentication, :enum, options: %w{basic digest}
12
+ add_field :mime_type, :string, default: 'json'
13
+ add_field :body, :text
14
+ add_field :body_layout, :string
15
+ add_field :function, :string
16
+ api_calls.record_class_name = 'APICall'
17
+ end
18
+ end
19
+
20
+ def self.down(site)
21
+ site.api_calls.destroy
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ class RedirectPageModelMigration < Migration
2
+ def self.up(site)
3
+ site.pages.create_model :redirect_page do |redirect_pages|
4
+ add_field :url, :string, searchable: false
5
+ add_one :page, show_blank: true, blank_text: 'None'
6
+ redirect_pages.record_class_name = 'RedirectPage'
7
+ end
8
+ end
9
+
10
+ def self.down(site)
11
+ site.redirect_pages.destroy
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ class MenuModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :menu do |menus|
4
+ add_one :root, model: :page, validations: {required: {}}
5
+ add_field :include_root, :boolean, default: false
6
+ add_field :include_all_children, :boolean, default: true
7
+ add_field :depth, :integer, default: 0, validations: {required: {}}
8
+ add_embed_many :exceptions do
9
+ add_one :page
10
+ add_field :show, :boolean, default: false
11
+ add_field :depth, :integer, default: 0, validations: {required: {}}
12
+ end
13
+ menus.record_class_name = 'Menu'
14
+ end
15
+ end
16
+
17
+ def self.down(site)
18
+ site.menus.destroy
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ Dir.chdir(File.dirname(__FILE__)) do
2
+ require './core/core'
3
+ require './api/api'
4
+ require './email/email'
5
+ require './pages/pages'
6
+ require './search/search'
7
+ require './security/security'
8
+ end
@@ -0,0 +1,379 @@
1
+ class FormBuilder
2
+ MONTHS = [
3
+ [1, "January"],
4
+ [2, "February"],
5
+ [3, "March"],
6
+ [4, "April"],
7
+ [5, "May"],
8
+ [6, "June"],
9
+ [7, "July"],
10
+ [8, "August"],
11
+ [9, "September"],
12
+ [10, "October"],
13
+ [11, "November"],
14
+ [12, "December"]
15
+ ]
16
+
17
+
18
+ def initialize(record, action, options={}, &block)
19
+ @record = record
20
+ @options = options
21
+ @block = block
22
+ @action = action
23
+
24
+ @remote = options.delete(:remote)
25
+ @method = options.delete(:method) || 'post'
26
+ @params = options.delete(:params) || {}
27
+ @embedded_record = options.delete(:embedded_record)
28
+ @blank_record = options.delete(:blank_record)
29
+ @prefix = options.delete(:prefix)
30
+ @id = options.delete(:id) || "form_for_#{record.id}"
31
+
32
+ # js functions
33
+ @success_function = options.delete(:success)
34
+ @errors_function = options.delete(:errors)
35
+ @failure_function = options.delete(:failure)
36
+ end
37
+
38
+ def form_for_section(section)
39
+ buffer = Ember::Template.buffer_from_block(@block)
40
+ section.displayed_fields.each do |field|
41
+ buffer << field_row(field.name, field)
42
+ end
43
+ ''
44
+ end
45
+
46
+ def field_row(name, field=nil)
47
+ field = @record.fields[name.to_s] if field.nil?
48
+ "<div class='contains-field-type-#{field.options['type']}'>" << label(name).to_s << "<div>" << field(name).to_s << status(name).to_s << "</div></div>"
49
+ end
50
+
51
+
52
+ def field(name, options={}, &block)
53
+ invalid = @record.errors.key?(name.to_s)
54
+ field_name = name.to_s
55
+ field = @record.field(field_name)
56
+ input_name = (options.delete(:name) || field_name).to_s
57
+ input_name = "#{@prefix}[][#{input_name}]" if @prefix
58
+ value = options.delete(:value) || @record.get(field_name)
59
+ type = options.delete(:as) || field.default_input_type
60
+
61
+ case type
62
+ when :text, :password, :hidden
63
+ value = nil if type == :password
64
+ value = value.id if value.is_a?(AbstractRecord)
65
+ value = value.to_s("F") if value.is_a?(BigDecimal)
66
+ element = build_element(:input, {type: type.to_s, value: value.to_s})
67
+ when :textarea
68
+ element = build_element(:textarea, {}, value.to_s)
69
+ when :html
70
+ element = build_element(:textarea, {class: 'html'}, value.to_s)
71
+ when :file, :image
72
+ elements = []
73
+ elements << build_element(:input, {type: 'hidden', name: input_name + '[_action]', value: 'set'})
74
+ if type == :file
75
+ elements << build_element(:p, {}, "File: <span>#{value && value.name || 'none'}</span>")
76
+ else
77
+ if !value.nil? && value.exist?
78
+ src = value.url(:admin_thumb).to_s
79
+ display = 'block'
80
+ else
81
+ src = ''
82
+ display = 'none'
83
+ end
84
+ elements << build_element(:div, {class: 'image_preview'}, [build_element(:img, {src: src, style: "display: #{display}"})])
85
+ elements << build_element(:p, {}, "Image: <span>#{(value && value.name) || 'none'}</span>")
86
+ end
87
+ elements << build_element(:input, {type: 'checkbox', name: input_name + '[_action]', value: 'clear'})
88
+ elements << build_element(:span, {}, "Delete")
89
+ elements << build_element(:input, {type: 'file', value: value.name, name: input_name + '[_value]'})
90
+ element = build_element(:span, {}, elements)
91
+ when :radio
92
+ base = {type: 'radio', name: input_name}
93
+ true_button = build_element(:input, base.merge(condition('checked', value, ['true', true])))
94
+ false_button = build_element(:input, base.merge(condition('checked', value, ['false', false])))
95
+ true_text = options.delete(:true) || 'Yes'
96
+ false_text = options.delete(:false) || 'No'
97
+ element = build_element(:span, {}, [true_text, true_button, false_text, false_button])
98
+ when :enum
99
+ element = build_select(value, field.options['options'], show_blank: field.show_blank, blank_text: field.blank_text)
100
+ when :store_one
101
+ element = build_select(value, field.record_options(@record), show_blank: field.show_blank, blank_text: field.blank_text, group_by: field.group_by, name_field: 'name', value_field: 'id')
102
+ when :store_many
103
+ element = build_select(value.collect(&:id), field.record_options(@record), show_blank: false, name_field: 'name', value_field: 'id', multiple: true)
104
+ input_name += '[]'
105
+ when :date, :datetime
106
+ # day
107
+ day_select = build_select(value.try(:day).to_s, (1..31), show_blank: true, blank_text: '', name_field: 'to_s', value_field: 'to_s')
108
+ day_select.set_attribute(:name, input_name + '[day]')
109
+ day_select.set_attribute(:id, input_name + '_day_')
110
+
111
+ # month
112
+ month_select = build_select(value.try(:month).to_s, MONTHS, show_blank: true, blank_text: '', name_field: 'last', value_field: 'first')
113
+ month_select.set_attribute(:name, input_name + '[month]')
114
+ month_select.set_attribute(:id, input_name + '_month_')
115
+
116
+ # year
117
+ year_select = build_select(value.try(:year).to_s, ((Time.now.year - 100)..(Time.now.year + 10)), show_blank: true, blank_text: '', name_field: 'to_s', value_field: 'to_s')
118
+ year_select.set_attribute(:name, input_name + '[year]')
119
+ year_select.set_attribute(:id, input_name + '_year_')
120
+
121
+ elements = [day_select, month_select, year_select]
122
+ if type == :datetime
123
+ # hour
124
+ hour_select = build_select(value.try(:hour).to_s, (0..23), show_blank: true, blank_text: '', name_field: 'to_s', value_field: 'to_s')
125
+ hour_select.set_attribute(:name, input_name + '[hour]')
126
+ hour_select.set_attribute(:id, input_name + '_hour_')
127
+
128
+ # minute
129
+ min_select = build_select(value.try(:min).to_s, (0..59), show_blank: true, blank_text: '', name_field: 'to_s', value_field: 'to_s')
130
+ min_select.set_attribute(:name, input_name + '[min]')
131
+ min_select.set_attribute(:id, input_name + '_min_')
132
+
133
+ elements += [hour_select, min_select]
134
+ end
135
+
136
+ element = build_element(:span, {}, elements)
137
+ when :embedded
138
+ if block_given?
139
+ if value.respond_to?(:each)
140
+ value.each do |document|
141
+ self.class.new(document, @action, {embedded_record: field, prefix: name, id: @id}, &block).render
142
+ end
143
+ else
144
+ self.class.new(value, @action, {embedded_record: field, prefix: name, id: @id}, &block).render
145
+ end
146
+
147
+ if options.delete(:blank_record)
148
+ self.class.new(value.new, @action, {embedded_record: field, blank_record: true, prefix: name, id: @id}, &block).render
149
+ end
150
+ end
151
+
152
+ buffer = Ember::Template.buffer_from_block(@block)
153
+ buffer << build_element(:input, {'type' => 'hidden', 'data-field' => input_name}).to_s
154
+ end
155
+
156
+ element.tap do |element|
157
+ class_name = invalid ? 'invalid' : (@record.new? ? 'new' : 'valid')
158
+ class_name += " field-type-#{field.options['type']}"
159
+ element.set_attribute(:id, input_name.gsub(/\W/, '_'))
160
+ element.set_attribute(:name, input_name)
161
+ element.set_attribute(:class, class_name)
162
+ element.set_attribute(:placeholder, field.placeholder || '')
163
+ element.set_attribute('data-field', input_name)
164
+ options.each do |name, value|
165
+ element.set_attribute(name.to_s, value)
166
+ end
167
+ end if element
168
+ end
169
+
170
+ def record
171
+ @record
172
+ end
173
+
174
+
175
+ def label(name, text=nil, options={})
176
+ text ||= name.to_s.humanize
177
+ build_element(:label, {:for => name.to_s}, text)
178
+ end
179
+
180
+
181
+ def status(*params)
182
+ if params.last.is_a?(Hash)
183
+ handles = params[0...-1]
184
+ options = params.last
185
+ else
186
+ handles = params
187
+ options = {}
188
+ end
189
+
190
+ handles = handles.collect(&:to_s)
191
+ name = options.delete(:as) || handles.first
192
+ new_text = options.delete(:new).to_s
193
+ valid_text = options.delete(:valid).to_s
194
+ invalid_text = options.delete(:invalid).to_s
195
+
196
+ if @record.errors.empty?
197
+ state = 'new'
198
+ message = new_text
199
+ elsif handles.any? {|name| @record.errors.key?(name)}
200
+ state = 'invalid'
201
+ unless invalid_text.blank?
202
+ message = invalid_text
203
+ else
204
+ message = handles.collect {|name| @record.errors.summarise[name]}.compact.join(', ')
205
+ end
206
+ else
207
+ state = 'valid'
208
+ message = valid_text
209
+ end
210
+
211
+ render_status(name, handles.join(' '), message, state, new_text, valid_text, invalid_text)
212
+ end
213
+
214
+ def progress(&block)
215
+ Ember::Template.wrap_content_block(block) do |content|
216
+ Hpricot::Elem.new('span', {
217
+ :class => 'yodel-form-activity',
218
+ :style => 'visibility: hidden'
219
+ }, [Hpricot::Text.new(content.join)])
220
+ end
221
+ end
222
+
223
+ def success(&block)
224
+ @success_function = Ember::Template.content_from_block(block).join
225
+ ''
226
+ end
227
+
228
+ def errors(&block)
229
+ @errors_function = Ember::Template.content_from_block(block).join
230
+ ''
231
+ end
232
+
233
+ def failure(&block)
234
+ @failure_function = Ember::Template.content_from_block(block).join
235
+ ''
236
+ end
237
+
238
+ def statuses(&block)
239
+ @status_template = block
240
+ end
241
+
242
+ def blank_record?
243
+ @blank_record
244
+ end
245
+
246
+ def render
247
+ if @embedded_record
248
+ buffer = Ember::Template.buffer_from_block(@block)
249
+ buffer << Ember::Template.content_from_block(@block, self)
250
+ else
251
+ Ember::Template.wrap_content_block(@block, self) do |content|
252
+ params = {
253
+ 'action' => @action,
254
+ 'method' => 'post',
255
+ 'enctype' => 'multipart/form-data',
256
+ 'data-remote' => (!!@remote).to_s,
257
+ 'id' => @id
258
+ }.merge(@params)
259
+
260
+ elements = [
261
+ Hpricot::Text.new(content.join),
262
+ Hpricot::Elem.new('input', {type: 'hidden', name: '_method', value: @method})
263
+ ]
264
+
265
+ if @success_function
266
+ params['data-success-function'] = "#{@id}_success"
267
+ elements << Hpricot::Text.new(define_callback_function('success', 'record', @success_function))
268
+ end
269
+
270
+ if @errors_function
271
+ params['data-errors-function'] = "#{@id}_errors"
272
+ elements << Hpricot::Text.new(define_callback_function('errors', 'errors', @errors_function))
273
+ end
274
+
275
+ if @failure_function
276
+ params['data-failure-function'] = "#{@id}_failure"
277
+ elements << Hpricot::Text.new(define_callback_function('failure', 'xhr', @failure_function))
278
+ end
279
+
280
+ Hpricot::Elem.new('form', params, elements)
281
+ end
282
+ end
283
+ end
284
+
285
+
286
+ private
287
+ def render_status(name, handles, message, state, new_text, valid_text, invalid_text)
288
+ if @status_template
289
+ element = Ember::Template.content_from_block(@status_template, name, message, state).join
290
+ else
291
+ element = Hpricot::Elem.new('span', {'class' => state}, [Hpricot::Text.new(message)])
292
+ end
293
+
294
+ Hpricot::Elem.new('span', {
295
+ 'data-new-text' => new_text,
296
+ 'data-valid-text' => valid_text,
297
+ 'data-invalid-text' => invalid_text,
298
+ 'data-handles' => handles,
299
+ 'class' => 'yodel-field-status'
300
+ }, [Hpricot::Text.new(element)])
301
+ end
302
+
303
+ def define_callback_function(name, parameter, source)
304
+ function_name = "#{@id}_#{name}"
305
+ instance_variable_set("@#{name}_function_name", function_name)
306
+ Hpricot::Elem.new('script', {}, [
307
+ Hpricot::Text.new("var #{function_name} = function(#{parameter}, json){"),
308
+ Hpricot::Text.new(source.to_s),
309
+ Hpricot::Text.new("}"),
310
+ ])
311
+ end
312
+
313
+ def build_element(tag, params, content=[])
314
+ content = [content] unless content.respond_to?(:to_a)
315
+ content = content.to_a.collect {|item| item.is_a?(String) ? Hpricot::Text.new(item): item}
316
+ Hpricot::Elem.new(tag.to_s, params, content)
317
+ end
318
+
319
+ def condition(name, value, options, multiple=false)
320
+ options = [options] unless options.respond_to?(:to_a) && !options.is_a?(BSON::ObjectId)
321
+ {value: options.first}.tap do |attributes|
322
+ if multiple
323
+ attributes[name] = name if value.to_a.include?(options.first)
324
+ else
325
+ attributes[name] = name if options.to_a.include?(value)
326
+ end
327
+ end
328
+ end
329
+
330
+ def build_select(current_value, values, options={})
331
+ show_blank = options[:show_blank]
332
+ blank_text = options[:blank_text]
333
+ group_by = options[:group_by]
334
+ name_field = options[:name_field]
335
+ value_field = options[:value_field]
336
+
337
+ group_by_field = group_by.is_a?(Hash) ? group_by.keys.first : group_by
338
+ current_value = current_value.map(&:to_s) if options[:multiple]
339
+
340
+ if group_by_field
341
+ select_options = Hash.new {|hash, key| hash[key] = []}
342
+ else
343
+ select_options = []
344
+ end
345
+
346
+ values.each do |value|
347
+ if name_field && value_field
348
+ option_name = value.send(name_field).to_s
349
+ option_value = value.send(value_field).to_s
350
+ else
351
+ option_name = option_value = value.to_s
352
+ end
353
+
354
+ element = build_element(:option, condition('selected', current_value, option_value, options[:multiple]), option_name)
355
+
356
+ if group_by_field
357
+ key = value.send(group_by_field).to_s
358
+ select_options[key] << element
359
+ else
360
+ select_options << element
361
+ end
362
+ end
363
+
364
+ if group_by_field
365
+ select_options = group_by[group_by_field].collect do |group_value, group_name|
366
+ build_element(:optgroup, {label: group_name}, select_options[group_value])
367
+ end
368
+ end
369
+
370
+ if show_blank
371
+ blank_text = blank_text || 'Other'
372
+ select_options.unshift(build_element(:option, condition('selected', current_value.to_s, ''), blank_text))
373
+ end
374
+
375
+ attributes = {}
376
+ attributes[:multiple] = 'multiple' if options[:multiple]
377
+ build_element(:select, attributes, select_options)
378
+ end
379
+ end