[v4.0] Content Blocks

Jamela Black edited this page Aug 3, 2014 · 7 revisions
Clone this wiki locally

This guide covers how developers can create content blocks. It covers the following concepts:

  • Generating a New Content Block
  • Customize Forms views
  • Attributes
  • Adding Relationships
  • Migrations
  • Grouping related Content Blocks
  • Viewable Content Blocks by Public [Addressable Blocks]

Generating a New Content Block

New blocks can be created using their own generator. For this guide, we will start by creating a Product block. This will represent a single product that users can browse and purchase for. CMS users will be able create new products via a generated user interface. Products can be place on pages as content, or fetched by Portlets based on specific criteria.

Run the following command:



Rails 3
$ rails g cms:content_block Product name:string price:integer description:html

This generates the Content Block with several attributes. We will cover a list of available attributes later.

Generated files

The generator creates a number of files, as well as modify the routes.rb file to add the new routes for modifying Products.

File Purpose
app/models/product.rb The Product content block (i.e. the model)
db/migrate/20100127012201_create_products.rb Migration to create the product tables (The timestamps will vary)
app/controllers/cms/products_controller.rb Controller to modify products
app/views/cms/products/_form.html.erb Partial for the form to edit Products
app/views/cms/products/render.html.erb View for rendering a Product
test/unit/models/product_test.rb Unit test for the Product model
test/functional/cms/products_controller_test.rb Functional test for the Product Controller
config/routes.rb Edited to add routing information for Products

Migrating the database

After generating the content block, you will need to run the migration to add the product tables. Run the following at your terminal:


$ rake db:migrate

This will create several tables and registers the new Product in the UI. You should not need to restart your server if its already running to use the admin interface.

Viewing the User Interface

After creating the model, you can review the basic user interface that has been generated to add and edit products. In the CMS UI, you can go directly to /cms/products. This is the index page for products, which will list all instances of your Product models in the database. As we have just created this block, there should be none in the database.

Click on the Add New Content button, which will bring up the form to add a new instance of a Product. This form will have three form fields for the three attributes we specified in our generator, name, price and description. The form fields available on this page are determined by what is in the +app/views/cms/products/_form.html.erb, which will cover later.

Fill in some data for each of these three fields and click the Save and Publish button. You will now be looking at a view of the product you just added. Each product has a default way of showing it’s data to the world, which is defined in the app/views/cms/products/render.html.erb file. When a block is first generated, it will just list it’s attributes. However, as you will see later, you can edit that file to change how it’s displayed. On this page (/cms/products/1) you are seeing a product by itself, but since any block can be added to a page, the render.html.erb determines how it will look.

Available Attributes

Blocks are really just ActiveRecord models, and they will support all of the same attribute or column types that Rails model or migrations do. Each attribute will be mapped to a column in the products table and will have a form field to allow users to edit it. Here is a list of the commonly used attributes. Not all attributes can be specified via the generator, though they can be added to blocks later. These attribute

Generator Migration Field Description
string string cms_text_field A single line text field allowing for basic text entry.
text text cms_text_area A multiline text area for basic text.
integer integer cms_text_field See string
decimal decimal cms_text_field See string
boolean boolean cms_check_box A single check box
date date cms_date_picker A javascript popup date picker that lets you select date from a mini calendar
datetime datetime cms_datetime_select A widget to choose month, day, year and time using several select boxes
category belongs_to :category cms_drop_down A styled select to choose any Category associated with the block
html text (64.kilobytes + 1) cms_text_editor A WYSIWYG Html Editor
attachment belongs_to :attachment cms_file_field A File Browse button to upload files into the repository.
NA NA cms_tag_list A text area that can be used to associate blocks with Tags (many to many)
NA integer cms_drop_down A styled select to handle creating belongs_to relationship with other blocks

Table Key

  • Generator – What is specified the generator (i.e. name:string)
  • Migration – The migration column type that Rails will use to generate the table.
  • Field – The name of the default Form Field method which is generated to allows users to edit that attribute.
  • Description – The basic purpose of the form field.

Examples

Here are some examples of potential usages for generating blocks, using the generator.


$ rails g cms:content_block Meeting name:string start_date:date end_time:datetime
$ rails g cms:content_block photo attachment:attachment on_shelf:category

Gotchas

  • Specifying the name attribute is optional. It will be included automatically if you don’t specify it.
  • Any block can only ever have one attribute of type ‘Attachment’. The field name must be ‘Attachment’, though you can change the label shown on the form field.

