polishing the UI for the content types, refactoring still in progress

This commit is contained in:
did 2011-12-22 01:31:33 +01:00
parent 00283993c0
commit d1bb45d7c8
15 changed files with 264 additions and 172 deletions

View File

@ -13,8 +13,8 @@ gem 'bson_ext', '~> 1.4.0'
gem 'mongoid', '~> 2.3.3'
gem 'locomotive_mongoid_acts_as_tree', :git => 'git@github.com:locomotivecms/mongoid_acts_as_tree.git'
# gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git'
gem 'custom_fields', :path => '../gems/custom_fields' # DEV
# gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => 'experimental'
# gem 'custom_fields', :path => '../gems/custom_fields' # DEV
gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => 'experimental'
gem 'kaminari'
gem 'haml', '~> 3.1.3'

View File

@ -7,6 +7,16 @@ GIT
fssm (>= 0.2.7)
sass (~> 3.1)
GIT
remote: git://github.com/locomotivecms/custom_fields.git
revision: 8bf202d6e0294ca2050ecd7ec44dcc4d570cb7fd
branch: experimental
specs:
custom_fields (2.0.0.rc1)
activesupport (~> 3.1.3)
carrierwave-mongoid (~> 0.1.3)
mongoid (~> 2.3.4)
GIT
remote: git://github.com/plataformatec/devise.git
revision: ede004169c6af7416f8c4e3fc29a653bee133f60
@ -28,14 +38,6 @@ GIT
specs:
locomotive_mongoid_acts_as_tree (0.1.5.7)
PATH
remote: ../gems/custom_fields
specs:
custom_fields (2.0.0.rc1)
activesupport (~> 3.1.3)
carrierwave-mongoid (~> 0.1.3)
mongoid (~> 2.3.4)
GEM
remote: http://rubygems.org/
specs:

View File

@ -23,6 +23,12 @@ class Locomotive.Views.ContentTypes.FormView extends Locomotive.Views.Shared.For
@slugify_name() # slugify the slug field from name
@enable_liquid_editing() # turn textarea into editable liquid code zone
@enable_public_form_checkbox()
@enable_order_by_toggler()
return @
render_custom_fields: ->
@ -33,8 +39,33 @@ class Locomotive.Views.ContentTypes.FormView extends Locomotive.Views.Shared.For
slugify_name: ->
@$('#content_type_name').slugify(target: @$('#content_type_slug'), sep: '_')
enable_liquid_editing: ->
input = @$('#content_type_raw_item_template')
@editor = CodeMirror.fromTextArea input.get()[0],
mode: 'liquid'
autoMatchParens: false
lineNumbers: false
passDelay: 50
tabMode: 'shift'
theme: 'default medium'
onChange: (editor) => @model.set(raw_item_template: editor.getValue())
enable_public_form_checkbox: ->
@_enable_checkbox 'public_form_enabled',
on_callback: => @$('#content_type_public_form_accounts_input').show()
off_callback: => @$('#content_type_public_form_accounts_input').hide()
enable_order_by_toggler: ->
@$('#content_type_order_by_input').bind 'change', (event) =>
target = @$('#content_type_order_direction_input')
if $(event.target).val() == '_position_in_list'
target.hide()
else
target.show()
show_error: (attribute, message, html) ->
if attribute == 'contents_custom_fields'
return if _.isEmpty(message)
for _message, index in message
@custom_fields_view._entry_views[index].show_error(_message[0])
else

View File

@ -91,13 +91,16 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
anchor.after(html)
_enable_checkbox: (name, options) ->
@$('li#page_' + name + '_input input[type=checkbox]').checkToggle
options ||= {}
model_name = options.model_name || @model.paramRoot
@$("li##{model_name}_#{name}_input input[type=checkbox]").checkToggle
on_callback: =>
_.each options.features, (exp) -> @$('li#page_' + exp + '_input').hide()
_.each options.features, (exp) -> @$("li##{model_name}_#{exp}_input").hide()
options.on_callback() if options.on_callback?
@_hide_last_separator()
off_callback: =>
_.each options.features, (exp) -> @$('li#page_' + exp + '_input').show()
_.each options.features, (exp) -> @$("li##{model_name}_#{exp}_input").show()
options.off_callback() if options.off_callback?
@_hide_last_separator()

