cck_forms 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/README.md +1 -0
  4. data/Rakefile +40 -0
  5. data/lib/cck_forms/date_time.rb +58 -0
  6. data/lib/cck_forms/engine.rb +23 -0
  7. data/lib/cck_forms/form_builder_extensions.rb +18 -0
  8. data/lib/cck_forms/parameter_type_class/album.rb +100 -0
  9. data/lib/cck_forms/parameter_type_class/base.rb +275 -0
  10. data/lib/cck_forms/parameter_type_class/boolean.rb +24 -0
  11. data/lib/cck_forms/parameter_type_class/checkboxes.rb +202 -0
  12. data/lib/cck_forms/parameter_type_class/date.rb +35 -0
  13. data/lib/cck_forms/parameter_type_class/date_range.rb +53 -0
  14. data/lib/cck_forms/parameter_type_class/date_time.rb +76 -0
  15. data/lib/cck_forms/parameter_type_class/enum.rb +53 -0
  16. data/lib/cck_forms/parameter_type_class/file.rb +80 -0
  17. data/lib/cck_forms/parameter_type_class/float.rb +20 -0
  18. data/lib/cck_forms/parameter_type_class/image.rb +20 -0
  19. data/lib/cck_forms/parameter_type_class/integer.rb +77 -0
  20. data/lib/cck_forms/parameter_type_class/integer_range.rb +150 -0
  21. data/lib/cck_forms/parameter_type_class/map.rb +259 -0
  22. data/lib/cck_forms/parameter_type_class/phones.rb +204 -0
  23. data/lib/cck_forms/parameter_type_class/string.rb +13 -0
  24. data/lib/cck_forms/parameter_type_class/string_collection.rb +23 -0
  25. data/lib/cck_forms/parameter_type_class/text.rb +18 -0
  26. data/lib/cck_forms/parameter_type_class/time.rb +30 -0
  27. data/lib/cck_forms/parameter_type_class/work_hours.rb +369 -0
  28. data/lib/cck_forms/version.rb +3 -0
  29. data/lib/cck_forms.rb +4 -0
  30. data/vendor/assets/javascripts/cck_forms/jquery.workhours.js +399 -0
  31. data/vendor/assets/javascripts/cck_forms/map.js.coffee +322 -0
  32. metadata +116 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9bd0c788c8c8ba1cd70dc9114891a33421105bc4