Other Attributes

This list of attributes isn’t comprehensive and any migration attribute supported by Rails may work. The content_block generator will just pass the attribute to the migration and use a cms_text_field to edit the attribute.

Validations

Since content blocks are just still ActiveRecord objects, you can use any Rails Validations to enforce the data validity. Let’s add one to our Product. Edit the app/models/product.rb file. It should look like this:


class Product < ActiveRecord::Base
  acts_as_content_block
end

Let’s assume we want to require that users will specify a price when they add a new product. Add the following to the block.


class Product < ActiveRecord::Base
   validates_presence_of :price
end

Then open up /cms/products in your browser and click Add New Content. Leave the price empty and try to save the block. The CMS will check the product has a price, and will display an error message if it’s not included. It will also highlight the field to indicate where the error is.

See the ActiveRecord Validation Guide for a more detailed list of supported validations.

Customizing the Form using SimpleForm

The form created for us is good enough, but suppose we want change it up a bit. For example, I would like to add some instructions to our price field to indicated that it’s a required integer (whole dollars only) and put it last in the order. We can do this by editing the app/views/cms/_form.html.erb partial.

In BCMS 4.0, all internal forms have been converted to use SimpleForm rather than our own custom form builder. This provides better consistency with bootstrap forms, as well as well tested API for defining new form inputs. This will primarily affect developers when they create content blocks. New content will be generated using simple_form syntax like so:

<%= f.input :photo, as: :file_picker %>

rather than the older syntax that looks like this:

<%= f.cms_file_field :photo %>

Changing the View

As mentioned before, each content block has a single view that will be used to render the HTML when this product is placed directly on a page. For our Product block, this file is app/views/cms/products/render.html.erb. Open up that file and you should see the following:


<p><b>Name:</b> <%= @content_block.name %></p>
<p><b>Price:</b> <%= @content_block.price %></p>
<p><b>Description:</b> <%= @content_block.description %></p>

This is a reasonable start, but its likely you will want to change this if you start placing Products onto pages. Let’s go ahead and alter this file to see how it works. Alter it to match the following:


<p><b><%= @content_block.name %></b> $<%= @content_block.price %>.00</p>
<p><%= @content_block.description %></p>

Then open the /cms/products/1 URL in your browser and see what a single product looks like. This isn’t the only way to alter how a block looks, as Portlets can also find and display blocks using different views. And if you don’t plan to use Products individually, you can likely skip this step and just build the portlets you need.

Adding more attributes

When working with blocks, its easiest if you know all your attributes before you generate the block. However, you can still add additional attributes after you have generated the block. To add a new field to a block, you will need to make the following changes:

  • Add the attribute via a migration.
  • Add the field to the form.

Let’s update our Product to add a Size attribute, which will be a drop down where users can choose S, M, L and XL.

Adding a content column via migration

To add a column to an existing block, run the following command:


$ script/generate migration update_products	

This will generate a single file db/migrate/20100129000441_update_products.rb. Open up that file, alter it to match the following:


class UpdateProducts < ActiveRecord::Migration
  def self.up    
    add_content_column :products, :size, :string    
  end

  def self.down
    remove_column :products, :size
    remove_column :product_versions, :size
  end
end

BrowserCMS adds a new migration method called add_content_column, which you can use to add the column to the correct tables. Run rake db:migrate to add the column to the database.

NOTE. One of the differences between a typical Rails Active Record and a CMS Content Block is that content blocks are versioned, meaning they keep track of every change made to them. To do this requires an additional table to keep all the older states. For example, there are two tables for our Product, products and product_versions. So when you add a new column, it must be added to both tables, which add_content_column does for you.

Add Field to Form

We want to add a selectbox to the form which will allow our user to choose from S, M, L or XL. Open the app/views/cms/products/_form.html.erb and add a new field cms_drop_down like this:


<%= f.cms_text_field :name %>
<%= f.cms_drop_down :size, ["S", "M", "L", "XL"] %>
<%= f.cms_text_editor :description %>
<%= f.cms_text_field :price, :instructions=>"Required. Must be an integer (i.e. 1, not 1.0)" %>

This will render a styled select box which will present the four choices and store the value as a string. Open /cms/products/new and you should see the following:

Renaming Content Types

