yodel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +1 -0
- data/README.rdoc +20 -0
- data/Rakefile +1 -0
- data/bin/yodel +4 -0
- data/lib/yodel.rb +22 -0
- data/lib/yodel/application/application.rb +44 -0
- data/lib/yodel/application/extension.rb +59 -0
- data/lib/yodel/application/request_handler.rb +48 -0
- data/lib/yodel/application/yodel.rb +25 -0
- data/lib/yodel/command/command.rb +94 -0
- data/lib/yodel/command/deploy.rb +67 -0
- data/lib/yodel/command/dns_server.rb +16 -0
- data/lib/yodel/command/installer.rb +229 -0
- data/lib/yodel/config/config.rb +30 -0
- data/lib/yodel/config/environment.rb +16 -0
- data/lib/yodel/config/yodel.rb +21 -0
- data/lib/yodel/exceptions/destroyed_record.rb +2 -0
- data/lib/yodel/exceptions/domain_not_found.rb +16 -0
- data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
- data/lib/yodel/exceptions/exceptions.rb +3 -0
- data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
- data/lib/yodel/exceptions/invalid_field.rb +2 -0
- data/lib/yodel/exceptions/invalid_index.rb +2 -0
- data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
- data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
- data/lib/yodel/exceptions/layout_not_found.rb +2 -0
- data/lib/yodel/exceptions/mass_assignment.rb +2 -0
- data/lib/yodel/exceptions/missing_migration.rb +2 -0
- data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
- data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
- data/lib/yodel/exceptions/unauthorised.rb +2 -0
- data/lib/yodel/exceptions/unknown_field.rb +2 -0
- data/lib/yodel/middleware/development_server.rb +180 -0
- data/lib/yodel/middleware/error_pages.rb +72 -0
- data/lib/yodel/middleware/public_assets.rb +78 -0
- data/lib/yodel/middleware/request.rb +16 -0
- data/lib/yodel/middleware/site_detector.rb +22 -0
- data/lib/yodel/mime_types/default_mime_set.rb +28 -0
- data/lib/yodel/mime_types/mime_type.rb +68 -0
- data/lib/yodel/mime_types/mime_type_set.rb +41 -0
- data/lib/yodel/mime_types/mime_types.rb +6 -0
- data/lib/yodel/mime_types/yodel.rb +15 -0
- data/lib/yodel/models/api/api.rb +1 -0
- data/lib/yodel/models/api/api_call.rb +87 -0
- data/lib/yodel/models/core/associations/association.rb +37 -0
- data/lib/yodel/models/core/associations/associations.rb +22 -0
- data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
- data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
- data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
- data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
- data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
- data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
- data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
- data/lib/yodel/models/core/associations/record_association.rb +38 -0
- data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
- data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
- data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
- data/lib/yodel/models/core/attachments/attachment.rb +73 -0
- data/lib/yodel/models/core/attachments/image.rb +38 -0
- data/lib/yodel/models/core/core.rb +15 -0
- data/lib/yodel/models/core/fields/alias_field.rb +32 -0
- data/lib/yodel/models/core/fields/array_field.rb +64 -0
- data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
- data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
- data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
- data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
- data/lib/yodel/models/core/fields/color_field.rb +4 -0
- data/lib/yodel/models/core/fields/date_field.rb +35 -0
- data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
- data/lib/yodel/models/core/fields/email_field.rb +10 -0
- data/lib/yodel/models/core/fields/enum_field.rb +33 -0
- data/lib/yodel/models/core/fields/field.rb +154 -0
- data/lib/yodel/models/core/fields/fields.rb +29 -0
- data/lib/yodel/models/core/fields/fields_field.rb +31 -0
- data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
- data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
- data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
- data/lib/yodel/models/core/fields/function_field.rb +28 -0
- data/lib/yodel/models/core/fields/hash_field.rb +54 -0
- data/lib/yodel/models/core/fields/html_field.rb +15 -0
- data/lib/yodel/models/core/fields/image_field.rb +11 -0
- data/lib/yodel/models/core/fields/integer_field.rb +25 -0
- data/lib/yodel/models/core/fields/password_field.rb +21 -0
- data/lib/yodel/models/core/fields/self_field.rb +27 -0
- data/lib/yodel/models/core/fields/string_field.rb +15 -0
- data/lib/yodel/models/core/fields/tags_field.rb +7 -0
- data/lib/yodel/models/core/fields/text_field.rb +7 -0
- data/lib/yodel/models/core/fields/time_field.rb +36 -0
- data/lib/yodel/models/core/functions/function.rb +471 -0
- data/lib/yodel/models/core/functions/functions.rb +2 -0
- data/lib/yodel/models/core/functions/trigger.rb +14 -0
- data/lib/yodel/models/core/log/log.rb +33 -0
- data/lib/yodel/models/core/log/log_entry.rb +12 -0
- data/lib/yodel/models/core/model/abstract_model.rb +59 -0
- data/lib/yodel/models/core/model/model.rb +460 -0
- data/lib/yodel/models/core/model/mongo_model.rb +25 -0
- data/lib/yodel/models/core/model/site_model.rb +17 -0
- data/lib/yodel/models/core/mongo/mongo.rb +3 -0
- data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
- data/lib/yodel/models/core/mongo/query.rb +68 -0
- data/lib/yodel/models/core/mongo/record_index.rb +89 -0
- data/lib/yodel/models/core/record/abstract_record.rb +411 -0
- data/lib/yodel/models/core/record/embedded_record.rb +47 -0
- data/lib/yodel/models/core/record/mongo_record.rb +83 -0
- data/lib/yodel/models/core/record/record.rb +386 -0
- data/lib/yodel/models/core/record/section.rb +21 -0
- data/lib/yodel/models/core/record/site_record.rb +31 -0
- data/lib/yodel/models/core/site/migration.rb +52 -0
- data/lib/yodel/models/core/site/remote.rb +61 -0
- data/lib/yodel/models/core/site/site.rb +202 -0
- data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
- data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
- data/lib/yodel/models/core/validations/errors.rb +51 -0
- data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
- data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
- data/lib/yodel/models/core/validations/format_validation.rb +10 -0
- data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
- data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
- data/lib/yodel/models/core/validations/length_validation.rb +28 -0
- data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
- data/lib/yodel/models/core/validations/required_validation.rb +9 -0
- data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
- data/lib/yodel/models/core/validations/validation.rb +39 -0
- data/lib/yodel/models/core/validations/validations.rb +15 -0
- data/lib/yodel/models/email/email.rb +79 -0
- data/lib/yodel/models/migrations/01_record_model.rb +29 -0
- data/lib/yodel/models/migrations/02_page_model.rb +45 -0
- data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
- data/lib/yodel/models/migrations/04_group_model.rb +61 -0
- data/lib/yodel/models/migrations/05_user_model.rb +24 -0
- data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
- data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
- data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
- data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
- data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
- data/lib/yodel/models/migrations/11_email_model.rb +28 -0
- data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
- data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
- data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
- data/lib/yodel/models/models.rb +8 -0
- data/lib/yodel/models/pages/form_builder.rb +379 -0
- data/lib/yodel/models/pages/html_decorator.rb +132 -0
- data/lib/yodel/models/pages/layout.rb +120 -0
- data/lib/yodel/models/pages/menu.rb +32 -0
- data/lib/yodel/models/pages/page.rb +378 -0
- data/lib/yodel/models/pages/pages.rb +7 -0
- data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
- data/lib/yodel/models/pages/redirect_page.rb +11 -0
- data/lib/yodel/models/search/search.rb +1 -0
- data/lib/yodel/models/search/search_page.rb +58 -0
- data/lib/yodel/models/security/facebook_login_page.rb +55 -0
- data/lib/yodel/models/security/group.rb +10 -0
- data/lib/yodel/models/security/guests_group.rb +5 -0
- data/lib/yodel/models/security/login_page.rb +20 -0
- data/lib/yodel/models/security/logout_page.rb +13 -0
- data/lib/yodel/models/security/noone_group.rb +5 -0
- data/lib/yodel/models/security/owner_group.rb +8 -0
- data/lib/yodel/models/security/password.rb +5 -0
- data/lib/yodel/models/security/password_reset_page.rb +47 -0
- data/lib/yodel/models/security/security.rb +10 -0
- data/lib/yodel/models/security/user.rb +33 -0
- data/lib/yodel/public/core/css/core.css +257 -0
- data/lib/yodel/public/core/css/reset.css +48 -0
- data/lib/yodel/public/core/images/cross.png +0 -0
- data/lib/yodel/public/core/images/spinner.gif +0 -0
- data/lib/yodel/public/core/images/tick.png +0 -0
- data/lib/yodel/public/core/images/yodel.png +0 -0
- data/lib/yodel/public/core/js/jquery.min.js +18 -0
- data/lib/yodel/public/core/js/json2.js +480 -0
- data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
- data/lib/yodel/request/authentication.rb +76 -0
- data/lib/yodel/request/flash.rb +28 -0
- data/lib/yodel/request/request.rb +4 -0
- data/lib/yodel/requires.rb +47 -0
- data/lib/yodel/task_queue/queue_daemon.rb +33 -0
- data/lib/yodel/task_queue/queue_worker.rb +32 -0
- data/lib/yodel/task_queue/stats_thread.rb +27 -0
- data/lib/yodel/task_queue/task.rb +62 -0
- data/lib/yodel/task_queue/task_queue.rb +40 -0
- data/lib/yodel/types/date.rb +5 -0
- data/lib/yodel/types/object_id.rb +11 -0
- data/lib/yodel/types/time.rb +5 -0
- data/lib/yodel/version.rb +3 -0
- data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
- data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
- data/system/etc/resolver/yodel +2 -0
- data/system/usr/local/bin/yodel_command_runner +2 -0
- data/system/usr/local/etc/yodel/development_settings.rb +28 -0
- data/system/usr/local/etc/yodel/production_settings.rb +27 -0
- data/system/var/log/yodel.log +0 -0
- data/test/helper.rb +18 -0
- data/test/test_yodel.rb +4 -0
- data/yodel.gemspec +47 -0
- 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,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
|