Creating a Nested Widget

When dealing with content, widgets, e.g. for text, images, Google maps, etc, come in handy. Add as many of them to your pages as you think appropriate, move them around, or edit their content.

In contrast to widgets containing and presenting their content directly, nested widgets are supposed to group other widgets together in order to make them function as a whole, or to simply arrange them nicely on the page. A tab group widget, for example, forms a tab group from the tab widgets you add to it.

Implementing a tab group widget

We will create a tab group widget consisting of tabs which are also represented as widgets. Every tab widget will have a title, which should be displayed as the tab title, and content that is displayed as the contents of the tab group if the tab is active. The content of a tab widget will be editable directly on the page. Adding, deleting, sorting and renaming tabs will be made available through the details dialog.

Here is what it should look like:

First, we'll have Scrivito generate the corresponding widget models:

Copy
rails generate scrivito:widget tab_group
rails generate scrivito:widget tab

The next step is to define the attributes the models require. We're going to provide two empty tabs for a new tab group by specifying them as the default of the “tabs” attribute. 

Also, we'll bind these widget types to one another to ensure that only tab widgets can be inserted into a tab group widget and, vice versa, that tab widgets may only be inserted into a tab group. The latter prevents tab widgets from being added directly to pages.

Copy
# app/models/tab_group_widget.rb

class TabGroupWidget < Widget
  attribute :tabs, :widgetlist, default: [
    TabWidget.new(title: 'First Tab'),
    TabWidget.new(title: 'Second Tab')
  ]

  def valid_widget_classes_for(field_name)
    [TabWidget]
  end
end
Copy
# app/models/tab_widget.rb

class TabWidget < Widget
  attribute :title, :string
  attribute :content, :widgetlist

  def self.valid_container_classes
    [TabGroupWidget]
  end
end

Next, we'll add the desired functionality to the widget views. The “show” view of the tab group widget should display its tabs and allow the user to edit their titles as well as the content of the active tab. Since the individual tab widgets are rendered by the template of the tab group widget, the tab widget template, “app/views/tab_widget/show.html.erb” should be removed.

Copy
<!-- app/views/tab_group_widget/show.html.erb -->

<div class="tab-group">
  <div class="tab-titles">
    <% widget.tabs.each_with_index do |tab, i| %>
      <div class="tab-title <%= 'tab-active' if i == 0 %>">
        <%= scrivito_value tab.title %>
      </div>
    <% end %>
  </div>

  <div class="tab-contents">
    <% widget.tabs.each_with_index do |tab, i| %>
      <%= scrivito_tag :div, tab, :content,         
        class: "tab-content #{'tab-active' if i == 0}" %>
    <% end %>
  </div>
</div>

We'll have the “details” view of the tab group widget display the tab widgets so that editors can rearrange tabs or delete them as needed:

Copy
<!-- app/views/tab_group_widget/details.html.erb -->

<%= scrivito_details_for 'Tabs' do %>
  <%= scrivito_tag :div, widget, :tabs %>
<% end %>

The details views of the tab widgets makes their titles editable:

Copy
<!-- app/views/tab_widget/details.html.erb -->

<%= scrivito_tag :div, widget, :title %>

Look and feel

To make our tab group look and feel like a real tab group, please copy and paste some CSS and JavaScript code to the project.

Show CSS and JavaScript
Copy
/* app/assets/stylesheets/application.css​ */

.tab-group
{
  padding: 5px;
  width: 100%;
}

.tab-group .tab-titles
{
  border-bottom: 1px solid #ddd;
  height: 42px;
}

.tab-group .tab-titles .tab-title
{
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
  float: left;
  margin-right: 10px;
  overflow: hidden;
  padding: 10px 50px;
  text-align: center;
}

.tab-group .tab-titles .tab-title: not(.tab-active)
{
  border: 1px solid #fff;
  border-bottom: 1px solid #ddd;
  color: #0e6dea;
}

.tab-group .tab-titles .tab-title: not(.tab-active): hover
{
  background: #eee;
  border: 1px solid #eee;
  border-bottom: 1px solid #ddd;
  color: #386bae;
  cursor: pointer;
}

.tab-group .tab-titles .tab-title.tab-active
{
  border: 1px solid #ddd;
  border-bottom: 1px solid #fff;
}

.tab-group .tab-contents
{
  clear: both;
}

.tab-group .tab-contents .tab-content
{
  display: none;
  min-height: 200px;
  padding: 10px;
  width: 100%;
}

.tab-group .tab-contents .tab-content.tab-active
{
  display: block;
}

.tab-title-details
{
  background: #bbb;
  border: 1px solid #aaa;
  padding: 10px;
}


Copy
// app/assets/javascripts/application.js​

scrivito.on('content', function(content) {
  $(content).find('.tab-group').each(function(i, tab_group) {
    tab_group = $(tab_group);
    tab_group.find('.tab-title').each(function(i, tab_title) {
      tab_title = $(tab_title);
      tab_title.click(function() {
        tab_group.find('.tab-title').removeClass('tab-active');
        tab_title.addClass('tab-active');
        tab_group.find('.tab-content').removeClass('tab-active');
        tab_group.find('.tab-content').eq(tab_title.index()).addClass('tab-active');
      });
    });
  });
});

Using the tab group widget

Now, navigate to a page that includes a widgetlist field and insert a tab group widget. The new tab group will be created with two tabs:

In the details dialog of the tab group widget you can rename existing tabs, change their order, delete or add a new tab:

You can, of course, edit the bodies of the tabs: