From 1a2467acf4f95239fe7df469922bca816d991f9e Mon Sep 17 00:00:00 2001 From: dinedine Date: Wed, 26 May 2010 02:41:10 +0200 Subject: [PATCH] sort contents within a conten type + make content type validation more robust + find a bug when dealing with 2 content types --- app/controllers/admin/contents_controller.rb | 6 ++++- app/helpers/admin/base_helper.rb | 16 ++++++------- app/helpers/admin/custom_fields_helper.rb | 2 +- app/models/content_instance.rb | 5 +++- app/models/content_type.rb | 21 ++++++++++------ app/models/page.rb | 1 + app/views/admin/contents/_form.html.haml | 2 ++ app/views/admin/contents/edit.html.haml | 3 ++- app/views/admin/contents/index.html.haml | 6 ++--- .../admin/shared/menu/_contents.html.haml | 24 ++++++++++++++++--- config/initializers/locomotive.rb | 2 ++ config/locales/admin_ui_en.yml | 5 +++- config/locales/default_en.yml | 1 + doc/TODO | 13 +++++----- public/javascripts/admin/application.js | 12 ++++++++++ public/javascripts/admin/contents.js | 1 + public/stylesheets/admin/application.css | 2 +- spec/models/content_type_spec.rb | 14 +++++++++-- 18 files changed, 99 insertions(+), 37 deletions(-) diff --git a/app/controllers/admin/contents_controller.rb b/app/controllers/admin/contents_controller.rb index c4e805cf..55fb518f 100644 --- a/app/controllers/admin/contents_controller.rb +++ b/app/controllers/admin/contents_controller.rb @@ -6,7 +6,7 @@ module Admin before_filter :set_content_type def index - @contents = @content_type.contents + @contents = @content_type.ordered_contents end def new @@ -42,7 +42,11 @@ module Admin end def sort + @content_type.sort_contents!(params[:order]) + flash_success! + + redirect_to admin_contents_url(@content_type.slug) end def destroy diff --git a/app/helpers/admin/base_helper.rb b/app/helpers/admin/base_helper.rb index e26eecc5..983d6d95 100644 --- a/app/helpers/admin/base_helper.rb +++ b/app/helpers/admin/base_helper.rb @@ -19,15 +19,13 @@ module Admin::BaseHelper css = "#{'on' if name == sections(:sub)} #{'links' if block_given?} #{options[:css]}" label_link = default_options[:i18n] ? t("admin.shared.menu.#{name}") : name - # if block_given? - # popup = content_tag(:div, capture(&block), :class => 'popup', :style => 'display: none') - # link = link_to(content_tag(:span, label_link + content_tag(:em)), url) - # concat(content_tag(:li, link + popup, :class => css)) - # else - # html = content_tag(:li, link_to(content_tag(:span, label_link), url), :class => css) - # end - - content_tag(:li, link_to(content_tag(:span, label_link), url), :class => css) + if block_given? + popup = content_tag(:div, capture(&block), :class => 'popup', :style => 'display: none') + link = link_to(content_tag(:span, preserve(label_link + content_tag(:em))), url) + content_tag(:li, link + popup, :class => css) + else + content_tag(:li, link_to(content_tag(:span, label_link), url), :class => css) + end end end \ No newline at end of file diff --git a/app/helpers/admin/custom_fields_helper.rb b/app/helpers/admin/custom_fields_helper.rb index 240405ee..1c2dc7e1 100644 --- a/app/helpers/admin/custom_fields_helper.rb +++ b/app/helpers/admin/custom_fields_helper.rb @@ -8,7 +8,7 @@ module Admin::CustomFieldsHelper end 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] end options + options_for_highlighted_field(content_type, collection_name) diff --git a/app/models/content_instance.rb b/app/models/content_instance.rb index c6c5472b..049a37d1 100644 --- a/app/models/content_instance.rb +++ b/app/models/content_instance.rb @@ -3,7 +3,7 @@ class ContentInstance include Mongoid::Timestamps ## fields (dynamic fields) ## - field :position, :type => Integer, :default => 0 + field :_position_in_list, :type => Integer, :default => 0 ## validations ## validate :require_highlighted_field @@ -11,6 +11,9 @@ class ContentInstance ## associations ## 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 ## protected diff --git a/app/models/content_type.rb b/app/models/content_type.rb index 946361e5..c6753b9a 100644 --- a/app/models/content_type.rb +++ b/app/models/content_type.rb @@ -16,10 +16,12 @@ class ContentType ## callbacks ## before_validate :normalize_slug + before_save :set_default_values ## validations ## validates_presence_of :site, :name, :slug validates_uniqueness_of :slug, :scope => :site + validates_size_of :content_custom_fields, :minimum => 1, :message => :array_too_short ## behaviours ## custom_fields_for :contents @@ -27,15 +29,15 @@ class ContentType ## methods ## 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 - def contents_order - self.ordered_contents.collect(&:id).join(',') - end - - def contents_order=(order) - @contents_order = order + def sort_contents!(order) + order.split(',').each_with_index do |id, position| + self.contents.find(id)._position_in_list = position + end + self.save end def highlighted_field @@ -44,6 +46,11 @@ class ContentType protected + def set_default_values + self.order_by ||= 'updated_at' + self.highlighted_field_name ||= self.content_custom_fields.first._name + end + def normalize_slug self.slug = self.name.clone if self.slug.blank? && self.name.present? self.slug.slugify! if self.slug.present? diff --git a/app/models/page.rb b/app/models/page.rb index 6ddf5c1e..bd3022fc 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -33,6 +33,7 @@ class Page validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 } ## named scopes ## + named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb ## behaviours ## acts_as_tree :order => ['position', 'asc'] diff --git a/app/views/admin/contents/_form.html.haml b/app/views/admin/contents/_form.html.haml index 1015c6f0..142ff0ea 100644 --- a/app/views/admin/contents/_form.html.haml +++ b/app/views/admin/contents/_form.html.haml @@ -1,5 +1,7 @@ - highlighted_field_name = @content.content_type.highlighted_field_name +- puts "current custom fields = #{@content.custom_fields.inspect}" + = f.inputs :name => :other_fields do - @content.custom_fields.each do |field| - required = highlighted_field_name == field._name diff --git a/app/views/admin/contents/edit.html.haml b/app/views/admin/contents/edit.html.haml index c0526753..52af2b85 100644 --- a/app/views/admin/contents/edit.html.haml +++ b/app/views/admin/contents/edit.html.haml @@ -3,8 +3,9 @@ - content_for :submenu do = 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.new'), new_admin_content_url(@content_type.slug), :class => 'new' %p= @content_type.description diff --git a/app/views/admin/contents/index.html.haml b/app/views/admin/contents/index.html.haml index be2f2c84..f249d23b 100644 --- a/app/views/admin/contents/index.html.haml +++ b/app/views/admin/contents/index.html.haml @@ -8,7 +8,6 @@ - content_for :buttons do = 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' - if @content_type.description.present? @@ -17,11 +16,10 @@ - if @contents.empty? %p.no-items= t('.no_items', :url => new_admin_content_url(@content_type.slug)) - 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| %li.content{ :id => "content-#{content._id}" } - - if @content_type.order_by == 'position' - %em + %em %strong = link_to content.send(@content_type.highlighted_field_name), edit_admin_content_path(@content_type.slug, content) .more diff --git a/app/views/admin/shared/menu/_contents.html.haml b/app/views/admin/shared/menu/_contents.html.haml index 67049d6a..5838660c 100644 --- a/app/views/admin/shared/menu/_contents.html.haml +++ b/app/views/admin/shared/menu/_contents.html.haml @@ -1,9 +1,27 @@ %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| - 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 = link_to content_tag(:span, t('admin.content_types.index.new')), new_admin_content_type_url \ No newline at end of file diff --git a/config/initializers/locomotive.rb b/config/initializers/locomotive.rb index 0f49ed88..527dcac3 100644 --- a/config/initializers/locomotive.rb +++ b/config/initializers/locomotive.rb @@ -3,6 +3,8 @@ require 'lib/core_ext.rb' Locomotive.configure do |config| config.default_domain = 'example.com' + + config.lastest_items_nb = 5 end # TODO: embed them in Locomotive right after configure diff --git a/config/locales/admin_ui_en.yml b/config/locales/admin_ui_en.yml index 7e7f298b..c1215c54 100644 --- a/config/locales/admin_ui_en.yml +++ b/config/locales/admin_ui_en.yml @@ -60,6 +60,7 @@ en: title: Listing pages no_items: "There are no pages for now. Just click here to create the first one." new: new page + lastest_items: Lastest pages page: updated_at: updated at edit: @@ -153,15 +154,17 @@ en: form: order_by: updated_at: 'By "updated at" date' - position: Manually + position_in_list: Manually contents: index: title: 'Listing "{{type}}"' edit: edit model + destroy: remove model download: download items new: new item no_items: "There are no items for now. Just click here to create the first one." + lastest_items: "Lastest items" new: title: '{{type}} — new item' edit: diff --git a/config/locales/default_en.yml b/config/locales/default_en.yml index b5fbd1da..00e59f61 100644 --- a/config/locales/default_en.yml +++ b/config/locales/default_en.yml @@ -7,6 +7,7 @@ en: needs_admin_account: "One admin account is required at least" protected_page: "You can not remove index or 404 pages" extname_changed: "New file does not have the original extension" + array_too_short: "is too small (minimum element number is {{count}})" attributes: defaults: diff --git a/doc/TODO b/doc/TODO index 652fda7e..cf320f5e 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,9 +1,5 @@ BOARD: - - content types / models (CRUD) - - require a custom field at least - - pre-select the first custom field as the highlighted one - - contents (CRUD) - - sort contents + - contents sub menu => BUG BACKLOG: - liquid rendering engine @@ -78,4 +74,9 @@ x custom fields: x rename asset_field x nested attributes x keep tracks of all custom fields (adding / editing assets) + order them - x duplicate fields \ No newline at end of file + 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 \ No newline at end of file diff --git a/public/javascripts/admin/application.js b/public/javascripts/admin/application.js index ce85514a..2bc5faca 100644 --- a/public/javascripts/admin/application.js +++ b/public/javascripts/admin/application.js @@ -44,6 +44,18 @@ var addCodeMirrorEditor = function(type, el, parser) { $(document).ready(function() { 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 $('.formtastic li input, .formtastic li textarea').focus(function() { $('.formtastic li.error p.inline-errors').fadeOut(200); diff --git a/public/javascripts/admin/contents.js b/public/javascripts/admin/contents.js index e3d463e7..2a1ff4fb 100644 --- a/public/javascripts/admin/contents.js +++ b/public/javascripts/admin/contents.js @@ -9,6 +9,7 @@ $(document).ready(function() { } $('ul#contents-list.sortable').sortable({ + handle: 'em', items: 'li.content', stop: function(event, ui) { updateContentsOrder(); } }); diff --git a/public/stylesheets/admin/application.css b/public/stylesheets/admin/application.css index fd96afea..6e79df0f 100644 --- a/public/stylesheets/admin/application.css +++ b/public/stylesheets/admin/application.css @@ -163,7 +163,7 @@ div#uploadAssetsInputQueue { display: none; } #contents-list li { background: none; } -#contents-list li em { +#contents-list.sortable li em { background-position: left -31px; cursor: move; } diff --git a/spec/models/content_type_spec.rb b/spec/models/content_type_spec.rb index 85547e02..ec0f16da 100644 --- a/spec/models/content_type_spec.rb +++ b/spec/models/content_type_spec.rb @@ -9,7 +9,9 @@ describe ContentType do context 'when validating' 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 # Validations ## @@ -29,11 +31,19 @@ describe ContentType do end 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.errors[:slug].should == ["is already taken"] 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 \ No newline at end of file