Proposal: Widgets and Panels

Status

Proposal under development

Target Release

4.0

Original Authors

Allen Gilliland

Abstract

To keep pace with other major blogging systems and provide standard features for blog customization, Roller should support a system for providing blog widgets. This is a proposal for how a widgets system might work in Roller. Other major blog systems which already include widget functionality are MovableType/TypePad, WordPress, LiveJournal, and Blogger, and undoubtably there are others as well. It's a good idea to check out how these other systems use widgets as a comparative basis for this proposal.

Requirements

  • define what is meant by a widget and how it would be defined in Roller.
  • define how widgets would be configured and grouped for use in blog templates and themes.
  • discuss how widgets would be controlled via the Roller UI.
  • define a way for Roller administrators to view the list of configured widgets, import new widgets, remove old widgets, and enable/disable widgets.

Design (Overview)

What is a widget?

A widget is a reusable component of content that can be inserted into any blog template, typically on the blog sidebar.

What can a widget do, what can it not do?

  • A widget can display any content normally available to weblog templates.
  • A widget can make use of macros and models.
  • A widget can be defined once and reused unlimited times, like themes.
  • Like normal templates, a widget cannot modify any data.
  • A widget cannot rely on specific data from the request, such as custom parameters.
  • A widget cannot define any new core functionality not already defined by the system, such as new urls.
  • Widgets are not contextual components, meaning they work only on page XXX or under condition YYY. they should be generic enough to work under all rendering conditions.
  • A widget is only used for display on weblog html pages, not for xml feed or other views.

How are widgets defined and configured?

A widget would be defined in basically the same way that a theme is defined. Widgets will contain an xml descriptor which defines the widget properties and behavior, then the widget would contain any number of template files and resources needed to render the content. Some rules ...

  • All widgets must have an xml descriptor.
  • All widgets must define a control template which is the template used to render the widget content.
  • A widget will be bundled into some kind of archive file, such as a jar file, so that it can be imported into the application easily.
  • widgets would be configured via a new page in the administration section which would list what widgets are configured and allow for importing of new widgets, removal of existing widgets, and enabling/disabling of widgets. this would all be possible at runtime, so the application would not need to be restarted to change widget definitions.

Grouping widgets into panels.

Widgets are typically configured into groupings which can define some layout options typically to determine the ordering of the widgets. To organize these groupings we would create the concept of "panels" which may contain any number of widgets and define how the widgets are layed out. The typical example of a widget panel is a blog sidebar.

A weblog would be allowed to define any number of panels to use in its templates, and panels would be defined either in a theme definition, or defined and customized on the Roller UI. The reason to support the idea of having multiple panels is two fold ...

1. some themes/designs will want more than one sidebar and should be allowd to do so while still leveraging the widgets feature for each.
2. if you take the idea one step further then technically you could define an entire theme using just panels and widgets and therefore provide a way for the user to customize all kinds of elements of their design using widgets, not just a sidebar.

The exact semantics of how this would work on the UI will be discussed more in the section about "Controlling widgets via the UI" below.

How do widgets get into templates?

Widgets could be added into templates using a macro call such as ...

// display a single widget
#showWidget("fooWidget")

// display a configured panel
#showPanel("sidebar")

// the code for the #showPanel macro would be roughly this ...
#set($panel = $model.weblog.getPanel($panelName))
#foreach($widget in $panel.widgets)
  #showWidget($model.weblog, $widget)
#end

Controlling widgets via the UI

One of the most important aspects of the widget system is to make it easy for users to choose widgets and have them inserted into their templates. This would require a new UI page which gives the user a view of what panels they have defined, what widgets are configured for a panel, and all widgets that are available in the system. The user would then be able to choose a panel to work on, then add/remove widgets from a panel using the pool of all available widgets.

The details of how this would work can be done in many ways, but the approach would be comparable to what other blog systems currently offer, so to get a sense for the UI it's best to try out the other systems.

Also, before spending too much time drawing up a UI I'd like to get general approval from the community with regards to the functional aspects of the proposal. It would be wasted time to design a nice UI only to have it discarded because we decided to use a different approach to the problem. Once we are committed to this general approach then we would spend more time flushing out the UI design and discussing that specifically. The main point is that users would have a wysiwyg style UI for choosing widgets and organizing them in panels which would alleviate the need for direct template editing.

Design (Object and Data Model)

how are widgets represented in the code?

Similar to the way themes work now, widgets will be defined using a new Widget pojo which will describe all elements of a widget as defined by the widget XML descriptor as well as provide access to all resources defined in the widget. The Widget pojos would look something like ...

// Every widget configured in the system gets one instance of the Widget class to represent it
public class Widget {
  String id
  String name
  String description
  List templates
  List resources

  WidgetTemplate getTemplate("path/inside/widget");

  WidgetResource getResource("path/inside/widget");
}

// templates that are part of a Widget
public class WidgetTemplate {
  String id
  String name
  String description
  
