cck_forms 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +254 -1
  3. data/lib/cck_forms/date_time.rb +4 -0
  4. data/lib/cck_forms/engine.rb +2 -2
  5. data/lib/cck_forms/form_builder_extensions.rb +2 -1
  6. data/lib/cck_forms/neofiles_denormalize.rb +71 -0
  7. data/lib/cck_forms/parameter_type_class/album.rb +15 -11
  8. data/lib/cck_forms/parameter_type_class/base.rb +58 -63
  9. data/lib/cck_forms/parameter_type_class/boolean.rb +6 -0
  10. data/lib/cck_forms/parameter_type_class/checkboxes.rb +31 -15
  11. data/lib/cck_forms/parameter_type_class/date.rb +5 -0
  12. data/lib/cck_forms/parameter_type_class/date_range.rb +8 -2
  13. data/lib/cck_forms/parameter_type_class/date_time.rb +10 -8
  14. data/lib/cck_forms/parameter_type_class/enum.rb +11 -0
  15. data/lib/cck_forms/parameter_type_class/file.rb +27 -20
  16. data/lib/cck_forms/parameter_type_class/float.rb +3 -0
  17. data/lib/cck_forms/parameter_type_class/image.rb +3 -1
  18. data/lib/cck_forms/parameter_type_class/integer.rb +10 -4
  19. data/lib/cck_forms/parameter_type_class/integer_range.rb +18 -0
  20. data/lib/cck_forms/parameter_type_class/map.rb +23 -23
  21. data/lib/cck_forms/parameter_type_class/phones.rb +14 -15
  22. data/lib/cck_forms/parameter_type_class/string.rb +3 -0
  23. data/lib/cck_forms/parameter_type_class/string_collection.rb +6 -0
  24. data/lib/cck_forms/parameter_type_class/text.rb +4 -0
  25. data/lib/cck_forms/parameter_type_class/time.rb +4 -0
  26. data/lib/cck_forms/parameter_type_class/work_hours.rb +24 -36
  27. data/lib/cck_forms/version.rb +1 -1
  28. data/vendor/assets/javascripts/cck_forms/index.js +2 -0
  29. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b40277032af600f826f2a98dbf141a6f5c2612c
4
- data.tar.gz: 7f1283c1d9b3cc1e5fcdde697c5bcb345d96fd1a
3
+ metadata.gz: 879b5ac5dd9add74c06463c6ea0ea8d9ac919690
4
+ data.tar.gz: 1deecc0d0cb355f2b1ab22881ffea197ff920926
5
5
  SHA512:
