adding contents works + add spec + enhance content types
This commit is contained in:
parent
61958d9452
commit
4534a11ce4
@ -18,7 +18,7 @@ module Admin
|
||||
end
|
||||
|
||||
def create
|
||||
@content = @content_type.contents.build(params[:content])
|
||||
@content = @content_type.contents.build(params[:content_instance])
|
||||
|
||||
if @content.save
|
||||
flash_success!
|
||||
@ -32,7 +32,7 @@ module Admin
|
||||
def update
|
||||
@content = @content_type.contents.find(params[:id])
|
||||
|
||||
if @content.update_attributes(params[:content])
|
||||
if @content.update_attributes(params[:content_instance])
|
||||
flash_success!
|
||||
redirect_to edit_admin_content_url(@content_type.slug, @content)
|
||||
else
|
||||
@ -41,6 +41,10 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def sort
|
||||
|
||||
end
|
||||
|
||||
def destroy
|
||||
@content = @content_type.contents.find(params[:id])
|
||||
|
||||
|
@ -1,13 +1,5 @@
|
||||
module Admin::CustomFieldsHelper
|
||||
|
||||
# def options_for_field_kind(selected = nil)
|
||||
# # %w{String Text Boolean Email File Date}
|
||||
# options = %w{String Text}.map do |kind|
|
||||
# [t("admin.custom_fields.kind.#{kind.downcase}"), kind]
|
||||
# end
|
||||
# options_for_select(options, selected)
|
||||
# end
|
||||
|
||||
def options_for_field_kind(selected = nil)
|
||||
# %w{String Text Boolean Email File Date}
|
||||
options = %w{String Text}.map do |kind|
|
||||
@ -15,4 +7,18 @@ module Admin::CustomFieldsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def options_for_order_by(content_type, collection_name)
|
||||
options = %w{updated_at position}.map do |type|
|
||||
[t("admin.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type]
|
||||
end
|
||||
options + options_for_highlighted_field(content_type, collection_name)
|
||||
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' }
|
||||
collection.map { |field| [field.label, field._name] }
|
||||
end
|
||||
|
||||
end
|
@ -2,7 +2,24 @@ class ContentInstance
|
||||
include Mongoid::Document
|
||||
include Mongoid::Timestamps
|
||||
|
||||
# fields ##
|
||||
field :name
|
||||
## fields (dynamic fields) ##
|
||||
field :position, :type => Integer, :default => 0
|
||||
|
||||
## validations ##
|
||||
validate :require_highlighted_field
|
||||
|
||||
## associations ##
|
||||
embedded_in :content_type, :inverse_of => :contents
|
||||
|
||||
## methods ##
|
||||
|
||||
protected
|
||||
|
||||
def require_highlighted_field
|
||||
_alias = self.content_type.highlighted_field._alias.to_sym
|
||||
if self.send(_alias).blank?
|
||||
self.errors.add(_alias, :blank)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,13 +1,14 @@
|
||||
class ContentType
|
||||
include Mongoid::Document
|
||||
include Mongoid::Timestamps
|
||||
# include Mongoid::CustomFields
|
||||
include Mongoid::CustomFields
|
||||
|
||||
## fields ##
|
||||
field :name
|
||||
field :description
|
||||
field :slug
|
||||
field :order_by
|
||||
field :highlighted_field_name
|
||||
|
||||
## associations ##
|
||||
belongs_to_related :site
|
||||
@ -21,10 +22,26 @@ class ContentType
|
||||
validates_uniqueness_of :slug, :scope => :site
|
||||
|
||||
## behaviours ##
|
||||
# custom_fields_for :contents
|
||||
custom_fields_for :contents
|
||||
|
||||
## methods ##
|
||||
|
||||
def ordered_contents
|
||||
self.contents.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
|
||||
end
|
||||
|
||||
def contents_order
|
||||
self.ordered_contents.collect(&:id).join(',')
|
||||
end
|
||||
|
||||
def contents_order=(order)
|
||||
@contents_order = order
|
||||
end
|
||||
|
||||
def highlighted_field
|
||||
self.content_custom_fields.detect { |f| f._name == self.highlighted_field_name }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def normalize_slug
|
||||
|
@ -1,7 +1,7 @@
|
||||
- title link_to(@collection.name.blank? ? @collection.name_was : @collection.name, '#', :rel => 'asset_collection_name', :title => t('.ask_for_name'), :class => 'editable')
|
||||
|
||||
- content_for :head do
|
||||
= javascript_include_tag 'admin/asset_collections.js', 'admin/custom_fields'
|
||||
= javascript_include_tag 'admin/asset_collections', 'admin/custom_fields'
|
||||
|
||||
- content_for :submenu do
|
||||
= render 'admin/shared/menu/assets'
|
||||
@ -25,6 +25,6 @@
|
||||
= f.input :name
|
||||
= f.input :slug, :required => false
|
||||
|
||||
= render 'custom_fields', :f => f
|
||||
= render 'admin/shared/custom_fields', :f => f, :collection_name => 'assets'
|
||||
|
||||
= render 'admin/shared/form_actions', :delete_button => link_to(content_tag(:span, t('.destroy')), admin_asset_collection_url(@collection), :confirm => t('admin.messages.confirm'), :method => :delete, :class => 'button small remove'), :button_label => :update
|
@ -1,4 +1,13 @@
|
||||
- content_for :head do
|
||||
= javascript_include_tag 'admin/custom_fields'
|
||||
|
||||
= f.inputs :name => :information do
|
||||
= f.input :name
|
||||
= f.input :slug, :required => false
|
||||
= f.input :description, :as => 'text'
|
||||
= f.input :slug
|
||||
= f.input :description
|
||||
|
||||
= render 'admin/shared/custom_fields', :f => f, :collection_name => 'contents'
|
||||
|
||||
= f.foldable_inputs :name => :options, :class => 'switchable' do
|
||||
= f.input :highlighted_field_name, :as => :select, :collection => options_for_highlighted_field(f.object, 'contents'), :include_blank => false
|
||||
= f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false
|
10
app/views/admin/contents/_form.html.haml
Normal file
10
app/views/admin/contents/_form.html.haml
Normal file
@ -0,0 +1,10 @@
|
||||
- highlighted_field_name = @content.content_type.highlighted_field_name
|
||||
|
||||
= f.inputs :name => :other_fields do
|
||||
- @content.custom_fields.each do |field|
|
||||
- required = highlighted_field_name == field._name
|
||||
|
||||
- if field.string?
|
||||
= f.input field._alias.to_sym, :label => field.label, :required => required
|
||||
- elsif field.text?
|
||||
= f.input field._alias.to_sym, :label => field.label, :as => :text, :required => required
|
15
app/views/admin/contents/edit.html.haml
Normal file
15
app/views/admin/contents/edit.html.haml
Normal file
@ -0,0 +1,15 @@
|
||||
- title t('.title', :type => @content_type.name.capitalize)
|
||||
|
||||
- content_for :submenu do
|
||||
= render 'admin/shared/menu/contents'
|
||||
|
||||
- content_for :buttons do
|
||||
= admin_button_tag t('admin.contents.index.edit'), edit_admin_content_type_url(@content_type), :class => 'edit'
|
||||
|
||||
%p= @content_type.description
|
||||
|
||||
= semantic_form_for @content, :url => admin_content_url(@content_type.slug, @content), :html => { :multipart => true } do |form|
|
||||
|
||||
= render 'form', :f => form
|
||||
|
||||
= render 'admin/shared/form_actions', :back_url => admin_contents_url(@content_type.slug), :button_label => :update
|
@ -1,8 +1,11 @@
|
||||
- title t('.title', :type => @content_type.name)
|
||||
- title t('.title', :type => @content_type.name.capitalize)
|
||||
|
||||
- content_for :submenu do
|
||||
= render 'admin/shared/menu/contents'
|
||||
|
||||
- content_for :head do
|
||||
= javascript_include_tag 'admin/contents'
|
||||
|
||||
- content_for :buttons do
|
||||
= admin_button_tag :edit, edit_admin_content_type_url(@content_type), :class => 'edit'
|
||||
= admin_button_tag :download, '#', :class => 'download'
|
||||
@ -14,4 +17,21 @@
|
||||
- if @contents.empty?
|
||||
%p.no-items= t('.no_items', :url => new_admin_content_url(@content_type.slug))
|
||||
- else
|
||||
foo bar
|
||||
%ul{ :id => 'contents-list', :class => "list #{'sortable' if @content_type.order_by == 'position'}" }
|
||||
- @contents.each do |content|
|
||||
%li.content{ :id => "content-#{content._id}" }
|
||||
- if @content_type.order_by == 'position'
|
||||
%em
|
||||
%strong
|
||||
= link_to content.send(@content_type.highlighted_field_name), edit_admin_content_path(@content_type.slug, content)
|
||||
.more
|
||||
%span
|
||||
= t('admin.contents.index.updated_at')
|
||||
= l content.updated_at, :format => :short rescue 'n/a'
|
||||
|
||||
= link_to image_tag('admin/list/icons/trash.png'), admin_content_path(@content_type.slug, content), :class => 'remove', :confirm => t('admin.messages.confirm'), :method => :delete
|
||||
|
||||
= form_tag sort_admin_contents_path(@content_type.slug), :method => :put, :class => 'formtastic' do
|
||||
= hidden_field_tag :order
|
||||
|
||||
= render 'admin/shared/form_actions', :delete_button => link_to(content_tag(:span, t('.destroy')), admin_content_type_url(@content_type), :confirm => t('admin.messages.confirm'), :method => :delete, :class => 'button small remove'), :button_label => :update
|
15
app/views/admin/contents/new.html.haml
Normal file
15
app/views/admin/contents/new.html.haml
Normal file
@ -0,0 +1,15 @@
|
||||
- title t('.title', :type => @content_type.name.capitalize)
|
||||
|
||||
- content_for :submenu do
|
||||
= render 'admin/shared/menu/contents'
|
||||
|
||||
- content_for :buttons do
|
||||
= admin_button_tag t('admin.contents.index.edit'), edit_admin_content_type_url(@content_type), :class => 'edit'
|
||||
|
||||
%p= @content_type.description
|
||||
|
||||
= semantic_form_for @content, :url => admin_contents_url(@content_type.slug), :html => { :multipart => true } do |form|
|
||||
|
||||
= render 'form', :f => form
|
||||
|
||||
= render 'admin/shared/form_actions', :back_url => admin_contents_url(@content_type.slug), :button_label => :create
|
@ -1,6 +1,10 @@
|
||||
- collection_name = "#{collection_name.singularize}_custom_fields"
|
||||
- custom_fields = f.object.send(collection_name.to_sym)
|
||||
- ordered_custom_fields = f.object.send(:"ordered_#{collection_name}")
|
||||
|
||||
= f.foldable_inputs :name => :custom_fields, :class => 'editable-list fields' do
|
||||
- f.object.ordered_asset_custom_fields.each do |field|
|
||||
= f.fields_for :asset_custom_fields, field, :child_index => field._index do |g|
|
||||
- ordered_custom_fields.each do |field|
|
||||
= f.fields_for collection_name.to_sym, field, :child_index => field._index do |g|
|
||||
%li{ :class => "item added #{'error' unless field.errors.empty?}"}
|
||||
%span.handle
|
||||
= image_tag 'admin/form/icons/drag.png'
|
||||
@ -20,7 +24,7 @@
|
||||
%span.actions
|
||||
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove first', :confirm => t('admin.messages.confirm')
|
||||
|
||||
= f.fields_for :asset_custom_fields, @collection.asset_custom_fields.build(:label => 'field name'), :child_index => '-1' do |g|
|
||||
= f.fields_for collection_name.to_sym, custom_fields.build(:label => 'field name'), :child_index => '-1' do |g|
|
||||
%li{ :class => 'item template' }
|
||||
%span.handle
|
||||
= image_tag 'admin/form/icons/drag.png'
|
@ -150,6 +150,10 @@ en:
|
||||
help: "Your model should have one field at least. The items created from this content type would have their first field mandatory."
|
||||
show_items: show items
|
||||
new_item: new item
|
||||
form:
|
||||
order_by:
|
||||
updated_at: 'By "updated at" date'
|
||||
position: Manually
|
||||
|
||||
contents:
|
||||
index:
|
||||
@ -158,6 +162,11 @@ en:
|
||||
download: download items
|
||||
new: new item
|
||||
no_items: "There are no items for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
||||
new:
|
||||
title: '{{type}} — new item'
|
||||
edit:
|
||||
title: '{{type}} — editing item'
|
||||
|
||||
|
||||
formtastic:
|
||||
titles:
|
||||
|
@ -45,8 +45,9 @@ Locomotive::Application.routes.draw do |map|
|
||||
|
||||
resources :content_types
|
||||
|
||||
resources :contents, :path => "content_types/:slug/contents"
|
||||
|
||||
resources :contents, :path => "content_types/:slug/contents" do
|
||||
put :sort, :on => :collection
|
||||
end
|
||||
end
|
||||
|
||||
# magic urls
|
||||
|
3
doc/TODO
3
doc/TODO
@ -1,6 +1,9 @@
|
||||
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
|
||||
|
||||
BACKLOG:
|
||||
- liquid rendering engine
|
||||
|
16
public/javascripts/admin/contents.js
Normal file
16
public/javascripts/admin/contents.js
Normal file
@ -0,0 +1,16 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
var updateContentsOrder = function() {
|
||||
var list = $('ul#contents-list.sortable');
|
||||
var ids = jQuery.map(list.sortable('toArray'), function(e) {
|
||||
return e.match(/content-(\w+)/)[1];
|
||||
}).join(',');
|
||||
$('#order').val(ids || '');
|
||||
}
|
||||
|
||||
$('ul#contents-list.sortable').sortable({
|
||||
items: 'li.content',
|
||||
stop: function(event, ui) { updateContentsOrder(); }
|
||||
});
|
||||
|
||||
});
|
@ -54,6 +54,14 @@ ul.list li {
|
||||
background: transparent url(/images/admin/list/item.png) no-repeat 0 0;
|
||||
}
|
||||
|
||||
ul.list li em {
|
||||
display: block;
|
||||
float: left;
|
||||
background: transparent url(/images/admin/list/item-left.png) no-repeat left 0;
|
||||
height: 31px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
ul.list li strong a {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
@ -151,6 +159,22 @@ div#asset-uploader { display: inline-block; margin-left: 10px; }
|
||||
div#asset-uploader span.spinner { position: relative; top: -3px; display: none; }
|
||||
div#uploadAssetsInputQueue { display: none; }
|
||||
|
||||
/* ___ contents ___ */
|
||||
|
||||
#contents-list li { background: none; }
|
||||
|
||||
#contents-list li em {
|
||||
background-position: left -31px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#contents-list li strong {
|
||||
margin-left: 18px;
|
||||
display: block;
|
||||
height: 31px;
|
||||
background: transparent url(/images/admin/list/item-right.png) no-repeat right 0;
|
||||
}
|
||||
|
||||
|
||||
/* ___ pages ___ */
|
||||
|
||||
|
33
spec/models/content_instance_spec.rb
Normal file
33
spec/models/content_instance_spec.rb
Normal file
@ -0,0 +1,33 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe ContentInstance do
|
||||
|
||||
before(:each) do
|
||||
Site.any_instance.stubs(:create_default_pages!).returns(true)
|
||||
@content_type = Factory.build(:content_type)
|
||||
@content_type.content_custom_fields.build :label => 'Title', :kind => 'String'
|
||||
@content_type.content_custom_fields.build :label => 'Description', :kind => 'Text'
|
||||
@content_type.highlighted_field_name = 'custom_field_1'
|
||||
end
|
||||
|
||||
context 'when validating' do
|
||||
|
||||
it 'should be valid' do
|
||||
build_content.should be_valid
|
||||
end
|
||||
|
||||
# Validations ##
|
||||
|
||||
it 'should validate presence of title' do
|
||||
content = build_content :title => nil
|
||||
content.should_not be_valid
|
||||
content.errors[:title].should == ["can't be blank"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def build_content(options = {})
|
||||
@content_type.contents.build({ :title => 'Locomotive', :description => 'Lorem ipsum....' }.merge(options))
|
||||
end
|
||||
|
||||
end
|
@ -6,6 +6,8 @@ describe ContentType do
|
||||
Site.any_instance.stubs(:create_default_pages!).returns(true)
|
||||
end
|
||||
|
||||
context 'when validating' do
|
||||
|
||||
it 'should have a valid factory' do
|
||||
Factory.build(:content_type).should be_valid
|
||||
end
|
||||
@ -33,3 +35,5 @@ describe ContentType do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -6,6 +6,7 @@ module Mongoid #:nodoc:
|
||||
parentize_without_custom_fields(object, association_name)
|
||||
|
||||
if self.custom_fields?(object, association_name)
|
||||
puts "[parentize_with_custom_fields] association_name = #{association_name} / #{self.custom_fields_association_name(association_name)}"
|
||||
self.class.send(:define_method, :custom_fields) do
|
||||
fields = object.send(self.custom_fields_association_name(association_name))
|
||||
fields.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
|
||||
|
Loading…
Reference in New Issue
Block a user