When you generate a content block, by default it will use the name of the class as the ‘Display Name’ through out the user interface. In the content library in the content type menu you see ‘Products’ and the page says ‘View Product’. In some cases, the name of the class may be different that how you want it displayed, or you might want to relabel it without rewriting your code.

You can set the display_name of a given content type by defining a new method in your block. Suppose we wanted to rename our Product Block from “Product” to “Book” throughout the UI. Edit the app/model/product.rb to add the following method.


class Product < ActiveRecord::Base
  # ...
  def self.display_name
    "Book"
  end
end	

BrowserCMS will use whatever value is returned by display_name in the UI for singular labels (i.e. Add New Book). It will automatically pluralize display_name as well. If you want to explicitly set the plural form of your block, you can do so by adding the following method to the block.


class Product < ActiveRecord::Base
  # ...
  def self.display_name_plural
    "Bookz"
  end
end	

NOTE. This will not change the path to manage the block. I.e. even after change from “Product” to “Book”, the route for viewing a Product will be /cms/products/1, not /cms/books/1.

Grouping Related Blocks

As you define new Content Blocks in your project, you will notice that each new one will appear in its own group on the Content Types menu of the Content Library. By default, each Block will be assigned to its own group. It’s often a good idea to group several related Content Blocks together into a single group to keep users understand which types belong together. Open the db/migrate/20100128194303_create_products.rb migration file, which is where the ‘Group’ name is set.


class CreateProducts < ActiveRecord::Migration
  def self.up
	# ...
    ContentType.create!(:name => "Product", :group_name => "Product")
  end
	# ...
end	

When a new ContentType is created, it determines which group_name is used. By default, the group name will be the same as the block name. Change the :group_name=>"Product" to :group_name=>"Store", then run the following command to drop and rebuild the database using the migrations rake db:drop db:create db:migrate

NOTE. This technique is an easy way to just toss out all your development data and start over. Often times, its quicker to just edit an existing migration and rerun them all, rather than create a ton of little migrations just for changes to your local database.

Adding a Second Block to ‘Store’ Group

Now that we have renamed our Group from ‘Product’ to ‘Store’, lets add another block to the ‘Store’. Run the following:


$ script/generate content_block ShippingMethod name:string insured:boolean

Before running the migration, edit the db/migrate/20100129154902_create_shipping_methods.rb file to alter the following line:


class CreateShippingMethods < ActiveRecord::Migration
  def self.up
    # ...
    ContentType.create!(:name => "ShippingMethod", :group_name => "Store")
  end

  # ...
end

Now run the migration to add the Shipping Method block. Open your browser to /cms/products and your Content Types menu should look like this:

Associations between Blocks

Since Content Blocks are Active Record objects, they can be associated with each other like any models can. This includes the commonly used belongs_to and has_many associations. You can see the Active Record Associations Guide for details on how associations work.

<<<REMOVE?<<<<<When you associate Content Blocks, you will need to determine how you allow users to edit content. For instance, if our Product had a belongs_to relationship with a Category object you can use a cms_drop_down Field that allows users to select a single category.>>>>>

<<<<REMOVE?<<<<<In some cases, you may need to build your own form fields for more complex relationships (Like javascript based controls). You can use the basic Rails FormBuilder methods to construct new Fields. To get things to look right (with labels and alignments), you should start with the html used to generate other fields and work backwards from there.>>>>>>>>

NOTE. Understanding Versioning: It’s important to recognize how versioning works with respect to related blocks. Versioning will keep track of the state of each block everytime the row is saved, but does not keep track of the relationships between blocks. So if I update our Product block, then renamed the Category block it’s related to, reverting that Product will not undo the Category name change.

Adding Associations between Content Blocks & Categories

The following will outline how to add categories to your content block. Category is an attribute included with BrowserCMS, therefore you must specify the namespace "class_name: ‘Cms::Category’ in your model when adding this relationship. Depending on the your model’s relationship, you may also need to create a join table.

The following is an example for a content block called Book, with a has_many_belongs_to relationship to categories.
1. Create category type through GUI under “Category Type”
2. Add categories to category type under “Category”
3. Run migration to create joins table for adding categories to books:


rails g migration create_books_cms_catergories_jointable

4. Once created add the following and run rake db:migrate:


class CreateBooksCmsCategoriesJoinTable < ActiveRecord::Migration
  def change
  	  create_table :books_cms_categories, id: false do |t|
      t.belongs_to :book
      t.belongs_to :category
  	end
  end