4
+ data.tar.gz: 6d1a53c9d48100fc922b37a87aa47255da929554
5
+ SHA512:
6
+ metadata.gz: 7af10e028591267acfaebd5d67a22027cd8a3b7a0d8769fbc212f9b30e99a166cba7e8aa699fff3510d8f92b50a16b05d74ce02bc62e29b1f5ffc8bc90a67066
7
+ data.tar.gz: ed6f2da8765459afd914fe42595068fa803b897f861e0650550e037f617d68276c58c59777f4d39045941cda21efd5e1e55947274c8dd5a8bcb012a0398f494d
data/LICENSE ADDED
@@ -0,0 +1 @@
1
+ Released under the MIT License.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ TODO
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'CckForms'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,58 @@
1
+ module CckForms::DateTime
2
+ extend ActiveSupport::Concern
3
+
4
+ module DateTimeParser
5
+ def date_object_from_what_stored_in_database(value)
6
+ parsed_value = nil
7
+ if value.is_a? Hash
8
+
9
+ force_timezone = true
10
+ v = value
11
+
12
+ if v.has_key? 'hour' and v.has_key? 'month'
13
+ parsed_value = DateTime.civil(v['year'], v['year'], v['day'], v['hour'], v['minute'])
14
+ elsif v.has_key? 'hour' or v.has_key? 'month'
15
+ if v.has_key? 'hour'
16
+ parsed_value = Time.parse("#{v['hour']}:#{v['minute']}")
17
+ elsif v.has_key? 'month'
18
+ parsed_value = Date.parse("#{v['day']}.#{v['month']}.#{v['year']}")
19
+ end
20
+ else
21
+ if v.has_key? '(5i)' and v.has_key? '(1i)' # date & time
22
+ parsed_value = DateTime.parse("#{v['(3i)']}.#{v['(2i)']}.#{v['(1i)']} #{v['(4i)']}:#{v['(5i)']}")
23
+ elsif v.has_key? '(5i)' # time
24
+ parsed_value = Time.parse("#{v['(4i)']}:#{v['(5i)']}")
25
+ elsif v.has_key? '(1i)' # date
26
+ parsed_value = Date.parse("#{v['(3i)']}.#{v['(2i)']}.#{v['(1i)']}")
27
+ end
28
+ end
29
+ end
30
+ value = parsed_value if parsed_value
31
+
32
+ if force_timezone || value.is_a?(Time)
33
+ value = value.change offset: ActiveSupport::TimeZone.new(Rails.application.config.time_zone).formatted_offset
34
+ end
35
+
36
+ value
37
+ end
38
+ end
39
+
40
+ module ClassMethods
41
+ include DateTimeParser
42
+
43
+ def default_options_for_date_time_selectors(value)
44
+ date_in_time_zone = value.in_time_zone(Rails.application.config.time_zone) rescue nil
45
+ [{default: date_in_time_zone, include_blank: false, with_css_classes: true}, {class: 'form-control'}]
46
+ end
47
+
48
+ def demongoize_value(value, parameter_type_class=nil)
49
+ date_object_from_what_stored_in_database value
50
+ end
51
+ end
52
+
53
+ include DateTimeParser
54
+
55
+ def mongoize
56
+ date_object_from_what_stored_in_database value
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ module CckForms
2
+ class Engine < ::Rails::Engine
3
+ config.autoload_paths << File.expand_path('../..', __FILE__)
4
+
5
+ config.after_initialize do
6
+ CckForms::ParameterTypeClass::Base.load_type_classes if Rails.application.config.cck_forms.load_type_classes
7
+ ActionView::Base.send :include, CckForms::FormBuilderExtensions if Rails.application.config.cck_forms.extend_form_builder
8
+ end
9
+
10
+ config.cck_forms = ActiveSupport::OrderedOptions.new
11
+
12
+ # general
13
+ config.cck_forms.load_type_classes = true
14
+ config.cck_forms.extend_form_builder = true
15
+
16
+ # phones
17
+ config.cck_forms.phones = ActiveSupport::OrderedOptions.new
18
+ config.cck_forms.phones.min_phones_in_form = 3
19
+ config.cck_forms.phones.mobile_codes = %w{ 777 705 771 701 702 775 778 700 707 }
20
+ config.cck_forms.phones.prefix = '+7'
21
+ config.cck_forms.phones.number_parts_glue = '-'
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # Примесь для расширения ActionView::Helpers::FormBuilder, чтобы работать с нашими полями:
2
+ #
3
+ # class CckEnabled
4
+ # field :logo, type: CckForms::ParameterTypeClass::Image
5
+ # end
6
+ #
7
+ # = form_for @cck_enabled do |f|
8
+ # = f.standalone_cck_field :logo
9
+ #
10
+ module CckForms::FormBuilderExtensions
11
+ ActionView::Helpers::FormBuilder.class_eval do
12
+ def standalone_cck_field(field_name, options = {})
13
+ fields_for(field_name) do |ff|
14
+ @template.raw object.send(field_name).build_form ff, options
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,100 @@
1
+ class CckForms::ParameterTypeClass::Album
2
+ include CckForms::ParameterTypeClass::Base
3
+
4
+ def self.name
5
+ 'Альбом'
6
+ end
7
+
8
+ # Преобразует данные для Монго.
9
+ # Приводит переданный массив или хэш объектов Neofiles::Image или их идентификаторов в массив.
10
+ def mongoize
11
+ the_value = value.is_a?(Hash) ? value['value'] : value
12
+
13
+ result = []
14
+ if the_value.respond_to? :each
15
+ the_value.each do |image|
16
+ image = image[1] if the_value.respond_to? :each_value
17
+ result.push(image.is_a?(::Neofiles::Image) ? image.id : image.to_s) if image.present?
18
+ end
19
+ end
20
+
21
+ result
22
+ end
23
+
24
+ # Преобразуем данные из Монго.
25
+ # Приводим в массив (по идее, массив идентификаторов Neofiles::Image, хотя может быть что угодно).
26
+ def self.demongoize_value(value, parameter_type_class=nil)
27
+ if value.respond_to? :each
28
+ value
29
+ else
30
+ []
31
+ end
32
+ end
33
+
34
+ # Строит форму для обновления файлов альбома.
35
+ #
36
+ # Ключи options:
37
+ #
38
+ # value - текущее значение (идентификатор или объект Neofiles::Album)
39
+ def build_form(form_builder, options)
40
+ set_value_in_hash options
41
+
42
+ options = {
43
+
44
+ }.merge options
45
+
46
+ the_value = options[:value].is_a?(Array) ? options[:value] : []
47
+ input_name_prefix = form_builder.object_name + '[value][]'
48
+ widget_id_prefix = form_builder_name_to_id form_builder, '_value_'
49
+ file_forms = []
50
+
51
+ the_value.each do |image_id|
52
+ file_forms << CckForms::ParameterTypeClass::Image.create_load_form( helper: self,
53
+ file: image_id,
54
+ input_name: input_name_prefix,
55
+ append_create: false,
56
+ clean_remove: true,
57
+ widget_id: widget_id_prefix + file_forms.length.to_s,
58
+ multiple: true,
59
+ with_desc: options[:with_desc])
60
+ end
61
+
62
+ add_file_form = CckForms::ParameterTypeClass::Image.create_load_form( helper: self,
63
+ file: nil,
64
+ input_name: input_name_prefix,
65
+ append_create: true,
66
+ clean_remove: true,
67
+ widget_id: widget_id_prefix + file_forms.length.to_s,
68
+ multiple: true,
69
+ with_desc: options[:with_desc])
70
+
71
+ id = form_builder_name_to_id form_builder, rand(1...1_000_000).to_s
72
+
73
+ <<HTML
74
+ <div class="neofiles-album-compact" id="#{id}">
75
+ #{file_forms.join}
76
+ #{add_file_form}
77
+ </div>
78
+
79
+ <script type="text/javascript">
80
+ $(function() {
81
+ $("##{id}").album();
82
+ });
83
+ </script>
84
+ HTML
85
+ end
86
+
87
+ def to_s(options = nil)
88
+ ''
89
+ end
90
+
91
+ def to_diff_value(options = {})
92
+ view_context = options[:view_context]
93
+ images_html_list = []
94
+ value.each do |image_id|
95
+ images_html_list << "<img style='width: 64px; height: 64px;' src='#{view_context.neofiles_image_path(id: image_id, format: '64x64', crop: 1)}'>"
96
+ end
97
+
98
+ images_html_list.join.html_safe if images_html_list.any?
99
+ end
100
+ end
@@ -0,0 +1,275 @@
1
+ # Базовая примесь для всех типов полей. Определяет всякие помогайки. Ее нужно включать методом include во все типы,
2
+ # например, String, Checkboxes и т. п.
3
+ #
4
+ # class CckForms::ParameterTypeClass::NewType
5
+ # include CckForms::ParameterTypeClass::Base
6
+ #
7
+ # def self.name
8
+ # 'Новый тип'
9
+ # end
10
+ # end
11
+ #
12
+ # CckForms::ParameterTypeClass::NewType.name # 'Новый тип', берется из CckForms::ParameterTypeClass::NewType::name
13
+ #
14
+ # Использование типов:
15
+ #
16
+ # field :cover_photo, type: CckForms::ParameterTypeClass::Image
17
+ # field :gallery, type: CckForms::ParameterTypeClass::Album
18
+ # field :description, type: CckForms::ParameterTypeClass::Text
19
+ #
20
+ # Что есть в базовом типе:
21
+ #
22
+ # 1) все помощники УРЛов вида edit_article_path, включаемые через include Rails.application.routes.url_helpers;
23
+ #
24
+ # 2) методы cck_param и value, которые возвращают текущий параметр (module CckForms::*::Parameter) и его значение;
25
+ #
26
+ # 3) методы with_cck_param(param) do ... и with_value(value) do ..., которые устанавливают соотв, значения методов
27
+ # cck_param/value на время выполнения блока (еще есть with_cck_param_and_value(param, value) do ...);
28
+ #
29
+ # 4) динамический метод (через method_missing и respond_to?) ..._with_value(value, args*), который делает то же,
30
+ # что и with_value, но для вызова 1 метода;
31
+ #
32
+ # 5) set_value_in_hash(hash), который кладет value в hash[:value];
33
+ #
34
+ # 6) помощники для получения ХТМЛ ID form(_builder)?_name_to_id;
35
+ #
36
+ # 7) методы для потребителей (типизированных объектов):
37
+ #
38
+ # self.code - код типа из имени модуля (CckForms::ParameterType::RichText -> rich_text)
39
+ # self.name - имя типа ("Cтрока")
40
+ #
41
+ module CckForms::ParameterTypeClass::Base
42
+ extend ActiveSupport::Concern
43
+
44
+ included do
45
+ # Кое-где понадобятся помогайки-пути, сразу их включим.
46
+ include Rails.application.routes.url_helpers
47
+
48
+ attr_accessor :value
49
+ attr_reader :valid_values_class_name, :cck_parameter
50
+
51
+ def initialize(options)
52
+ options = options.symbolize_keys
53
+
54
+ self.value = options[:value]
55
+ if value.is_a?(Hash) && value.has_key?('value')
56
+ self.value = self.value['value']
57
+ end
58
+
59
+ valid_values = options.delete(:valid_values).presence
60
+ valid_values_class_name = options.delete(:valid_values_class_name)
61
+ cck_parameter = options.delete(:cck_parameter)
62
+
63
+ @valid_values = valid_values
64
+ @valid_values_class_name = valid_values_class_name
65
+ @cck_parameter = cck_parameter
66
+ @extra_options = options[:extra_options] || options.dup
67
+ end
68
+ end
69
+
70
+
71
+
72
+ module ClassMethods
73
+ def demongoize(something_from_database)
74
+ new value: demongoize_value(something_from_database)
75
+ end
76
+
77
+ def demongoize_value(value, parameter_type_class=nil)
78
+ value
79
+ end
80
+
81
+ def mongoize(object)
82
+ case object
83
+ when self then object.mongoize
84
+ # TODO: why only these classes? does any scalar fit?
85
+ when Hash, Array, String then new(value: object).mongoize
86
+ else object
87
+ end
88
+ end
89
+
90
+ # Возвращает строку с яваскриптовым кодом для выполнения в БД кода emit операции map-reduce на поле данного типа.
91
+ # Поскольку у различных типов данные в БД хранятся по-разному, и вообще понятие "текущего значения этого типа"
92
+ # различается, мы вынуждены отдельно описывать операцию emit, там где это необходимо. Смысл в том, чтобы этот метод
93
+ # вызвал emit для каждого хранимого значения.
94
+ #
95
+ # Пример: есть поле city типа checkboxes, хранящее список городов, то-есть объекты, описываемые этим полем,
96
+ # могут быть в разных городах. Если мы хотим сделать группировочный запрос (частный случай map-reduce) в БД, чтобы,
97
+ # например, подсчитать кол-во объектов в городах, мы не можем просто сделать emit для всего поля city, поскольку
98
+ # оно содержит список городов. Мы должны вызывать emit для каждого элемента массива (т. е., для каждого идентификатора
99
+ # города). Генерацией этого кода и занимается этот метод.
100
+ #
101
+ # В частности, он используется при подсчете популярных значения различных полей (сколько объявлений, поданых
102
+ # в разные города, и т. п.)
103
+ #
104
+ # По-умолчанию, считаем, что значение, хранимое в поле "#{feild_name}" атомарно, и вызываем emit для него.
105
+ #
106
+ # Подробнее про map-reduce см. http://docs.mongodb.org/manual/applications/map-reduce/
107
+ def emit_map_reduce(feild_name)
108
+ field_name = 'this.' + feild_name
109
+ "if(#{field_name} && #{field_name} != '') emit(#{field_name}, 1)"
110
+ end
111
+
112
+ # Конвертирует имя элемента формы в ID, например facility[cck_params][1][value] -> facility_cck_params_1_value.
113
+ def form_name_to_id(name)
114
+ name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, '_').sub(/_\z/, '')
115
+ end
116
+
117
+ # Методы, которые нужны наследникам (CckForms::ParameterTypeClass::*) для отображения, сортировки и прочей настройки самих типов.
118
+ # Эти методы не нужны обычным объектам - пользователям типов.
119
+
120
+ # CckForms::ParameterTypeClass::Checkboxes -> 'checkboxes'
121
+ # CckForms::ParameterTypeClass::RichText -> 'rich_text'
122
+ def code
123
+ self.to_s.demodulize.underscore
124
+ end
125
+
126
+ # Имя типа, например, для select>option[value]
127
+ def name
128
+ nil
129
+ end
130
+ end
131
+
132
+
133
+
134
+ # Загрузит все классы-наследники.
135
+ # TODO: relies on all classes to reside in this class' directory
136
+ def self.load_type_classes
137
+ return if @type_classes_loaded
138
+
139
+ path = File.dirname(__FILE__)
140
+ Dir[path + '/*.rb'].each do |filename|
141
+ require_dependency filename unless filename.ends_with? '/base.rb'
142
+ end
143
+
144
+ @type_classes_loaded = true
145
+ end
146
+
147
+
148
+
149
+ # "Нормальные" методы, которые будут доступны всем пользователям данного модуля.
150
+
151
+ # -> [[key1, value1], [key2, value2], ...]
152
+ # Нужен для построения ХТМЛ СЕЛЕКТов.
153
+ def valid_values_enum
154
+ valid_values = self.valid_values
155
+ return [] if valid_values.blank?
156
+ result = []
157
+ method_for_enumerating = valid_values.is_a?(Array) ? :each_with_index : :each_pair
158
+ valid_values.send(method_for_enumerating) do |key, value|
159
+ result.push [value, key]
160
+ end
161
+ result
162
+ end
163
+
164
+ # -> "georgian: грузинская, albanian: албанская"
165
+ # Нужен для построения ХТМЛа и строк.
166
+ def valid_values_as_string
167
+ valid_values_enum.map { |enum| "#{enum[1]}: #{enum[0]}" }.join "\n"
168
+ end
169
+
170
+ # Чтобы получать данные из форм и сохранять в БД. Использовать во вьюхах:
171
+ # = f.text_field :valid_values_as_string
172
+ def valid_values_as_string=(string)
173
+ new_valid_values = {}
174
+ string.split("\n").reject { |line| line.blank? }.each do |line|
175
+ splitted = line.split(':', 2)
176
+ new_valid_values[splitted[0].strip] = splitted[1].strip if splitted.length == 2 and splitted[0].present?
177
+ end
178
+ self.valid_values = new_valid_values
179
+ end
180
+
181
+ # Вернет класс, указанный как valid_values_class_name.
182
+ # "City" -> City
183
+ def valid_values_class
184
+ if valid_values_class_name.present?
185
+ if valid_values_class_name.is_a? Class
186
+ valid_values_class_name
187
+ else # если это не строка, то пусть будет выборошено исключение
188
+ valid_values_class_name.constantize
189
+ end
190
+ else
191
+ nil
192
+ end
193
+ end
194
+
195
+ # Существует ли valid_values_class?
196
+ def valid_values_class?
197
+ not valid_values_class.nil?
198
+ end
199
+
200
+ # Если valid_values пустой, и есть valid_values_class, запишет в valid_values все значения
201
+ # из valid_values_class. Считает, что класс похож на ActiveRecord, и значения берет из all.
202
+ def valid_values
203
+ @valid_values ||= begin
204
+ if vv_class = valid_values_class
205
+ valid_values = {}
206
+ vv_class.all.each { |valid_value_object| valid_values[valid_value_object.id] = valid_value_object.to_s }
207
+ valid_values
208
+ end
209
+ end
210
+ end
211
+
212
+ # Строит форму редактирования. Просто input:text со всеми переданными опциями (значение можно определить через
213
+ # options[:value]).
214
+ def build_form(form_builder, options)
215
+ set_value_in_hash options
216
+ form_builder.text_field :value, options
217
+ end
218
+
219
+ # Вернем в виде HTML
220
+ def to_html(options = nil)
221
+ to_s options
222
+ end
223
+
224
+ # Чтобы принимать аргумент options
225
+ def to_s(options = nil)
226
+ value.to_s
227
+ end
228
+
229
+ # Отображение для страниц "было-стало" в админках и пр. (например, тип "карта" можнет вернуть ХТМЛ картинки-миниатюры).
230
+ def to_diff_value(options = nil)
231
+ to_html options
232
+ end
233
+
234
+ # Формируем поисковый запрос для монго на основе запроса (типа мини-DSL язык запроса, свой для каждого типа).
235
+ def search(selectable, field, query)
236
+ selectable.where(field => query.to_s)
237
+ end
238
+
239
+ # Нужен для Rails.application.routes.url_helpers
240
+ def default_url_options
241
+ {}
242
+ end
243
+
244
+ # Реализация перобразования в/из Монго по умолчанию: берем то, что в value
245
+ def mongoize
246
+ value
247
+ end
248
+
249
+ def demongoize_value
250
+ self.class.demongoize_value value, self
251
+ end
252
+
253
+ def demongoize_value!
254
+ self.value = demongoize_value
255
+ end
256
+
257
+
258
+
259
+ private
260
+
261
+ # options[:value] = value
262
+ def set_value_in_hash(options)
263
+ options[:value] = value unless options.has_key? :value
264
+ end
265
+
266
+ # См. ClassMethod.form_name_to_id
267
+ def form_name_to_id(name)
268
+ self.class.form_name_to_id name
269
+ end
270
+
271
+ # Конвертирует имя элемента формы из FormBuilder, см. form_name_to_id.
272
+ def form_builder_name_to_id(form_builder, suffix = '')
273
+ form_name_to_id([form_builder.options[:namespace], form_builder.object_name].compact.join('_') + suffix)
274
+ end
275
+ end
@@ -0,0 +1,24 @@
1
+ class CckForms::ParameterTypeClass::Boolean
2
+ include CckForms::ParameterTypeClass::Base
3
+
4
+ def self.name
5
+ 'Галочка (да/нет)'
6
+ end
7
+
8
+ def value?
9
+ value.present? && value != '0'
10
+ end
11
+
12
+ def mongoize
13
+ value?
14
+ end
15
+
16
+ def to_s(options = nil)
17
+ value? ? 'да' : 'нет'
18
+ end
19
+
20
+ def build_form(form_builder, options)
21
+ set_value_in_hash options
22
+ form_builder.check_box :value, options.merge(value: 1, checked: value?)
23
+ end
24
+ end