How to create content elements
Content elements are collections of elements with the same properties. They can be used for creating any type of content like news, calendar items, companies, contacts, etc. These elements can then be displayed on the site by using the content collection section (for displaying lists) and the content item section (for displaying an individual item).
Content elements in Skyline are a subclass of articles (just like pages) which means they can contain static data and contain sections.
Here I'll guide you through creating a calendar item content element to create an events list and a detail page for each event.
Migration
First of all we'll create a migration:
$ script/generate migration CreateCalendarItemData
Our calendar item will have two static fields: title and date
class CreateCalendarItemData < ActiveRecord::Migration
def self.up
create_table :calendar_item_data do |t|
t.string :title
t.date :date
t.timestamps
end
end
def self.down
drop_table :calendar_item_data
end
end
Model
After we've created the migration we move onto the model.
calendar_item.rb
class CalendarItem < Skyline::Article
class Data < Skyline::Article::Data
set_table_name "calendar_item_data"
include Skyline::Taggable
has_one :calendar_item, :foreign_key => "published_publication_data_id", :class_name => "CalendarItem"
named_scope :published, lambda{
{:include => [:calendar_item], :conditions => "skyline_articles.published_publication_data_id = calendar_item_data.id"}
}
# Default scope for site
default_scope :order => "date DESC"
def date
self[:date].present? ? self[:date] : Date.today
end
end
include Skyline::ContentItemSectionSelectable
def for_content_item_section
self.published
end
# Default scope for Skyline
default_scope :include => :default_variant_data, :order => "calendar_item_data.date DESC"
def title
"#{I18n.l(self.default_variant_data.date, :format => :default)} #{self.default_variant_data.title}"
end
def url
p = Settings.get_page(:content_pages, "calendar_item_page_id".to_sym)
[p.andand.url,self.id].compact.join("/")
end
end
In the code above you can see we define CalendarItem as a Skyline::Article
This gives us the functionalities needed to add sections to our content element.
The Data class defined within CalendarItem can be seen as a standard ActiveRecord object that links directly to our database table.
include Skyline::Taggable
This registers the model in the Skyline::taggable_models list. Which means it will show up in the list of the content collection section.
Although it will add the interface for tagging your calendar item it will not automatically add the fields to your view. We'll come to that when we start creating the views.
include Skyline::ContentItemSectionSelectable
def for_content_item_section
self.published
end
By adding Skyline::Sections::ContentItemSection the calendar_item will show up in your content_item section.
The method for_content_item_section is used to filter the elements that are shown in the content_item section. In this case it's only the published elements that show up.
- Tip!
-
in your application folder run
$ rake doc:plugins
To generate the Skyline documentation in doc/plugins/skyline
has_one :calendar_item, :foreign_key => "published_publication_data_id", :class_name => "CalendarItem"
To be able access the data in the calendar item we need to create a has_one relationship between them.
I'll explain the url method further down when we get to the configuration bit.
The rest of code is pretty much self explanatory so I'll skip to the next bit.
Views
The views are used to render the Skyline management screens for the content items. The folder structure is based on the module name space so we'll create the following folder structure:
|-app
|-views
|-skyline
|-articles
|-calendar_item
|-_data.html.erb
|-_header.html.erb
The views of content object consist of 2 parts.
- _header.html.erb: view for general article metadata like variant and template selection
- _data.html.erb : view with edit form fields to enter fields defined in article data in this case title and date.
_header.html.erb
<dl class="advanced closed">
<dt><a href="#" id="toggle_page_advanced"><span><%= t(:advanced, :scope => [@article.class, :headers]) %></span></a></dt>
<dd>
<table class="fields">
<tbody>
<tr>
<th><%= v.label_with_text :name %></th>
<td><%= v.text_field :name %></td>
</tr>
<% if @renderable_scope.templates_for(@variant.article).size > 1 %>
<tr>
<th><%= v.label_with_text :template %></th>
<td><%= v.select :template, templates_for_select(@variant.article) %></td>
</tr>
<% end %>
</tbody>
</table>
</dd>
</dl>
_data.html.erb
<div class="section">
<div class="head">
<%= a.object.class.human_name %>
</div>
<div class="body">
<div class="body">
<table class="fields">
<tbody>
<tr>
<th><%= vd.label_with_text :title %></th>
<td><%= vd.text_field :title, :class => "full" %></td>
</tr>
<tr>
<th><%= vd.label_with_text :date %></th>
<td><%= vd.date_select :date %></td>
</tr>
<tr>
<th><%= vd.label_with_text :raw_tags %></th>
<td>
<dl class="tagselector" id="calendar_item-tagselector">
<dt></dt>
<dd><%= vd.text_area :raw_tags, :rows => nil %></dd>
<dt class="tags"><%= t(:available_tags, :scope => [:media_file, :edit]) %></dt>
<dd class="tags">
<%= render :partial => "/skyline/tags/available_tags", :locals => {:tags => CalendarItem::Data.available_tags} %>
</dd>
</dl>
<script type="text/javascript" charset="utf-8">
new Skyline.TagSelector("<%= vd.dom_id :raw_tags %>","#calendar_item-tagselector .taglist li");
</script>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
The view uses standard rails helpers to create the input fields for the calender item data.
As described before content items can be made taggable. This can be used to filter certain elements when displayed in a list. To do this we include the following code into our view:
<tr>
<th><%= vd.label_with_text :raw_tags %></th>
<td>
<dl class="tagselector" id="calendar_item-tagselector">
<dt></dt>
<dd><%= vd.text_area :raw_tags, :rows => nil %></dd>
<dt class="tags"><%= t(:available_tags, :scope => [:media_file, :edit]) %></dt>
<dd class="tags">
<%= render :partial => "/skyline/tags/available_tags", :locals => {:tags => CalendarItem::Data.available_tags} %>
</dd>
</dl>
<script type="text/javascript" charset="utf-8">
new Skyline.TagSelector("<%= vd.dom_id :raw_tags %>","#calendar_item-tagselector .taglist li");
</script>
</td>
</tr>
Templates for collection
As described before, the folder structure of templates is based on the model name space excluding Skyline.
So to create a template for our content element we need to create the following folder structure:
|-app
|-templates
|-sections
|-content_collection_section
|-default
|-index.html.erb
|-_calendar_items.html.erb
index.html.erb
When rendering a content collection template Skyline looks for an index.html.erb
The index creates a collection of the elements based filtered by tag and limited by the number of rows. Then it renders the partial corresponding with the name of the content elements:
<% proxy = content_collection_section.content_class.published.with_tags(content_collection_section.tags).scoped(:limit => content_collection_section.number) %>
<%= render :partial => "#{content_collection_section.content_name.pluralize}", :locals => {content_collection_section.content_name.pluralize.to_sym => proxy} %>
_calendar_items.html.erb
The partial _calendar_times renders the collection just as normal partial would do.
<dl>
<dt>Calendar</dt>
<dd>
<% if calendar_items.any? %>
<ul class="links">
<% calendar_items.each do |item| %>
<li>
<a href="<%= item.calendar_item.url %>">
<span class="date"><%= l(item.date,:format => :default) %></span>
<%= item.title %>
</a>
</li>
<% end %>
</ul>
<% end %>
</dd>
</dl>
Note!
methods outside of the Data class of the calendar_item are available as item.calendar_item
Templates for content items
To show an individual item on a page we can use the content_item section. Above we can see we included
Skyline::Sections::ContentItemSection
To have the calendar_items show up in the list.
Now we can create a template for it.
|-app
|-templates
|-sections
|-content_item_section
|-default
|-_calendar_item.html.erb
_calendar_item.html.erb
As a calendar item is a subclass of article we can render it by using the same heplers as page.
<h1><%= calendar_item.published_publication.data.title %></h1>
<%= render_collection(calendar_item.published_publication.sections) %>
Hint!
You should check in the view if the calendar item is published and has a title.
Configuration
Now that we've created our migration, model, views and templates it's time to configure Skyline to use all of the things.
First of all we want our calendar item to show up in the content library. For this we need to add a line in the configure block to our skyline_configuration.rb in config/initializers.
skyline_configuration.rb
config.articles = ["CalendarItem"]
settings.rb
In Skyline you have the possibility to use a model named Settings. Settings can be used to store any constants related to the webiste.
To use Settings we first have to create a migration:
class CreateSettings < ActiveRecord::Migration
def self.up
create_table :settings do |t|
t.column :page, :string
t.column :data, :text
end
end
def self.down
drop_table :settings
end
end
in the CalendarItem model we have a method called url. This method reads a preset page from the skyline settings that is used to render an individual calendar item.
So here is the code for the Settings model (app/models/settings.rb):
class Settings < ActiveRecord::Base
include Skyline::Settings
include Skyline::ContentItem
referable_serialized_content :calendar_item_page
page :content_pages, :title => "Content pages" do |p|
p.field :calendar_item_page_id do |f|
f.editor = :page_browser
f.label = "Calendar item page"
f.description = "This page will be used to render an individual calendar item."
end
end
end
In our settings model we scope the calendar_item_page into content_pages to keep things clear.
Skyline provides a helper page_browser to select a page so we won't have to bother with creating views.
After you can select a page to render in the Admin -> Settings on the Skyline website.
- Hint!
-
As you select a new page to render a content item you can create a separate template for these pages.
PagesController
Finally we have to tell Skyline what to do when we navigate to the calendar detail page.
This is done by overwriting the show method in the Skyline PagesController.
pages_controller.rb
class PagesController < Skyline::Site::PagesController
def show
renderer = @site.renderer
if @page_version
language = "nl"
renderer.assigns.update(:language => language)
# =================
# = Calendar item =
# =================
if page = Settings.get_page(:content_pages, :calendar_item_page_id) && @url_parts.any?
@calendar_item = CalendarItem.find_by_id(@url_parts.join("/")).andand.published_publication
if @calendar_item
body = renderer.render(@calendar_item)
renderer.assigns.update(:body => body)
end
end
# ========
# = Page =
# ========
if renderer.assigns[:body].blank? && @url_parts.empty?
renderer.assigns.update(:body => self.response.body)
end
render :text => renderer.render(@page_version) if renderer.assigns[:body].present?
end
# ================================================
# = Fallback; render 404 if nothing was rendered =
# ================================================
self.handle_404 unless performed?
end
end
- Hint!
-
You could also add a page for dealing with a 404 error to the settings and overwrite the handle_404 method to return the page from the settings.
This will give your users a nice page when they reach a page that doesn't exist.