CCK Forms

CCK Forms is a companion to the yet-to-be-published CCK gem. (CCK stands for Content Construction Kit — a name borrowed from PHP Drupal CMS.)

Whilst the CCK gem provides ability to store category-related custom fields in an ActiveModel, this gem defines possible field types for that matter (string, enum, image album etc.)

As CCK is aimed to simplify storing & editing model fields (especially in admin panels), these custom field types define common (and complex) notions like work hours, sets of images, WYSIWYG-capable fields and so on. "Define" here means these field types can be stored in CCK-capable models and they all have HTML form templates. The latter implies possible standalone usage, for example, if you just need a field of type Image inside your model, you can use CCK Forms without CCK and still have nice looking and convenient editor form template.

The UI is written in Bootstrap 3 and can not be easily changed, sorry folks.

Criticism

Generally speaking, this gem combines two aspects: CCK-related storage things and UI editor forms. The latter is very project & design dependent and should not be fixed in the gem (especially in the way it is now, with HTML constructed in class methods, heavily relying on Bootstrap). Moreover, it may be a good idea to completely decouple UI into separate gem/module using, say, Simpleforms to do the frontend job.

Then, this gem will only define pure classes to be used either with CCK or standalone as proper "types", like String or MapPoint or anything else.

Installation, dependencies

Important: CCK requires a MongoDB database as a mean to store data, so your models must be Mongoid documents.

Add CCK Forms and its dependencies to your gemfile:

# neofiles-related
gem 'neofiles'
gem 'ruby-imagespec', git: 'git://github.com/dim/ruby-imagespec.git'
gem 'mini_magick', '3.7.0'

gem 'cck_forms'

This gem requires Neofiles for File, Image and Album types. Please read its installation instructions, there are some gotchas.

Next, include CSS & JS files where needed (say, application.js or admin.js)...

#= require jquery # neofiles requires jquery
#= require neofiles
#= require cck_forms

... and application.css/admin.css or whatever place you need it in:

 *= require neofiles

Don't forget to set up Neofiles routing!

Usage with CCK

Basic usage only, more to come when CCK gem will finally be published.

# in model:
class Content
  include Mongoid::Document
  include Cck::Cckable

  cck_config do |c|
    c.category 'news' do |cc|
      cc.string 'title', 'News title'
      cc.enum   'region', valid_values: {world: 'World news', local: 'Local news'}
      cc.image  'announce_pic', 'Announce image', hint: '100x100 only'
      cc.text   'body', 'News article body'
      cc.map    'map_point', 'Where did it happen?'

      c.require_params %w{title body}
    end

    c.category 'page' do |cc|
      ...
    end
  end

  field :category_id, type: String
end

# in controller:
def edit
  @content = Content.new category_id: params[:category_id]
end

# in view:
= form_for @content do |ff|
 = cck_fields_for :cck_params

# elsewhere:
content = Content.find(...)
content.cck_params[:title].to_s
content.cck_params[:map_point].value[:latitude]
content.cck_params.to_h.each_pair { |k, v| puts "#{k}: #{v}" } # outputs every CCK field with its ID

Standalone usage

First, create a model and add as many CCK fields as you need:

class User
  include Mongoid::Document

  field :avatar,  type: CckForms::ParameterTypeClass::Image
  field :cv,      type: CckForms::ParameterTypeClass::File
  field :phones,  type: CckForms::ParameterTypeClass::Phones

  validates do |doc|
    if doc.avatar.try(:value) && doc.avatar.value.width > 1000
      doc.errors.add :avatar, 'must be no more than 1000 px wide'
    end

    if doc.phones.try(:value) && doc.phones.value.count > 2
      doc.errors.add :phones, 'must have at most 2 phone numbers'
    end
  end
end

Then in a view form use helper to output UI:

= form_for @user, html: {class: 'form-horizontal'} do |f|
  .form-group
    label.control-label.col-sm-2 Avatar
    .col-sm-9= f.standalone_cck_field :avatar

  .form-group
     label.control-label.col-sm-2 CV
     .col-sm-9= f.standalone_cck_field :cv, with_desc: true

  .form-group
    label.control-label.col-sm-2 Phone numbers
    .col-sm-9= f.standalone_cck_field :phones
end

Use model fields as usual with one exception: to get a field value you need to unwrap its first with call to value (as it is a wrapper class instance). Assign values directly though.

user = User.first
puts "User avatar file: #{neofiles_image_path user.avatar.value} (#{user.avatar.value.length} bytes)"
puts "User phone#{user.phones.try(:value).try(:count).to_i == 0 ? '' : 's'}: #{user.phones.to_s}"

user.avatar = Neofiles::Image.find(...)
user.phones = ['+7 111 222 33 44', '1231231231', {prefix: '+906', code: '1234', number: '223344'}]
user.save! # should raise exception indicating phones validation failure

Common methods:

  • value: returns the real field value. Each field type has its own value, say Phones returns an array of phone numbers and Map returns a hash with keys :latitude, :longitude, :zoom
  • to_s: string representation
  • to_html: HTML representation
  • to_diff_value: representation in form was/became to show history of changes
  • search: returns a Mongoid Criteria filled with search query params specific to this particular field type
  • build_form: builds an HTML editor form

Available field types

Album: sortable collection of images.

Boolean: checkbox.

Checkboxes: several checkboxes. Requires the valid_values option.

Date, DateTime, Time: date/date&time/time select.

DateRange: two sets of selects "date from/till".

Enum: select or set of radio buttons. Requires the valid_values option.

File, Image: single file or image.

Integer, Float: numeric input.

IntegerRange: two inputs for "number from/till".

Map: map point. Two map providers available: Google and Yandex. Google requires an API key.

Phones: array of phone numbers.

String: text input.

StringCollection: set of strings. Represented by a textarea, one line — one string.

Text: textarea.

WorkHours: complex input to construct work schedule on a weekly basis.

WatermarklessAlbum, WatermarklessImage: same as Album & Image, but do not place watermarks. For banners or important sliders, for example.

Configuration

CCK Forms offers the following config options which can be set in config/application.rb or config/environments/*.rb:

# load all available type classes on application start
config.cck_forms.load_type_classes = true

# extend default form builder to add `standalone_cck_field` method
config.cck_forms.extend_form_builder = true

# how many phone numbers will the edit form contain by default for each field
config.cck_forms.phones.min_phones_in_form = 3

# which area codes are considered as mobile carrier codes (mobile and landline numbers have different HTML forms)
# the codes listed below are Kazakhstan mobile operators as of year 2016
config.cck_forms.phones.mobile_codes = %w{ 777 705 771   701 702 775 778   700   707 }

# phone number prefix
# +7 is Russia/Kazakhstan
config.cck_forms.phones.prefix = '+7'

# the glue for concatenating phone number parts: 111[glue]22[glue]33
config.cck_forms.phones.number_parts_glue = '-'

# the default map provider; if not specified, google is the default
config.cck_forms.maps.default_type = 'yandex'.freeze

Roadmap, TODOs

  • Add new field type: Tags (a collection of — possibly pre-set — strings)
  • Extract HTML templates into separate gem/module
  • Extract map providers or at lease make them configurable
  • Custom phone format on input/ouput (#build_form, #to_html)

License

Released under the MIT License.