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 'mongoid', '~> 2.3.3'
gem 'locomotive_mongoid_acts_as_tree', :git => 'git@github.com:locomotivecms/mongoid_acts_as_tree.git' 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', :git => 'git://github.com/locomotivecms/custom_fields.git'
gem 'custom_fields', :path => '../gems/custom_fields' # DEV # gem 'custom_fields', :path => '../gems/custom_fields' # DEV
# gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => 'experimental' gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => 'experimental'
gem 'kaminari' gem 'kaminari'
gem 'haml', '~> 3.1.3' gem 'haml', '~> 3.1.3'

View File

@ -7,6 +7,16 @@ GIT
fssm (>= 0.2.7) fssm (>= 0.2.7)
sass (~> 3.1) 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 GIT
remote: git://github.com/plataformatec/devise.git remote: git://github.com/plataformatec/devise.git
revision: ede004169c6af7416f8c4e3fc29a653bee133f60 revision: ede004169c6af7416f8c4e3fc29a653bee133f60
@ -28,14 +38,6 @@ GIT
specs: specs:
locomotive_mongoid_acts_as_tree (0.1.5.7) 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 GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: 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 @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 @ return @
render_custom_fields: -> render_custom_fields: ->
@ -33,8 +39,33 @@ class Locomotive.Views.ContentTypes.FormView extends Locomotive.Views.Shared.For
slugify_name: -> slugify_name: ->
@$('#content_type_name').slugify(target: @$('#content_type_slug'), sep: '_') @$('#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) -> show_error: (attribute, message, html) ->
if attribute == 'contents_custom_fields' if attribute == 'contents_custom_fields'
return if _.isEmpty(message)
for _message, index in message for _message, index in message
@custom_fields_view._entry_views[index].show_error(_message[0]) @custom_fields_view._entry_views[index].show_error(_message[0])
else else

View File

@ -91,13 +91,16 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
anchor.after(html) anchor.after(html)
_enable_checkbox: (name, options) -> _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: => 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? options.on_callback() if options.on_callback?
@_hide_last_separator() @_hide_last_separator()
off_callback: => 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? options.off_callback() if options.off_callback?
@_hide_last_separator() @_hide_last_separator()

View File

@ -307,7 +307,27 @@ form.formtastic {
background: transparent image-url("locomotive/icons/asset_add.png") no-repeat left 1px; 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 } // li.code
&.subdomain { &.subdomain {
@ -440,7 +460,7 @@ form.formtastic {
&#custom_fields_input { &#custom_fields_input {
padding-top: 10px; padding-top: 17px;
li.custom-field { li.custom-field {
height: auto; height: auto;
@ -454,6 +474,7 @@ form.formtastic {
position: relative; position: relative;
top: 3px; top: 3px;
margin-left: 5px; margin-left: 5px;
cursor: move;
} }
span.label-input { span.label-input {

View File

@ -5,7 +5,7 @@ module Locomotive
respond_to :json, :only => [:create, :update, :destroy] respond_to :json, :only => [:create, :update, :destroy]
helper 'Locomotive::CustomFields' helper 'Locomotive::Accounts', 'Locomotive::CustomFields'
def new def new
@content_type = current_site.content_types.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 } site.memberships.detect { |m| m.admin? && m.account == current_locomotive_account }
end end
def options_for_account
current_site.accounts.collect { |a| ["#{a.name} <#{a.email}>", a.id] }
end
end end

View File

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

View File

@ -40,7 +40,9 @@ module Locomotive
end end
def item_template_must_be_valid 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
end end

View File

@ -10,27 +10,18 @@
= f.input :slug = f.input :slug
= f.input :description = 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' } = 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' - if @content_type.persisted?
/
/ - unless f.object.new_record? = f.inputs :name => :presentation, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do
/ = f.foldable_inputs :name => :presentation, :class => 'switchable' do = f.input :highlighted_field_id, :as => :select, :collection => options_for_highlighted_field(@content_type), :include_blank => false
/ = f.input :highlighted_field_name, :as => :select, :collection => options_for_highlighted_field(f.object, 'contents'), :include_blank => false = f.input :group_by_field_id, :as => :select, :collection => options_for_group_by_field(@content_type)
/ = f.input :group_by_field_name, :as => :select, :collection => options_for_group_by_field(f.object, 'contents') = f.input :raw_item_template, :as => :'Locomotive::SmallCode'
/ = f.custom_input :item_template, :css => 'small-code' do
/ %code{ :class => 'html' } = f.inputs :name => :options, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do
/ = f.text_area :raw_item_template, :class => 'small' = 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.foldable_inputs :name => :options, :class => 'switchable' do = f.input :public_form_enabled, :as => :'Locomotive::Toggle'
/ = f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false = 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') }
/ = 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') }
/
/
/
/

View File

@ -40,7 +40,7 @@ en:
reset: Reset site 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." 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: content_type:
item_template: Item template raw_item_template: Item template
api_accounts: Notified Accounts api_accounts: Notified Accounts
content_instance: content_instance:
_slug: Permalink _slug: Permalink
@ -91,11 +91,11 @@ en:
reset: "If enabled, all the data of your site will be destroyed before importing the new site" reset: "If enabled, all the data of your site will be destroyed before importing the new site"
content_type: content_type:
name: "We suggest you to type the plural form of the model. Ex: Projects, Recipes, Posts, Articles, ...etc" 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 }}" slug: "It will be used as the name of the collection in the liquid templates. Ex: <span class='code'>{{ contents.my_projects }}</span>"
item_template: "You can customize the text displayed for each item in the list. Simply use Liquid. Ex: {{ content.name }})" 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>"
api_enabled: "It is used to let people from outside to create new instances (example: messages in a contact form)" public_form_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" 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": "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" 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 look n feel
x display errors x display errors
x slugify x slugify
x refactor highlighted_field (field id instead of name)
- other content type options - other content type options
- show / hide options of a field based on its type - 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 - change in main menu
- manage contents - manage contents
- list - list

View File

@ -4,4 +4,9 @@
- theme_assets.images => theme_assets.image_picker - theme_assets.images => theme_assets.image_picker
- assets => content_assets - 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