end
</pre>
5. Add relationships to your content block (in the model):

module MyProjectName
  class Book < ActiveRecord::Base
     has_and_belongs_to_many :categories, class_name: 'Cms::Category'
  end
end
</pre>
6. Update your _form.html.erb to add:


<%= f.association :categories, collection: Cms::Category.of_type("Genre") %>

6. Now if you go to your content block in the GUI, you should see the categories listed in your category type where you can assign it a category as needed.

Field Types <<<<UPDATE?<<>>>

This section servers as a quick reference of the field types that Content Blocks can have. Fields are the html widgets you use to edit properties on blocks. The complete list of available fields is:

  • Text Fields (cms_text_field)
  • Text Areas (cms_text_area)
  • Check box (cms_check_box)
  • Drop Down / Selects (cms_drop_down)
  • Text Editor (cms_text_editor)
  • Attachment (cms_file_field)
  • Tag List (cms_tag_list)
  • Date Picker (cms_date_picker)
  • Date Time Select (cms_datetime_select)

Adding Associations <<<<<Select / cms_drop_down?>>>>>

To deal with associations, Simple Form can generate select inputs, a series of radios buttons or checkboxes. Lets see how it works: imagine you have a user model that belongs to a company and has_and_belongs_to_many roles. The structure would be something like:


module MyProjectName
  class Product < ActiveRecord::Base
  has_and_belongs_to_many :vendors
end

class Author < ActiveRecord::Base
  has_and belongs_to_many :vendors
end

Now we have the user form: _form.html.erb


<%= f.input :name, as: :name %>
<%= f.input :price %>
<%= f.input :description, as: :text_editor %>
<%= f.association :vendors %>

Simple enough, right? This is going to render a :select input for choosing the :vendor, and another :select input with :multiple option for the :vendors.

Attachments / cms_file_field

Each content block can have a single file attachment, which will allow users to upload files into the content repository. After uploading, the file will be
stored in a section within the CMS. Since sections determine permissions, this will allow you to control which users can access the file itself. Attached files
will have their own versioning history, which will keep track of changes.

This helper will create a stylized file upload file. An uploaded file will be associated with the content_block to which it is attached.

In form.html.erb (View)


<%= f.cms_file_field :attachment
file, :label => “File” %>

In product.rb (Block)


class Product < ActiveRecord::Base
   acts_as_content_block :belongs_to_attachment => true
end

In create_products.rb (Migration)


create_content_table :products do |t|
    t.belongs_to :attachment
    t.integer :attachment_version
end

Tags / cms_tag_list

Each content block can have many tags, much like a file_block or image_block. Each tag creates a record in the tags and taggings tables.

To enable tags for a content block:
In form.html.erb (View)


<%= f.cms_tag_list %>

In product.rb (Block)


Class Product < ActiveRecord::Base
    acts_as_content_block :taggable => true
end

No migration is necessary.

  1. To Do – Add additional examples of other Fields (like cms_drop_down with example of relationships)

Controllers

Each block will also have an associated Controller, which gives it the same CRUD methods that a typical Rails resource would have, along with a few others (like publishing and reverting). By default, the generated controller inherits all this behavior from Cms::ContentBlockController and is therefore pretty empty. Like so:


class Cms::ProductsController < Cms::ContentBlockController
end

If you want to define new methods (or override the existing methods) you can do so by editing this controller.

Public Controllers

The above controller is intended to handle only admin related actions. If you want to create a controller to use in the front end of your application, create a products controller that is not in the CMS namespace, so as not to conflict the CMS functionality.

Viewable Content blocks by Public [Addressable Blocks]

Content blocks can created with as their own pages that are viewable by the public. This means you can easily create a Product page at specific path (like /products/browsercms) without needing to create a separate portlet. To make a block addressable, a developer must do the following:

Content blocks can created with as their own pages. To make a block addressable, a developer must do the following:

1. Add is_addressable to the model. This will automatically generate a :slug form field when creating/editing instances.
2. Set the Page Template that should be used (defaults to ‘default’).


class Product < ActiveRecord::Base
  is_addressable path: "/products", template: "product"
end

3. New instances of Products will be created in a section called ‘Products’ with a path of ‘/products’.


class Product < ActiveRecord::Base
  is_addressable path: "/products", template: "product"
end

4. Add the following field to the _form.html.erb.


<%= f.input :slug, as: :path %>