6
- metadata.gz: 46bb42b6d45311327227f56ddce705a6626df1796844b88533c6383d38b497f37d8547ca51f482f84c2dcbef74a6bb3ad8b2f37d5e41d07877a901f55e31534a
7
- data.tar.gz: cd1050682502bfd5a6968e8cdc6b60342610604795d23c9248394a1fec1156725428e80a2c69177144cb81fea24acbb6997d1052687f4cc054faaa6f9e16fde9
6
+ metadata.gz: e493ead26cdd54b827078bfdd6ac8c63446dfb1e9e80af6cf42b0f3607d2944101650764d130fd3c1a8c1ff8290051c0c841f015676fe4fc5eefe4d86888cddf
7
+ data.tar.gz: 28a00be50aaecd6165db9dbb3158a1971de0b74d30fe18c97b7fed33ff9e95cb7f527ae9e7bbdad8bd3808f4fc6d25f947fe9c02e83ca846c171e46c99ccf202
data/README.md CHANGED
@@ -1 +1,254 @@
1
- TODO
1
+ CCK Forms
2
+ =========
3
+
4
+ CCK Forms is a companion to the yet-to-be-published CCK gem. (CCK stands for Content Construction Kit — a name borrowed
5
+ from PHP Drupal CMS.)
6
+
7
+ Whilst the CCK gem provides ability to store category-related custom fields in an ActiveModel, this gem defines possible
8
+ field types for that matter (string, enum, image album etc.)
9
+
10
+ As CCK is aimed to simplify storing & editing model fields (especially in admin panels), these custom field types define
11
+ common (and complex) notions like work hours, sets of images, WYSIWYG-capable fields and so on. "Define" here means
12
+ these field types can be stored in CCK-capable models and they all have HTML form templates. The latter implies possible
13
+ standalone usage, for example, if you just need a field of type Image inside your model, you can use CCK Forms without
14
+ CCK and still have nice looking and convenient editor form template.
15
+
16
+ The UI is written in Bootstrap 3 and can not be easily changed, sorry folks.
17
+
18
+
19
+ Criticism
20
+ ---------
21
+
22
+ Generally speaking, this gem combines two aspects: CCK-related storage things and UI editor forms. The latter is very
23
+ project & design dependent and should not be fixed in the gem (especially in the way it is now, with HTML constructed
24
+ in class methods, heavily relying on Bootstrap). Moreover, it may be a good idea to completely decouple UI into separate
25
+ gem/module using, say, Simpleforms to do the frontend job.
26
+
27
+ Then, this gem will only define pure classes to be used either with CCK or standalone as proper "types", like String
28
+ or MapPoint or anything else.
29
+
30
+
31
+ Installation, dependencies
32
+ --------------------------
33
+
34
+ ***Important***: CCK requires a MongoDB database as a mean to store data, so your models must be Mongoid documents.
35
+
36
+ Add CCK Forms and its dependencies to your gemfile:
37
+
38
+ ``` ruby
39
+ # neofiles-related
40
+ gem 'neofiles'
41
+ gem 'ruby-imagespec', git: 'git://github.com/dim/ruby-imagespec.git'
42
+ gem 'mini_magick', '3.7.0'
43
+
44
+ gem 'cck_forms'
45
+ ```
46
+
47
+ This gem requires [Neofiles](https://github.com/ilya-konanykhin/neofiles) for File, Image and Album types. Please read
48
+ its installation instructions, there are some gotchas.
49
+
50
+ Next, include CSS & JS files where needed (say, application.js or admin.js)...
51
+
52
+ ``` javascript
53
+ #= require jquery # neofiles requires jquery
54
+ #= require neofiles
55
+ #= require cck_forms
56
+ ```
57
+
58
+ ... and application.css/admin.css or whatever place you need it in:
59
+
60
+ ``` css
61
+ *= require neofiles
62
+ ```
63
+
64
+ Don't forget to set up Neofiles routing!
65
+
66
+
67
+ Usage with CCK
68
+ --------------
69
+
70
+ Basic usage only, more to come when CCK gem will finally be published.
71
+
72
+ ```ruby
73
+ # in model:
74
+ class Content
75
+ include Mongoid::Document
76
+ include Cck::Cckable
77
+
78
+ cck_config do |c|
79
+ c.category 'news' do |cc|
80
+ cc.string 'title', 'News title'
81
+ cc.enum 'region', valid_values: {world: 'World news', local: 'Local news'}
82
+ cc.image 'announce_pic', 'Announce image', hint: '100x100 only'
83
+ cc.text 'body', 'News article body'
84
+ cc.map 'map_point', 'Where did it happen?'
85
+
86
+ c.require_params %w{title body}
87
+ end
88
+
89
+ c.category 'page' do |cc|
90
+ ...
91
+ end
92
+ end
93
+
94
+ field :category_id, type: String
95
+ end
96
+
97
+ # in controller:
98
+ def edit
99
+ @content = Content.new category_id: params[:category_id]
100
+ end
101
+
102
+ # in view:
103
+ = form_for @content do |ff|
104
+ = cck_fields_for :cck_params
105
+
106
+ # elsewhere:
107
+ content = Content.find(...)
108
+ content.cck_params[:title].to_s
109
+ content.cck_params[:map_point].value[:latitude]
110
+ content.cck_params.to_h.each_pair { |k, v| puts "#{k}: #{v}" } # outputs every CCK field with its ID
111
+ ```
112
+
113
+
114
+ Standalone usage
115
+ ----------------
116
+
117
+ First, create a model and add as many CCK fields as you need:
118
+
119
+ ```ruby
120
+ class User
121
+ include Mongoid::Document
122
+
123
+ field :avatar, type: CckForms::ParameterTypeClass::Image
124
+ field :cv, type: CckForms::ParameterTypeClass::File
125
+ field :phones, type: CckForms::ParameterTypeClass::Phones
126
+
127
+ validates do |doc|
128
+ if doc.avatar.try(:value) && doc.avatar.value.width > 1000
129
+ doc.errors.add :avatar, 'must be no more than 1000 px wide'
130
+ end
131
+
132
+ if doc.phones.try(:value) && doc.phones.value.count > 2
133
+ doc.errors.add :phones, 'must have at most 2 phone numbers'
134
+ end
135
+ end
136
+ end
137
+ ```
138
+
139
+ Then in a view form use helper to output UI:
140
+
141
+ ```slim
142
+ = form_for @user, html: {class: 'form-horizontal'} do |f|
143
+ .form-group
144
+ label.control-label.col-sm-2 Avatar
145
+ .col-sm-9= f.standalone_cck_field :avatar
146
+
147
+ .form-group
148
+ label.control-label.col-sm-2 CV
149
+ .col-sm-9= f.standalone_cck_field :cv, with_desc: true
150
+
151
+ .form-group
152
+ label.control-label.col-sm-2 Phone numbers
153
+ .col-sm-9= f.standalone_cck_field :phones
154
+ end
155
+ ```
156
+
157
+ Use model fields as usual with one exception: to get a field value you need to unwrap its first with call to `value`
158
+ (as it is a wrapper class instance). Assign values directly though.
159
+
160
+ ```ruby
161
+ user = User.first
162
+ puts "User avatar file: #{neofiles_image_path user.avatar.value} (#{user.avatar.value.length} bytes)"
163
+ puts "User phone#{user.phones.try(:value).try(:count).to_i == 0 ? '' : 's'}: #{user.phones.to_s}"
164
+
165
+ user.avatar = Neofiles::Image.find(...)
166
+ user.phones = ['+7 111 222 33 44', '1231231231', {prefix: '+906', code: '1234', number: '223344'}]
167
+ user.save! # should raise exception indicating phones validation failure
168
+ ```
169
+
170
+ Common methods:
171
+ * `value`: returns the real field value. Each field type has its own value, say Phones returns an array of phone numbers
172
+ and Map returns a hash with keys `:latitude, :longitude, :zoom`
173
+ * `to_s`: string representation
174
+ * `to_html`: HTML representation
175
+ * `to_diff_value`: representation in form was/became to show history of changes
176
+ * `search`: returns a Mongoid Criteria filled with search query params specific to this particular field type
177
+ * `build_form`: builds an HTML editor form
178
+
179
+
180
+ Available field types
181
+ ---------------------
182
+
183
+ ***Album***: sortable collection of images.
184
+
185
+ ***Boolean***: checkbox.
186
+
187
+ ***Checkboxes***: several checkboxes. Requires the `valid_values` option.
188
+
189
+ ***Date, DateTime, Time***: date/date&time/time select.
190
+
191
+ ***DateRange***: two sets of selects "date from/till".
192
+
193
+ ***Enum***: select or set of radio buttons. Requires the `valid_values` option.
194
+
195
+ ***File, Image***: single file or image.
196
+
197
+ ***Integer, Float***: numeric input.
198
+
199
+ ***IntegerRange***: two inputs for "number from/till".
200
+
201
+ ***Map***: map point. Two map providers available: Google and Yandex. Google requires an API key.
202
+
203
+ ***Phones***: array of phone numbers.
204
+
205
+ ***String***: text input.
206
+
207
+ ***StringCollection***: set of strings. Represented by a textarea, one line — one string.
208
+
209
+ ***Text***: textarea.
210
+
211
+ ***WorkHours***: complex input to construct work schedule on a weekly basis.
212
+
213
+
214
+ Configuration
215
+ -------------
216
+
217
+ CCK Forms offers the following config options which can be set in `config/application.rb` or `config/environments/*.rb`:
218
+
219
+ ```ruby
220
+ # load all available type classes on application start
221
+ config.cck_forms.load_type_classes = true
222
+
223
+ # extend default form builder to add `standalone_cck_field` method
224
+ config.cck_forms.extend_form_builder = true
225
+
226
+ # how many phone numbers will the edit form contain by default for each field
227
+ config.cck_forms.phones.min_phones_in_form = 3
228
+
229
+ # which area codes are considered as mobile carrier codes (mobile and landline numbers have different HTML forms)
230
+ # the codes listed below are Kazakhstan mobile operators as of year 2016
231
+ config.cck_forms.phones.mobile_codes = %w{ 777 705 771 701 702 775 778 700 707 }
232
+
233
+ # phone number prefix
234
+ # +7 is Russia/Kazakhstan
235
+ config.cck_forms.phones.prefix = '+7'
236
+
237
+ # the glue for concatenating phone number parts: 111[glue]22[glue]33
238
+ config.cck_forms.phones.number_parts_glue = '-'
239
+ ```
240
+
241
+
242
+ Roadmap, TODOs
243
+ --------------
244
+
245
+ - Add new field type: Tags (a collection of — possibly pre-set — strings)
246
+ - Extract HTML templates into separate gem/module
247
+ - Extract map providers or at lease make them configurable
248
+ - Custom phone format on input/ouput (`#build_form, #to_html`)
249
+
250
+
251
+ License
252
+ -------
253
+
254
+ Released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -1,7 +1,11 @@
1
+ # Utility module grouping all date & time related methods
2
+ #
1
3
  module CckForms::DateTime
2
4
  extend ActiveSupport::Concern
3
5
 
4
6
  module DateTimeParser
7
+ # Create Date/DateTime object from its MongoDB/HTML representation: either a hash with keys :year, :month and so on
8
+ # or a Hash with keys (5i), (1i) etc.
5
9
  def date_object_from_what_stored_in_database(value)
6
10
  parsed_value = nil
7
11
  if value.is_a? Hash
@@ -10,8 +10,8 @@ module CckForms
10
10
  config.cck_forms = ActiveSupport::OrderedOptions.new
11
11
 
12
12
  # general
13
- config.cck_forms.load_type_classes = true
14
- config.cck_forms.extend_form_builder = true
13
+ config.cck_forms.load_type_classes = true # if true, require all default type classes
14
+ config.cck_forms.extend_form_builder = true # if true, extend FormBuilder via form_builder_extensions.rb
15
15
 
16
16
  # phones
17
17
  config.cck_forms.phones = ActiveSupport::OrderedOptions.new
@@ -1,4 +1,4 @@
1
- # Примесь для расширения ActionView::Helpers::FormBuilder, чтобы работать с нашими полями:
1
+ # Extends ActionView::Helpers::FormBuilder to add CCK Forms related methods:
2
2
  #
3
3
  # class CckEnabled
4
4
  # field :logo, type: CckForms::ParameterTypeClass::Image
@@ -9,6 +9,7 @@
9
9
  #
10
10
  module CckForms::FormBuilderExtensions
11
11
  ActionView::Helpers::FormBuilder.class_eval do
12
+ # Returns HTML for a standalone CCK field field_name
12
13
  def standalone_cck_field(field_name, options = {})
13
14
  fields_for(field_name) do |ff|
14
15
  @template.raw object.send(field_name).build_form ff, options
@@ -0,0 +1,71 @@
1
+ # Utility class to handle denormalization of Neofile::File object fields to be stored in a host object.
2
+ #
3
+ # This module is included in Album and Image parameter type classes.
4
+ #
5
+ module CckForms::NeofilesDenormalize
6
+ extend ActiveSupport::Concern
7
+
8
+ # Fields that should not be stored in a host object since they are mutable
9
+ NEOFILES_LAZY_ATTRS = %i{ no_wm description is_deleted }
10
+
11
+ module ClassMethods
12
+ # Returns all fields of Neofiles::File obj to be denormalized
13
+ def neofiles_attrs(obj)
14
+ obj.attributes.except *NEOFILES_LAZY_ATTRS
15
+ end
16
+
17
+ # Returns all fields of Neofiles::File to be denormalized or the object ID if the object itself can not be found
18
+ def neofiles_attrs_or_id(obj_or_id, klass = ::Neofiles::File)
19
+ if obj_or_id.present?
20
+ obj, id = if obj_or_id.is_a? klass
21
+ [obj_or_id, nil]
22
+ elsif obj_or_id.is_a?(::String) || obj_or_id.is_a?(::BSON::ObjectId)
23
+ [::Neofiles::File.where(id: obj_or_id).first, obj_or_id.to_s]
24
+ end
25
+
26
+ obj.try { |x| neofiles_attrs(x) } || id
27
+ end
28
+ end
29
+
30
+ # Constructs a Mongoid::Document of class klass with attrs as if it was a usual document loaded from MongoDB
31
+ def neofiles_mock(attrs, klass)
32
+ Mongoid::Factory.from_db(klass, attrs).tap do |obj|
33
+ neofiles_lazy_loadable obj
34
+ end
35
+ end
36
+
37
+ # If attrs_or_id is a Hash, constructs a mock from it. Otherwise, load an object by its ID
38
+ def neofiles_mock_or_load(attrs_or_id, klass = ::Neofiles::File)
39
+ if attrs_or_id.present?
40
+ case attrs_or_id
41
+ when ::String then klass.where(id: attrs_or_id).first
42
+ when ::BSON::ObjectId then klass.where(id: attrs_or_id).first
43
+ when ::Hash then neofiles_mock(attrs_or_id.with_indifferent_access, klass)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Makes obj lazy load fields NEOFILES_LAZY_ATTRS. That is, when these fields are accessed vie getters or
49
+ # read_attribute, make a request to MongoDB to fetch fresh data (all at once)
50
+ def neofiles_lazy_loadable(obj)
51
+ def obj.__lazy_load
52
+ return if @__lazy_loaded
53
+ @__lazy_loaded = true
54
+ from_db = self.class.find(id)
55
+ attributes.merge! from_db.attributes.slice(*NEOFILES_LAZY_ATTRS)
56
+ end
57
+
58
+ def obj.read_attribute(field)
59
+ __lazy_load if field.in? NEOFILES_LAZY_ATTRS
60
+ super(field)
61
+ end
62
+
63
+ NEOFILES_LAZY_ATTRS.each do |field|
64
+ obj.define_singleton_method field do
65
+ __lazy_load
66
+ super()
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,12 +1,14 @@
1
+ # Represents an ordered collection of photos (Image types).
2
+ #
1
3
  class CckForms::ParameterTypeClass::Album
2
4
  include CckForms::ParameterTypeClass::Base
5
+ include CckForms::NeofilesDenormalize
3
6
 
4
7
  def self.name
5
8
  'Альбом'
6
9
  end
7
10
 
8
- # Преобразует данные для Монго.
9
- # Приводит переданный массив или хэш объектов Neofiles::Image или их идентификаторов в массив.
11
+ # Converts input array of Neofiles::Image or IDs to array of hashes (denormalized image data) or IDs
10
12
  def mongoize
11
13
  the_value = value.is_a?(Hash) ? value['value'] : value
12
14
 
@@ -14,28 +16,27 @@ class CckForms::ParameterTypeClass::Album
14
16
  if the_value.respond_to? :each
15
17
  the_value.each do |image|
16
18
  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?
19
+ result.push self.class.neofiles_attrs_or_id(image, ::Neofiles::Image)
18
20
  end
19
21
  end
20
22
 
21
- result
23
+ result.compact
22
24
  end
23
25
 
24
- # Преобразуем данные из Монго.
25
- # Приводим в массив (по идее, массив идентификаторов Neofiles::Image, хотя может быть что угодно).
26
+ # Converts input array of attr hashes or IDs to array if Neofiles::Image (possibly lazy loadable)
26
27
  def self.demongoize_value(value, parameter_type_class=nil)
27
28
  if value.respond_to? :each
28
- value
29
+ value = value.values if value.respond_to? :values
30
+ value.map { |x| neofiles_mock_or_load(x) }.compact
29
31
  else
30
32
  []
31
33
  end
32
34
  end
33
35
 
34
- # Строит форму для обновления файлов альбома.
36
+ # options:
35
37
  #
36
- # Ключи options:
37
- #
38
- # value - текущее значение (идентификатор или объект Neofiles::Album)
38
+ # value - current value (ID or Neofiles::Album)
39
+ # with_desc - if true, show the description edit richtext (default false)
39
40
  def build_form(form_builder, options)
40
41
  set_value_in_hash options
41
42
 
@@ -49,6 +50,7 @@ class CckForms::ParameterTypeClass::Album
49
50
  file_forms = []
50
51
 
51
52
  the_value.each do |image_id|
53
+ image_id = image_id.is_a?(::Neofiles::File) ? image_id.id : image_id
52
54
  file_forms << CckForms::ParameterTypeClass::Image.create_load_form( helper: self,
53
55
  file: image_id,
54
56
  input_name: input_name_prefix,
@@ -84,10 +86,12 @@ class CckForms::ParameterTypeClass::Album
84
86
  HTML
85
87
  end
86
88
 
89
+ # Returns empty string
87
90
  def to_s(options = nil)
88
91
  ''
89
92
  end
90
93
 
94
+ # Returns a collection of 64x64 IMGs
91
95
  def to_diff_value(options = {})
92
96
  view_context = options[:view_context]
93
97
  images_html_list = []