![]() |
|
gjPositionsPlugin - 1.0.0Multi content type index page composition |
|
![]() |
4
users
Sign-in
to change your status |
symfony plugin that adds a compositioning workflow to your admin module (so called Composition Canvas) that allows the user to compose a canvas from components/partials (so called Design Elements) and manually assign any type of content (so called Content Elements) to them. |
symfony plugin that adds a compositioning workflow to your admin module (so called Composition Canvas) that allows the user to compose a canvas from components/partials (so called Design Elements) and manually assign any type of content (so called Content Elements) to them.
| Name | Status | |
|---|---|---|
|
|
lead | yl.laci <<ta>> refeac |
Copyright (c) 2010-2011 Christian Schaefer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| Version | License | API | Released |
|---|---|---|---|
| 1.0.0stable | MIT license | 1.0.0stable | 13/12/2010 |
Many websites - especially those driven by editorial content - have one or more aggregation pages like home pages, index pages, category pages and so on. Their purpose is to aggregate the underlying contents often with the latest contents on top but also you often find contents listed manually by an editor using some kind of administration tool.
The difficulty is that the underlying contents can be of various types like articles, galleries, videos, quizes, etc. .. Most of these contents will have their own custom visualisation as i.e. a gallery will render differently than an article.
gjPositionsPlugin provides means to compose such aggregation pages from a flexible list of design elements such as slideshows or teasers and to manually assign any type of content to them.
This functionality is provided in form of admin modules that you can generate for any of your models (in case a home page and an index page are not the same).
Currently you can only get this plugin from GitHub. It is still highly experimental and will change.
$ cd plugins
$ git clone git://github.com/caefer/gjPositionsPlugin.git
This plugin depends on my fork of sfDoctrineDynamicFormRelationsPlugin which is originally developed by Kris Wallsmith. A pull request is issued already so hopefully both versions can be merged again.
$ cd plugins
$ git clone git://github.com/caefer/sfDoctrineDynamicFormRelationsPlugin.git
Then edit your ProjectConfiguration class and enable the plugins.
<?php
require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
$this->enablePlugins('sfDoctrinePlugin');
$this->enablePlugins('gjPositionsPlugin');
$this->enablePlugins('sfDoctrineDynamicFormRelationsPlugin');
}
}
Next you need to publish the plugins assets by running the following.
$ php symfony plugin:publish-assets
Please note that gjPositionsPlugin requires jQuery and jQuery UI. It was tested with jQuery version 1.4.2 (jquery-1.4.2.min.js) and jQuery UI version 1.8.6 (jquery-ui-1.8.6.min.js). Both files have to be loaded for the admin module to work!
To get started you have to follow these following steps.
Assuming you have a model called Homepage you simply have to add the gjCompositionCanvas behaviour to its schema definition.
Homepage:
actAs:
Timestampable: ~
gjCompositionCanvas: ~
columns:
title: string(255)
headline: string(255)
...
This one is totally up to you! A Design Element can be anything from a simple partial template to a complex component implementing business logic. The only thing you have to regard is that the only the following parameters will be passed to your partial/component:
params is an array of settings saved for this Design Element. You will see in a moment how this can be done.subject is the instance of your composition model (i.e. Homepage) that this Design Element is assigned to.contents is a list of Content Elements that were manually assigned to the Design Element.Lets assume you wrote a simple partial yourmodule/banner that renders a bit of HTML and contains no other PHP (more complex examples will follow later in this document).
all:
gjPositionsPlugin:
design_elements:
banner:
description: "This is a simple banner"
applies_to: [ Homepage ]
include: "yourmodule/banner"
accept: ~
params: ~
With these settings you defined a Design Element called banner that can be assigned to a Homepage and will render the partial yourmodule/banner.
If you want to provide a little business logic you may prefer a component. Here is what the settings would look like.
all:
gjPositionsPlugin:
design_elements:
banner:
description: "This is a simple banner"
applies_to: [ Homepage ]
include: [ yourmodule, banner ] // for a component you provide an array
accept: ~
params: ~
The other settings will be explained further on.
You simply generate an admin module for your Homepage model with the symfony/doctrine admin generator.
$ php symfony doctrine:generate-admin --theme=composition frontend Homepage
The only difference is that you use the composition theme provided by gjPositionsPlugin.
Now browse to your freshly generated admin module and take a look. You will see that the edit view of your admin module consists of three columns.
HomepageHomepageYou don't have to change anything in your action so it might look as simple as this.
public function executeIndex(sfWebRequest $request)
{
$id = $request->getParameter('id');
$this->forward404unless($id);
$this->homepage = Doctrine_Core::getTable('Homepage')->find($id);
}
The interesting part happens in the template.
<?php use_helper('gjPositions') ?>
...
<?php foreach($page['DesignElements'] as $name => $designElement): ?>
<?php include_design_element($designElement); ?>
<?php endforeach; ?>
...
Through the DesingElements relation you can simply iterate of all Design Elements that you have assigned to this Homepage in the admin module. The gjPositionsHelperprovides a helper function include_design_element() which merges the Design Element with the settings from your app.yml and returns either an include_partial() or include_component().
And that's all there is to it to be able to compose a Homepage from various Design Elements.
But of course there is more to it!
If make heavy use of gjPositionsPlugin and turned multiple models into Composition Canvases you might find the situation that some Design Elements are not suitable for all Composition Canvases.
I.e. a contactform might be only suitable for a Sidebarbut not for a Homepage.
Taking the above example the following settings will limit the use of a banner to only Homepages and Sidebars.
all:
gjPositionsPlugin:
design_elements:
banner:
description: "This is a simple banner"
applies_to: [ Homepage, Sidebar ]
include: [ yourmodule, banner ]
accept: ~
params: ~
With all the above you would be able to implement for example a Design Element that displays the latest articles that have been created in your database. This would be a simple component that fetches a number of articles from the database and renders them in a list in its partial template.
You can use this one Design Element on all your Homepages. But you might want to filter the articles for a section homepage to show only those related to this section? Or you might want to list the latest 20 articles on your homepage but only 10 on your section homepage?
For this you can configure parameters on Design Elements like the following.
all:
gjPositionsPlugin:
design_elements:
latestArticles:
description: "Shows the latest articles"
applies_to: [ Homepage ]
include: [ article, latest ]
accept: ~
params:
number: { type: text, default: "5" }
section_id: { type: text, default: false }
These above settings will result in two more input fields on the Design Element in your admin module. They can be edited per assignation.
All types of <input/> tags are available but probably only a few like text, checkbox or radio make sense.
The values of these parameters will be available in your component and/or partial.
// in your component
...
$this->articles = Doctrine_Query::create()
->from('Article a')
->orderBy('a.created_at DESC')
->limit($this->params['number'])
->execute();
...
// in your partial
...
<?php for($i=0; $i < $params['number']; $i++): ?>
...
<?php endfor; ?>
...
Another form of parameterisation but with added usability is the assignment of Content Elements to a Design Element.
To give you an example you might want to show a Design Element "slideshow" on the top of your homepage but you want to assign the images manually.
Lets prepare the Image model first.
Image:
actAs:
gjCompositionContent: ~
title: string(255)
file: string(255)
...
After you regenerated the model classes you Image will have a relation to the Content Elements (gjContentElement) which are loosely coupled to it. No extra fields will be added.
Then you implement a slideshow component in your "gallery" module that expects a number of images to be displayed in a loop (i.e. using a jQuery plugin). Configure it like this.
all:
gjPositionsPlugin:
design_elements:
slideshow:
description: "Shows a slideshow of manually assigned images"
applies_to: [ Homepage ]
include: [ gallery, slideshow ]
accept: [ Image ]
params: ~
Now in your component and partial template you can access the list of all assigned content elements.
// in your component
...
foreach($this->contents as $content)
{
...
}
...
// in your partial
...
<?php foreach($contents as $content): ?>
...
<?php endforeach; ?>
...
Each content will be of type gjContentElement on which the original content (in this case an Image object) is available throught the getObject() method. So you can do the following.
// in your partial
...
<?php foreach($contents as $content): ?>
<?php $image = $content->getObject(); ?>
<?php echo image_tag($image->file, array('title' => $image->title)); ?>
<?php endforeach; ?>
...
When you created your admin module symfony added an sfDoctrineRouteCollection to your applications routing.yml like this.
homepage:
class: sfDoctrineRouteCollection
options:
model: Homepage
module: homepage
prefix_path: /homepage
column: id
with_wildcard_routes: true
As the composition admin module will contain a lot of informations it will issue a lot of queries to your database because all relations are by default lazy loaded which will happen for each and every iteration on the page.
To reduce the number of queries to a minimum you simply have to add two more lines to the above route definition so it looks like this.
homepage:
class: sfDoctrineRouteCollection
options:
model: Homepage
module: homepage
prefix_path: /homepage
column: id
with_wildcard_routes: true
model_methods:
object: getObject
This will add all appropriate left joins to the queries which will in effect avoid a lot of similar queries.
When introducing gjPositionsPlugin to an existing project chances are that you are not able to regenerate your admin modules as you have already made some modifications to it. So instead there needs to be a way to adapt this functionality without loosing your old work.
You can do this in three simple steps.
In your admin module you have to create the file components.class.php just next to your actions.class.php in the actions folder.
<?php
require_once sfConfig::get('sf_module_cache_dir').'/auto'.ucfirst('demo_page').'/actions/components.class.php';
/**
* demo_page components.
*
* @package positionsdemo
* @subpackage demo_page
* @author Your name here
* @version SVN: $Id: components.class.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
*/
class demo_pageComponents extends autoDemo_pageComponents
{
}
The above assumes the name
demo_pagefor your module and you have to replace it with the real name.
Open your generator.yml and change the theme to "composition".
generator:
class: sfDoctrineGenerator
param:
model_class: DemoPage
theme: composition
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: demo_page
with_doctrine_route: true
actions_base_class: sfActions
config:
actions: ~
fields: ~
list: ~
filter: ~
form: ~
edit: ~
new: ~
Now what's left is to clear the cache in order for the admin generator to generate the module from scratch in your cache directory.
$ php symfony cc
