sort contents within a conten type + make content type validation more robust + find a bug when dealing with 2 content types
This commit is contained in:
parent
4534a11ce4
commit
1a2467acf4
@ -6,7 +6,7 @@ module Admin
|
|||||||
before_filter :set_content_type
|
before_filter :set_content_type
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@contents = @content_type.contents
|
@contents = @content_type.ordered_contents
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@ -42,7 +42,11 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def sort
|
def sort
|
||||||
|
@content_type.sort_contents!(params[:order])
|
||||||
|
|
||||||
|
flash_success!
|
||||||
|
|
||||||
|
redirect_to admin_contents_url(@content_type.slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -19,15 +19,13 @@ module Admin::BaseHelper
|
|||||||
css = "#{'on' if name == sections(:sub)} #{'links' if block_given?} #{options[:css]}"
|
css = "#{'on' if name == sections(:sub)} #{'links' if block_given?} #{options[:css]}"
|
||||||
|
|
||||||
label_link = default_options[:i18n] ? t("admin.shared.menu.#{name}") : name
|
label_link = default_options[:i18n] ? t("admin.shared.menu.#{name}") : name
|
||||||
# if block_given?
|
if block_given?
|
||||||
# popup = content_tag(:div, capture(&block), :class => 'popup', :style => 'display: none')
|
popup = content_tag(:div, capture(&block), :class => 'popup', :style => 'display: none')
|
||||||
# link = link_to(content_tag(:span, label_link + content_tag(:em)), url)
|
link = link_to(content_tag(:span, preserve(label_link + content_tag(:em))), url)
|
||||||
# concat(content_tag(:li, link + popup, :class => css))
|
content_tag(:li, link + popup, :class => css)
|
||||||
# else
|
else
|
||||||
# html = content_tag(:li, link_to(content_tag(:span, label_link), url), :class => css)
|
content_tag(:li, link_to(content_tag(:span, label_link), url), :class => css)
|
||||||
# end
|
end
|
||||||
|
|
||||||
content_tag(:li, link_to(content_tag(:span, label_link), url), :class => css)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -8,7 +8,7 @@ module Admin::CustomFieldsHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def options_for_order_by(content_type, collection_name)
|
def options_for_order_by(content_type, collection_name)
|
||||||
options = %w{updated_at position}.map do |type|
|
options = %w{updated_at _position_in_list}.map do |type|
|
||||||
[t("admin.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type]
|
[t("admin.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, collection_name)
|
||||||
|
@ -3,7 +3,7 @@ class ContentInstance
|
|||||||
include Mongoid::Timestamps
|
include Mongoid::Timestamps
|
||||||
|
|
||||||
## fields (dynamic fields) ##
|
## fields (dynamic fields) ##
|
||||||
field :position, :type => Integer, :default => 0
|
field :_position_in_list, :type => Integer, :default => 0
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validate :require_highlighted_field
|
validate :require_highlighted_field
|
||||||
@ -11,6 +11,9 @@ class ContentInstance
|
|||||||
## associations ##
|
## associations ##
|
||||||
embedded_in :content_type, :inverse_of => :contents
|
embedded_in :content_type, :inverse_of => :contents
|
||||||
|
|
||||||
|
## named scopes ##
|
||||||
|
named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
@ -16,10 +16,12 @@ class ContentType
|
|||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
before_validate :normalize_slug
|
before_validate :normalize_slug
|
||||||
|
before_save :set_default_values
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validates_presence_of :site, :name, :slug
|
validates_presence_of :site, :name, :slug
|
||||||
validates_uniqueness_of :slug, :scope => :site
|
validates_uniqueness_of :slug, :scope => :site
|
||||||
|
validates_size_of :content_custom_fields, :minimum => 1, :message => :array_too_short
|
||||||
|
|
||||||
## behaviours ##
|
## behaviours ##
|
||||||
custom_fields_for :contents
|
custom_fields_for :contents
|
||||||
@ -27,15 +29,15 @@ class ContentType
|
|||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
def ordered_contents
|
def ordered_contents
|
||||||
self.contents.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
|
column = self.order_by.to_sym
|
||||||
|
self.contents.sort { |a, b| (a.send(column) || 0) <=> (b.send(column) || 0) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def contents_order
|
def sort_contents!(order)
|
||||||
self.ordered_contents.collect(&:id).join(',')
|
order.split(',').each_with_index do |id, position|
|
||||||
end
|
self.contents.find(id)._position_in_list = position
|
||||||
|
end
|
||||||
def contents_order=(order)
|
self.save
|
||||||
@contents_order = order
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def highlighted_field
|
def highlighted_field
|
||||||
@ -44,6 +46,11 @@ class ContentType
|
|||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def set_default_values
|
||||||
|
self.order_by ||= 'updated_at'
|
||||||
|
self.highlighted_field_name ||= self.content_custom_fields.first._name
|
||||||
|
end
|
||||||
|
|
||||||
def normalize_slug
|
def normalize_slug
|
||||||
self.slug = self.name.clone if self.slug.blank? && self.name.present?
|
self.slug = self.name.clone if self.slug.blank? && self.name.present?
|
||||||
self.slug.slugify! if self.slug.present?
|
self.slug.slugify! if self.slug.present?
|
||||||
|
@ -33,6 +33,7 @@ class Page
|
|||||||
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
|
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
|
||||||
|
|
||||||
## named scopes ##
|
## named scopes ##
|
||||||
|
named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
|
||||||
|
|
||||||
## behaviours ##
|
## behaviours ##
|
||||||
acts_as_tree :order => ['position', 'asc']
|
acts_as_tree :order => ['position', 'asc']
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
- highlighted_field_name = @content.content_type.highlighted_field_name
|
- highlighted_field_name = @content.content_type.highlighted_field_name
|
||||||
|
|
||||||
|
- puts "current custom fields = #{@content.custom_fields.inspect}"
|
||||||
|
|
||||||
= f.inputs :name => :other_fields do
|
= f.inputs :name => :other_fields do
|
||||||
- @content.custom_fields.each do |field|
|
- @content.custom_fields.each do |field|
|
||||||
- required = highlighted_field_name == field._name
|
- required = highlighted_field_name == field._name
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
- content_for :submenu do
|
- content_for :submenu do
|
||||||
= render 'admin/shared/menu/contents'
|
= render 'admin/shared/menu/contents'
|
||||||
|
|
||||||
- content_for :buttons do
|
- content_for :buttons do
|
||||||
= admin_button_tag t('admin.contents.index.edit'), edit_admin_content_type_url(@content_type), :class => 'edit'
|
= admin_button_tag t('admin.contents.index.edit'), edit_admin_content_type_url(@content_type), :class => 'edit'
|
||||||
|
= admin_button_tag t('admin.contents.index.new'), new_admin_content_url(@content_type.slug), :class => 'new'
|
||||||
|
|
||||||
%p= @content_type.description
|
%p= @content_type.description
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
- content_for :buttons do
|
- content_for :buttons do
|
||||||
= admin_button_tag :edit, edit_admin_content_type_url(@content_type), :class => 'edit'
|
= admin_button_tag :edit, edit_admin_content_type_url(@content_type), :class => 'edit'
|
||||||
= admin_button_tag :download, '#', :class => 'download'
|
|
||||||
= admin_button_tag :new, new_admin_content_url(@content_type.slug), :class => 'new'
|
= admin_button_tag :new, new_admin_content_url(@content_type.slug), :class => 'new'
|
||||||
|
|
||||||
- if @content_type.description.present?
|
- if @content_type.description.present?
|
||||||
@ -17,11 +16,10 @@
|
|||||||
- if @contents.empty?
|
- if @contents.empty?
|
||||||
%p.no-items= t('.no_items', :url => new_admin_content_url(@content_type.slug))
|
%p.no-items= t('.no_items', :url => new_admin_content_url(@content_type.slug))
|
||||||
- else
|
- else
|
||||||
%ul{ :id => 'contents-list', :class => "list #{'sortable' if @content_type.order_by == 'position'}" }
|
%ul{ :id => 'contents-list', :class => "list #{'sortable' if @content_type.order_by == '_position_in_list'}" }
|
||||||
- @contents.each do |content|
|
- @contents.each do |content|
|
||||||
%li.content{ :id => "content-#{content._id}" }
|
%li.content{ :id => "content-#{content._id}" }
|
||||||
- if @content_type.order_by == 'position'
|
%em
|
||||||
%em
|
|
||||||
%strong
|
%strong
|
||||||
= link_to content.send(@content_type.highlighted_field_name), edit_admin_content_path(@content_type.slug, content)
|
= link_to content.send(@content_type.highlighted_field_name), edit_admin_content_path(@content_type.slug, content)
|
||||||
.more
|
.more
|
||||||
|
@ -1,9 +1,27 @@
|
|||||||
%ul
|
%ul
|
||||||
= admin_submenu_item 'pages', admin_pages_url
|
= admin_submenu_item 'pages', admin_pages_url do
|
||||||
|
.header
|
||||||
|
%p= link_to t('admin.pages.index.new'), new_admin_page_url
|
||||||
|
.inner
|
||||||
|
%h2= t('admin.pages.index.lastest_items')
|
||||||
|
%ul
|
||||||
|
- current_site.pages.latest_updated.each do |page|
|
||||||
|
%li
|
||||||
|
= link_to truncate(page.title, :length => 40), edit_admin_page_url(page)
|
||||||
|
%span= time_ago_in_words(page.updated_at)
|
||||||
|
|
||||||
- current_site.content_types.each do |content_type|
|
- current_site.content_types.each do |content_type|
|
||||||
- item_on = (content_type.slug == @content_type.slug) rescue nil
|
- item_on = (content_type.slug == @content_type.slug) rescue nil
|
||||||
= admin_submenu_item content_type.name, admin_contents_url(content_type.slug), :i18n => false, :css => (item_on ? 'on' : '')
|
= admin_submenu_item content_type.name, admin_contents_url(content_type.slug), :i18n => false, :css => (item_on ? 'on' : '') do
|
||||||
|
.header
|
||||||
|
%p= link_to t('admin.contents.index.new'), new_admin_content_url(content_type.slug)
|
||||||
|
.inner
|
||||||
|
%h2= t('admin.contents.index.lastest_items')
|
||||||
|
%ul
|
||||||
|
- content_type.contents.latest_updated.each do |content|
|
||||||
|
%li
|
||||||
|
= link_to truncate(content.send(content_type.highlighted_field_name), :length => 40), edit_admin_content_path(content_type.slug, content)
|
||||||
|
%span= time_ago_in_words(content.updated_at)
|
||||||
|
|
||||||
.action
|
.action
|
||||||
= link_to content_tag(:span, t('admin.content_types.index.new')), new_admin_content_type_url
|
= link_to content_tag(:span, t('admin.content_types.index.new')), new_admin_content_type_url
|
@ -3,6 +3,8 @@ require 'lib/core_ext.rb'
|
|||||||
|
|
||||||
Locomotive.configure do |config|
|
Locomotive.configure do |config|
|
||||||
config.default_domain = 'example.com'
|
config.default_domain = 'example.com'
|
||||||
|
|
||||||
|
config.lastest_items_nb = 5
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: embed them in Locomotive right after configure
|
# TODO: embed them in Locomotive right after configure
|
||||||
|
@ -60,6 +60,7 @@ en:
|
|||||||
title: Listing pages
|
title: Listing pages
|
||||||
no_items: "There are no pages for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
no_items: "There are no pages for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
||||||
new: new page
|
new: new page
|
||||||
|
lastest_items: Lastest pages
|
||||||
page:
|
page:
|
||||||
updated_at: updated at
|
updated_at: updated at
|
||||||
edit:
|
edit:
|
||||||
@ -153,15 +154,17 @@ en:
|
|||||||
form:
|
form:
|
||||||
order_by:
|
order_by:
|
||||||
updated_at: 'By "updated at" date'
|
updated_at: 'By "updated at" date'
|
||||||
position: Manually
|
position_in_list: Manually
|
||||||
|
|
||||||
contents:
|
contents:
|
||||||
index:
|
index:
|
||||||
title: 'Listing "{{type}}"'
|
title: 'Listing "{{type}}"'
|
||||||
edit: edit model
|
edit: edit model
|
||||||
|
destroy: remove model
|
||||||
download: download items
|
download: download items
|
||||||
new: new item
|
new: new item
|
||||||
no_items: "There are no items for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
no_items: "There are no items for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
||||||
|
lastest_items: "Lastest items"
|
||||||
new:
|
new:
|
||||||
title: '{{type}} — new item'
|
title: '{{type}} — new item'
|
||||||
edit:
|
edit:
|
||||||
|
@ -7,6 +7,7 @@ en:
|
|||||||
needs_admin_account: "One admin account is required at least"
|
needs_admin_account: "One admin account is required at least"
|
||||||
protected_page: "You can not remove index or 404 pages"
|
protected_page: "You can not remove index or 404 pages"
|
||||||
extname_changed: "New file does not have the original extension"
|
extname_changed: "New file does not have the original extension"
|
||||||
|
array_too_short: "is too small (minimum element number is {{count}})"
|
||||||
|
|
||||||
attributes:
|
attributes:
|
||||||
defaults:
|
defaults:
|
||||||
|
13
doc/TODO
13
doc/TODO
@ -1,9 +1,5 @@
|
|||||||
BOARD:
|
BOARD:
|
||||||
- content types / models (CRUD)
|
- contents sub menu => BUG
|
||||||
- require a custom field at least
|
|
||||||
- pre-select the first custom field as the highlighted one
|
|
||||||
- contents (CRUD)
|
|
||||||
- sort contents
|
|
||||||
|
|
||||||
BACKLOG:
|
BACKLOG:
|
||||||
- liquid rendering engine
|
- liquid rendering engine
|
||||||
@ -78,4 +74,9 @@ x custom fields:
|
|||||||
x rename asset_field
|
x rename asset_field
|
||||||
x nested attributes
|
x nested attributes
|
||||||
x keep tracks of all custom fields (adding / editing assets) + order them
|
x keep tracks of all custom fields (adding / editing assets) + order them
|
||||||
x duplicate fields
|
x duplicate fields
|
||||||
|
x content types / models (CRUD)
|
||||||
|
x require a custom field at least
|
||||||
|
x pre-select the first custom field as the highlighted one
|
||||||
|
x contents (CRUD)
|
||||||
|
x sort contents
|
@ -44,6 +44,18 @@ var addCodeMirrorEditor = function(type, el, parser) {
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
I18nLocale = $('meta[name=locale]').attr('content');
|
I18nLocale = $('meta[name=locale]').attr('content');
|
||||||
|
|
||||||
|
// sub menu links
|
||||||
|
$('#submenu ul li.links').hover(function() {
|
||||||
|
$(this).addClass('hover');
|
||||||
|
$(this).find('.popup').show();
|
||||||
|
}, function() {
|
||||||
|
$(this).removeClass('hover');
|
||||||
|
$(this).find('.popup').hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((css = $('#submenu > ul').attr('class')) != '')
|
||||||
|
$('#submenu > ul > li.' + css).addClass('on');
|
||||||
|
|
||||||
// form
|
// form
|
||||||
$('.formtastic li input, .formtastic li textarea').focus(function() {
|
$('.formtastic li input, .formtastic li textarea').focus(function() {
|
||||||
$('.formtastic li.error p.inline-errors').fadeOut(200);
|
$('.formtastic li.error p.inline-errors').fadeOut(200);
|
||||||
|
@ -9,6 +9,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('ul#contents-list.sortable').sortable({
|
$('ul#contents-list.sortable').sortable({
|
||||||
|
handle: 'em',
|
||||||
items: 'li.content',
|
items: 'li.content',
|
||||||
stop: function(event, ui) { updateContentsOrder(); }
|
stop: function(event, ui) { updateContentsOrder(); }
|
||||||
});
|
});
|
||||||
|
@ -163,7 +163,7 @@ div#uploadAssetsInputQueue { display: none; }
|
|||||||
|
|
||||||
#contents-list li { background: none; }
|
#contents-list li { background: none; }
|
||||||
|
|
||||||
#contents-list li em {
|
#contents-list.sortable li em {
|
||||||
background-position: left -31px;
|
background-position: left -31px;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@ describe ContentType do
|
|||||||
context 'when validating' do
|
context 'when validating' do
|
||||||
|
|
||||||
it 'should have a valid factory' do
|
it 'should have a valid factory' do
|
||||||
Factory.build(:content_type).should be_valid
|
content_type = Factory.build(:content_type)
|
||||||
|
content_type.content_custom_fields.build :label => 'anything', :kind => 'String'
|
||||||
|
content_type.should be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
# Validations ##
|
# Validations ##
|
||||||
@ -29,11 +31,19 @@ describe ContentType do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'should validate uniqueness of slug' do
|
it 'should validate uniqueness of slug' do
|
||||||
content_type = Factory(:content_type)
|
content_type = Factory.build(:content_type)
|
||||||
|
content_type.content_custom_fields.build :label => 'anything', :kind => 'String'
|
||||||
|
content_type.save
|
||||||
(content_type = Factory.build(:content_type, :site => content_type.site)).should_not be_valid
|
(content_type = Factory.build(:content_type, :site => content_type.site)).should_not be_valid
|
||||||
content_type.errors[:slug].should == ["is already taken"]
|
content_type.errors[:slug].should == ["is already taken"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should validate size of content custom fields' do
|
||||||
|
content_type = Factory.build(:content_type)
|
||||||
|
content_type.should_not be_valid
|
||||||
|
content_type.errors[:content_custom_fields].should == ["is too small (minimum element number is 1)"]
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user