View File

@ -307,7 +307,27 @@ form.formtastic {
background: transparent image-url("locomotive/icons/asset_add.png") no-repeat left 1px;
}
}
} // .more
&.small {
> textarea, .CodeMirror, .CodeMirror-scroll {
height: 60px;
width: 706px;
}
.CodeMirror {
float: left;
margin-top: 0px;
margin-bottom: 5px;
}
div.inline-errors, p.inline-hints {
margin-left: 160px;
}
}
} // li.code
&.subdomain {
@ -440,7 +460,7 @@ form.formtastic {
&#custom_fields_input {
padding-top: 10px;
padding-top: 17px;
li.custom-field {
height: auto;
@ -454,6 +474,7 @@ form.formtastic {
position: relative;
top: 3px;
margin-left: 5px;
cursor: move;
}
span.label-input {

View File

@ -5,7 +5,7 @@ module Locomotive
respond_to :json, :only => [:create, :update, :destroy]
helper 'Locomotive::CustomFields'
helper 'Locomotive::Accounts', 'Locomotive::CustomFields'
def new
@content_type = current_site.content_types.new

View File

@ -4,5 +4,9 @@ module Locomotive::AccountsHelper
site.memberships.detect { |m| m.admin? && m.account == current_locomotive_account }
end
def options_for_account
current_site.accounts.collect { |a| ["#{a.name} <#{a.email}>", a.id] }
end
end

View File

@ -1,17 +1,32 @@
module Locomotive::CustomFieldsHelper
def options_for_custom_field_type
# %w(string text category boolean date file has_one has_many).map do |kind|
%w(string text category boolean date file).map do |type|
%w(string text select boolean date file).map do |type|
[t("custom_fields.kind.#{type}"), type]
end
end
def options_for_order_by(content_type, collection_name)
def options_for_highlighted_field(content_type)
content_type.ordered_contents_custom_fields.find_all do |field|
%w(string date select).include?(field.type)
end.map do |field|
[field.label, field._id]
end
end
def options_for_group_by_field(content_type)
content_type.ordered_contents_custom_fields.find_all do |field|
%w(select).include?(field.type)
end.map do |field|
[field.label, field._id]
end
end
def options_for_order_by(content_type)
options = %w{created_at updated_at _position_in_list}.map do |type|
[t("locomotive.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type]
end
options + options_for_highlighted_field(content_type, collection_name)
options + options_for_highlighted_field(content_type)
end
def options_for_order_direction
@ -20,123 +35,109 @@ module Locomotive::CustomFieldsHelper
end
end
def options_for_highlighted_field(content_type, collection_name)
custom_fields_collection_name = "ordered_#{collection_name.singularize}_custom_fields".to_sym
collection = content_type.send(custom_fields_collection_name)
collection.delete_if { |f| f.label == 'field name' || %w(file has_one has_many).include?(f.kind) }
collection.map { |field| [field.label, field._name] }
end
# def options_for_text_formatting
# options = %w(none html).map do |option|
# [t("locomotive.custom_fields.text_formatting.#{option}"), option]
# end
# end
def options_for_group_by_field(content_type, collection_name)
custom_fields_collection_name = "ordered_#{collection_name.singularize}_custom_fields".to_sym
collection = content_type.send(custom_fields_collection_name)
collection.delete_if { |f| not f.category? }
collection.map { |field| [field.label, field._name] }
end
def options_for_text_formatting
options = %w(none html).map do |option|
[t("locomotive.custom_fields.text_formatting.#{option}"), option]
end
end
def options_for_association_target
current_site.reload.content_types.collect { |c| [c.name, c.content_klass.to_s] }
end
def options_for_reverse_lookups(my_content_type)
klass_name = my_content_type.content_klass.to_s
[].tap do |options|
ContentType.where(:'contents_custom_fields.kind' => 'has_one', :'contents_custom_fields.target' => klass_name).each do |content_type|
content_type.contents_custom_fields.find_all { |f| f.has_one? && f.target == klass_name }.each do |field|
options << {
:klass => content_type.content_klass.to_s,
:label => field.label,
:name => field._name
}
end
end
end
end
def filter_options_for_reverse_has_many(contents, reverse_lookup, object)
# Only display items which don't belong to a different object
contents.reject do |c|
owner = c.send(reverse_lookup.to_sym)
!(owner.nil? || owner == object._id)
end
end
def options_for_has_one(field, value)
self.options_for_has_one_or_has_many(field) do |groups|
grouped_options_for_select(groups.collect do |g|
if g[:items].empty?
nil
else
[g[:name], g[:items].collect { |c| [c._label, c._id] }]
end
end.compact, value)
end
end
def options_for_has_many(field, content = nil)
self.options_for_has_one_or_has_many(field, content)
end
def options_for_has_one_or_has_many(field, content = nil, &block)
content_type = field.target.constantize._parent.reload
if content_type.groupable?
grouped_contents = content_type.list_or_group_contents
grouped_contents.each do |g|
g[:items] = filter_options_for_reverse_has_many(g[:items], field.reverse_lookup, content)
end if field.reverse_has_many?
if block_given?
block.call(grouped_contents)
else
grouped_contents.collect do |g|
if g[:items].empty?
nil
else
{ :name => g[:name], :items => g[:items].collect { |c| [c._label, c._id] } }
end
end.compact
end
else
contents = content_type.ordered_contents
if field.reverse_has_many?
contents = filter_options_for_reverse_has_many(contents, field.reverse_lookup, content)
end
contents.collect { |c| [c._label, c._id] }
end
end
def has_many_data_to_js(field, content)
options = {
:taken_ids => content.send(field._alias.to_sym).ids
}
if !content.new_record? && field.reverse_has_many?
url_options = {
:breadcrumb_alias => field.reverse_lookup_alias,
"content[#{field.reverse_lookup_alias}]" => content._id
}
options.merge!(
:new_item => {
:label => t('locomotive.contents.form.has_many.new_item'),
:url => new_content_url(field.target_klass._parent.slug, url_options)
},
:edit_item_url => edit_content_url(field.target_klass._parent.slug, 42, url_options)
)
end
collection_to_js(options_for_has_many(field, content), options)
end
# def options_for_association_target
# current_site.reload.content_types.collect { |c| [c.name, c.content_klass.to_s] }
# end
#
# def options_for_reverse_lookups(my_content_type)
# klass_name = my_content_type.content_klass.to_s
#
# [].tap do |options|
# ContentType.where(:'contents_custom_fields.kind' => 'has_one', :'contents_custom_fields.target' => klass_name).each do |content_type|
# content_type.contents_custom_fields.find_all { |f| f.has_one? && f.target == klass_name }.each do |field|
# options << {
# :klass => content_type.content_klass.to_s,
# :label => field.label,
# :name => field._name
# }
# end
# end
# end
# end
#
# def filter_options_for_reverse_has_many(contents, reverse_lookup, object)
# # Only display items which don't belong to a different object
# contents.reject do |c|
# owner = c.send(reverse_lookup.to_sym)
# !(owner.nil? || owner == object._id)
# end
# end
#
# def options_for_has_one(field, value)
# self.options_for_has_one_or_has_many(field) do |groups|
# grouped_options_for_select(groups.collect do |g|
# if g[:items].empty?
# nil
# else
# [g[:name], g[:items].collect { |c| [c._label, c._id] }]
# end
# end.compact, value)
# end
# end
#
# def options_for_has_many(field, content = nil)
# self.options_for_has_one_or_has_many(field, content)
# end
#
# def options_for_has_one_or_has_many(field, content = nil, &block)
# content_type = field.target.constantize._parent.reload
#
# if content_type.groupable?
# grouped_contents = content_type.list_or_group_contents
#
# grouped_contents.each do |g|
# g[:items] = filter_options_for_reverse_has_many(g[:items], field.reverse_lookup, content)
# end if field.reverse_has_many?
#
# if block_given?
# block.call(grouped_contents)
# else
# grouped_contents.collect do |g|
# if g[:items].empty?
# nil
# else
# { :name => g[:name], :items => g[:items].collect { |c| [c._label, c._id] } }
# end
# end.compact
# end
# else
# contents = content_type.ordered_contents
#
# if field.reverse_has_many?
# contents = filter_options_for_reverse_has_many(contents, field.reverse_lookup, content)
# end
#
# contents.collect { |c| [c._label, c._id] }
# end
# end
#
# def has_many_data_to_js(field, content)
# options = {
# :taken_ids => content.send(field._alias.to_sym).ids
# }
#
# if !content.new_record? && field.reverse_has_many?
# url_options = {
# :breadcrumb_alias => field.reverse_lookup_alias,
# "content[#{field.reverse_lookup_alias}]" => content._id
# }
#
# options.merge!(
# :new_item => {
# :label => t('locomotive.contents.form.has_many.new_item'),
# :url => new_content_url(field.target_klass._parent.slug, url_options)
# },
# :edit_item_url => edit_content_url(field.target_klass._parent.slug, 42, url_options)
# )
# end
#
# collection_to_js(options_for_has_many(field, content), options)
# end
end

View File

@ -0,0 +1,22 @@
module Locomotive
class SmallCodeInput < Formtastic::Inputs::TextInput
def wrapper_html_options
super.tap do |opts|
opts[:class] += ' code small'
end
end
def input_wrapping(&block)
template.content_tag(:li,
[template.capture(&block), error_html, error_anchor, hint_html].join("\n").html_safe,
wrapper_html_options
)
end
def error_anchor
template.content_tag(:span, '', :class => 'error-anchor')
end
end
end

View File

@ -11,12 +11,12 @@ module Locomotive
field :name
field :description
field :slug
field :highlighted_field_id, :type => BSON::ObjectId
field :group_by_field_id, :type => BSON::ObjectId
field :order_by
field :order_direction, :default => 'asc'
field :highlighted_field_name
field :group_by_field_name
field :api_enabled, :type => Boolean, :default => false
field :api_accounts, :type => Array
field :public_form_enabled, :type => Boolean, :default => false
field :public_form_accounts, :type => Array
## associations ##
belongs_to :site, :class_name => 'Locomotive::Site'
@ -48,6 +48,14 @@ module Locomotive
# self.group_by_field && group_by_field.category?
# end
def highlighted_field
self.contents_custom_fields.find(self.highlighted_field_id)
end
def group_by_field
self.contents_custom_fields.find(self.group_by_field_id)
end
def order_manually?
self.order_by == '_position_in_list'
end
@ -133,7 +141,7 @@ module Locomotive
def set_default_values
self.order_by ||= 'created_at'
self.highlighted_field_name ||= self.contents_custom_fields.first.name
self.highlighted_field_id ||= self.contents_custom_fields.first._id
end
def normalize_slug

View File

@ -40,7 +40,9 @@ module Locomotive
end
def item_template_must_be_valid
@item_parsing_errors.try(:each) { |msg| self.errors.add :item_template, msg }
@item_parsing_errors.try(:each) do |msg|
%w(item_template raw_item_template).each { |field| self.errors.add field.to_sym, msg }
end
end
end

View File

@ -10,27 +10,18 @@
= f.input :slug
= f.input :description
= f.inputs :name => :custom_fields, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do
= f.inputs :name => :custom_fields, :class => "inputs foldable" do
= f.input :contents_custom_fields, :as => :'Locomotive::Empty', :label => false, :wrapper_html => { :id => 'custom_fields_input' }
/ = render 'locomotive/custom_fields/index', :form => f, :collection_name => 'contents'
/
/ - unless f.object.new_record?
/ = f.foldable_inputs :name => :presentation, :class => 'switchable' do
/ = f.input :highlighted_field_name, :as => :select, :collection => options_for_highlighted_field(f.object, 'contents'), :include_blank => false
/ = f.input :group_by_field_name, :as => :select, :collection => options_for_group_by_field(f.object, 'contents')
/ = f.custom_input :item_template, :css => 'small-code' do
/ %code{ :class => 'html' }
/ = f.text_area :raw_item_template, :class => 'small'
/
/ = f.foldable_inputs :name => :options, :class => 'switchable' do
/ = f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false
/ = f.input :order_direction, :as => :select, :collection => options_for_order_direction, :include_blank => false, :wrapper_html => { :style => "#{'display: none' if f.object.order_manually?}" }
/ = f.custom_input :api_enabled, :css => 'toggle' do
/ = f.check_box :api_enabled
/ = hidden_field_tag 'content_type[api_accounts][]', ''
/ = f.input :api_accounts, :as => :select, :collection => current_site.accounts.collect { |a| ["#{a.name} <#{a.email}>", a.id] }, :include_blank => false, :multiple => true, :wrapper_html => { :class => 'multiple', :style => (f.object.api_enabled? ? '' : 'display: none') }
/
/
/
/
- if @content_type.persisted?
= f.inputs :name => :presentation, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do
= f.input :highlighted_field_id, :as => :select, :collection => options_for_highlighted_field(@content_type), :include_blank => false
= f.input :group_by_field_id, :as => :select, :collection => options_for_group_by_field(@content_type)
= f.input :raw_item_template, :as => :'Locomotive::SmallCode'
= f.inputs :name => :options, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do
= f.input :order_by, :as => :select, :collection => options_for_order_by(@content_type), :include_blank => false
= f.input :order_direction, :as => :select, :collection => options_for_order_direction, :include_blank => false, :wrapper_html => { :style => "#{'display: none' if @content_type.order_manually?}" }
= f.input :public_form_enabled, :as => :'Locomotive::Toggle'
= f.input :public_form_accounts, :as => :select, :collection => options_for_account, :include_blank => false, :multiple => true, :wrapper_html => { :class => 'multiple', :style => (@content_type.public_form_enabled? ? '' : 'display: none') }

View File

@ -40,7 +40,7 @@ en:
reset: Reset site
default_site_template: "Use the default site template. Click <a href='#'>here</a> to upload a site template as a zip file instead."
content_type:
item_template: Item template
raw_item_template: Item template
api_accounts: Notified Accounts
content_instance:
_slug: Permalink
@ -91,11 +91,11 @@ en:
reset: "If enabled, all the data of your site will be destroyed before importing the new site"
content_type:
name: "We suggest you to type the plural form of the model. Ex: Projects, Recipes, Posts, Articles, ...etc"
slug: "It will be used as the name of the collection in the liquid templates. Ex: {{ contents.my_projects }}"
item_template: "You can customize the text displayed for each item in the list. Simply use Liquid. Ex: {{ content.name }})"
api_enabled: "It is used to let people from outside to create new instances (example: messages in a contact form)"
api_accounts: "A notification email will be sent to each of the accounts listed above when a new instance is created"
slug: "It will be used as the name of the collection in the liquid templates. Ex: <span class='code'>{{ contents.my_projects }}</span>"
raw_item_template: "You can customize the text displayed for each item in the list. Simply use Liquid. Ex: <span class='code'>{{ content.name }})</span>"
public_form_enabled: "It is used to let people from outside to create new instances (example: messages in a contact form)"
public_form_accounts: "A notification email will be sent to each of the accounts listed above when a new instance is created"
"custom_fields/field":
name: "Name of the property for liquid templates. Ex: &#123;&#123; your_object.&lt;name_of_your_field&gt; &#125;&#125;"
name: "Name of the property for liquid templates. Ex: <span class='code'>&#123;&#123; your_object.&lt;name_of_your_field&gt; &#125;&#125;</span>"
hint: "Text displayed in the model form just below the field"

View File

@ -44,9 +44,11 @@ x edit my site
x look n feel
x display errors
x slugify
x refactor highlighted_field (field id instead of name)
- other content type options
- show / hide options of a field based on its type
- refactor highlighted_field (field id instead of name)
- select: add/edit/remove options
- text: formatting
- change in main menu
- manage contents
- list

View File

@ -4,4 +4,9 @@
- theme_assets.images => theme_assets.image_picker
- assets => content_assets
- EditableXXX => Locomotive::EditableXXX (en mongodb)
- EditableXXX => Locomotive::EditableXXX (in mongodb)
- content_types:
- category => select
- highlighted_field_name => highlighted_field_id
- api_enabled => public_form_enabled
- api_accounts => public_form_accounts