  InputStream getInputStream();
}

// resource files that are part of a Widget
public class WidgetResource {
  String path

  InputStream getInputStream();
}

how are widgets associated with a weblog? how do panels fit in?

a weblog may define 0 or more Panels which are layout containers for widgets. to associate a widget with a weblog requires a Panel and to access a panel you would use a weblog such as, weblog.getPanels() or weblog.getPanel("name")

a Panel may contain 0 or more Widgets organized in some kind of layout. Most of the time it would be vertical, but we could also allow for other layout options such as horizontal, grid, etc. to access the widgets associated with a panel you would use panel.getWidgets()

The code would look something like ...

// A panel which is attached to a specific weblog
WeblogPanel {
  String id
  WebsiteData weblog
  String name
  String description
  String layout (some way to describe a layout)
  List widgets (reference to widgets configured in this panel)
}

// A WeblogWidget only represents the association between a real Widget
// and a WeblogPanel, it does not contain any actual Widget information
WeblogWidget {
  String id
  WeblogPanel panel (panel this is attached to)
  String widgetId (id of the actual widget this represents)
  int ordering (some way to order the widgets within the panel)
}


-- sql tables
roller_weblogpanels(
  varchar(48) panelid not null primary key, 
  varchar(48) weblogid not null,
  varchar(64) name not null,
  varchar(255) description,
  varchar(64) layout not null
);

roller_panelwidgets(
  varchar(48) id not null primary key,
  varchar(48) panelid not null, 
  varchar(64) widgetid not null, 
  integer position not null
);

Design (Rendering)

what actually happens to render a widget and insert it into a template?

Option 1: Just #parse() the widget control template during normal page rendering

In this approach, widgets are rendered as part of the overall rendering process for a page, so widgets are just templates that get parsed and included. i.e. a call to #showWidget("foo") triggers a call to #parse("widget:foo") and relies on a WidgetResourceLoader to provide an input stream to the appropriate widget control template.

Pros:

  • simplest approach and least resource intensive.

Cons:

  • less opportunity for caching since the logic is inside of a macro call, tied to normal page rendering, and mostly handled by velocity.
  • we don't have much control over widget rendering. widgets would have to work with whatever is available in the normal model for page rendering, and wouldn't have an opportunity to be given special rendering attention. so in this situation widgets basically just become a glorified way to make macro calls.

Option 2: Widgets go through the full rendering system

With this scenario widgets go through the full rendering process and the output simply gets pasted into the template. so calling #showWidget("foo") calls a Model method like $model.widget("foo") that instigates the typical rendering process and inserts the returned output. The $model.widget("foo") call may trigger something like this ...

WidgetManager.renderWidget(widgetName, Weblog weblog)
    - caching lookup (optional)
    - lookup control Template for rendering specified widget
    - calls renderer manager to find renderer for widget template
    - render output
    - caching storage (optional)
    - return output

Pros:

  • Caching can be done easily by the WidgetManager so that widgets aren't re-rendered for every page.
  • We can control Models for widgets, optionally doing something fancy here like allowing special Model classes for certain widgets. we also wouldn't have to load the model with all the same data available for normal page rendering, which can be a lot of unnecessary data when pre-3.0 model loading is enabled.

Cons:

  • some level of performance hit during rendering since you add in the overhead of going back through the rendering system, possibly multiple times. this could likely be negated by providing some decent caching for widgets, so that once a widget is rendered it can be reused multiple times without needing to be re-rendered.

A Walkthrough

1. Load Widget Library. a widget is imported into the system, either at runtime or was previously imported and is loaded at app init time. in this step some kind of WidgetManager would inspect all of the widget archive files, parse their XML descriptors, and construct Widget objects to represent them while the system is running.

2. Associate widgets to a weblog. to make use of widgets they need to be attached to a weblog via a panel. this is done either by a theme or directly by a weblog owner on the UI. the end result is that a WeblogPanel is defined and any number of WeblogWidgets is associated to the panel. The WeblogWidgets simply defines an association indicating that widget "foo" is attached to panel "sidebar" in weblog "myWeblog". The actual "foo" widget is maintained by the WidgetManager.

3. Rendering a widget. when a weblog page is being rendered it would indicate that it wants to render a specific panel using a macro call like #showPanel("sidebar"). that macro would perform the necessary logic to lookup the panel from the weblog, $model.weblog.getPanel("sidebar"), then render each widget in the panel using a loop over $panel.widgets. For each widget being rendered the process would be slightly different depending on the rendering approach chosen, but the outcome would be the same.

Issues

  • how to access widget resources? Roller takes the approach that all components used by a weblog should be accessed via that weblogs urls, but this creates some issues for resources that are meant to be shared across weblogs.
    • option 1, use the existing resource servlet the same way we do for theme resources.
    • option 2, pick a url location outside of the weblog url space (/roller-ui/) to use for accessing widget resources, such as /roller-ui/widgets/<widget>/
  • No labels