|
|
||||||||||||||||||
|
|
||||||||||||||||||
![]() |
![]() |
Issue 8 - Revision 8 / September 26, 2004
|
|||
|
Archetypes: (Part III) - Customizing Views - - - - - - - - - - - - By Sidnei da Silva | Jul 21, 2004 Abstract In the previous article you learned
what Archetypes is and how to create a minimal In the present article you are going to learn:
Customizing Archetypes Views It's very common for people building Archetypes products to need to add extra stuff to the The good news: Archetypes makes it very easy to do this kind of customization. In fact, it's so "Talk is cheap. Show me the code" -- Linux Torvalds Ok, so now that we've made you curious enough about it, it's time to show you the code, and the available points of customization in Archetypes' autogenerated views.
<metal:main_macro define-macro="main"
tal:define="portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
base_macros here/base/macros;
view_template python:'%s_view' % portal_type;
view_macros python:path('here/%s/macros|nothing' % view_template);
header_macro view_macros/header | header_macro | base_macros/header;
body_macro view_macros/body | body_macro | base_macros/body;
folderlisting_macro view_macros/folderlisting | folderlisting | base_macros/folderlisting;
footer_macro view_macros/footer | footer_macro | base_macros/footer;
errors python:request.get('errors', {})">
<metal:use_header use-macro="header_macro" />
<metal:use_body use-macro="body_macro" />
<metal:use_body use-macro="folderlisting_macro" />
<metal:use_footer use-macro="footer_macro" />
</metal:main_macro>
Let's get down and dirty, and see what's going on here. The very first thing that happens is that the portal type of the content being rendered is called and normalized, first by lowercasing the string and then by replacing empty spaces with underscores. Then '_view' is appended to the resulting string. This is what happens if you have a 'News Item' being rendered:
In [1]: s = 'News Item' Then, using this name, a template is looked up using the 'path()' built-in of Page Templates. If the template cannot be found, or an object with that name is found that doesn't have a 'macros' subitem, or the object is a template but doesn't have the needed macro, the following happens:
Once you've understood this, you should be able to make the more common customizations in such a way that you don't need to customize the whole 'base_view' or 'base_edit' template, or even create a complete template from scratch. All you have to do is to create a basic template with a special name containing just the macros you are interested in overriding. The benefit you gain from doing this is that your templates remain clean and simple, and the likelihood that your templates will break when you upgrade Archetypes or Plone is very small. Reusing existing macrosNow, suppose you do want to write a complete template from scratch. Actually, you probably wouldn't want to write the whole thing, unless you really know what you are doing. It would also mean you're reinventing the wheel (and though we do that a lot in the Zope world, it is rarely justified). Suppose you want to reuse the part that contains the autogenerated form widgets. It's probably a good idea to do so, because the respective templates are somewhat complex, and require a deep understanding of what is going on. This part of the templates is located at the 'edit_macros' template. Reuse does depend on a couple of variables being defined, which is done on the 'base_edit' template, just before the macros are called from 'edit_macros'. Here's an excerpt from 'base_edit' defining the variables:
...
define="errors options/state/getErrors | nothing;
Iterator python:modules['Products.Archetypes'].IndexIterator
schematas here/Schemata;
fieldsets python:[key for key in schematas.keys() if key != 'metadata'];
default_fieldset python:(not schematas or schematas.has_key('default')) and 'default' or fieldsets[0];
fieldset request/fieldset|options/fieldset|default_fieldset;
fields python:schematas[fieldset].fields();
...
One thing you will notice here is that it defines what fieldsets are shown, and how they are ordered. By default, the same order is used that is returned by the 'here/Schemata' call. Schemata is based on an OrderedDict, so it keeps the same ordering, which is also the order by which keys are created. You could as well use your own custom sorting based, say, on manual ordering or alphanumeric. You make the choice. It also defines which fields are going to be shown, which by default are all the fields from the current 'fieldset' (just another name for 'schemata'). Here, too, you could choose to make a different set of fields available, and even order the fields in a different way. Back to our topic: to reuse the autogenerated edit form on your own template, you need to do two things:
<metal:block use-macro=”here/edit_macros/macros/body” />
Let me give you a more concrete example. Suppose you installed the 'Calendaring' product from the Collective project. Calendaring installs a 'Calendar' and an 'Event', which are both Archetypes-based content objects. Here's a screenshot of the default autogenerated view.
Now, let's say you want to override the view for the Calendar to display a custom piece of information. All you have to do is to create a Page Template named 'calendar_view' in the 'custom' folder of 'portal_skins' with the following content:
<metal:block define-macro="body">
<h1>I am an overridden body macro!</h1>
</metal:block>
And now, a screenshot of the modified view:
As you should have noticed, the change only took three lines. No Python, no funky installation scripts. It's impossible to be simpler! Reusing existing widgetsFor widgets the 'incantation' needed is very similar. Suppose you want to customize one of your widgets to have a different look when being rendered in 'view' mode than other widgets of the same type. The first thing that comes to mind would be to create a whole new widget, or maybe to customize the widget template. Both of these are valid approaches, but they can be very complex if you've never done them before. Instead, we are going to show you how to customize a single 'mode' of a single widget with the least amount of code, while the same look as other widgets is retained for the remaining modes. All widget classes in Archetypes support overriding the widget template by passing the template name as a keyword argument, which is looked up in the skins. What we need to do to customize a widget view is:
Let's look at how to create a simple template for a widget, based on the string widget template, and then at how to make the same widget use our brand-new template. For this example we will use the 'Event' content type of the Calendaring product. Here you can see a screenshot of the default view.
For our template, we will create a macro named 'view', and then reuse the macros 'edit' and 'search' from the default string widget. I won't go into all the details as the code should be quite straightforward to read. Our custom 'view' mode simply renders a string backwards.
<metal:block define-macro="view"
tal:define="value python:list(accessor());
dummy python:value.reverse();
value python:''.join(value)">
<tal:block replace="structure value" />
</metal:block>
<metal:block define-macro="edit">
<metal:block use-macro="here/widgets/string/macros/edit"/>
</metal:block>
<metal:block define-macro="search">
<metal:block use-macro="here/widgets/string/macros/search"/>
</metal:block>
Now, to hook this into an existing Archetype:
Done! Restart your Zope instance and visit an instance of your Archetype to make sure the content of your field is being rendered backwards in 'view' mode.
As you can see here, I've set the 'Location', 'Contact Name' and 'Contact Email' fields to use the 'reversed_string_widget' template. One example of this system metadata is the “searchable” parameter we described above. When Archetypes renders the value to be indexed in the “SearchableText” index (default in CMF), it goes looking for fields with the searchable flag on and tries to concatenate all their values together to form a long string to be indexed. Another example of this system metadata is the “index” parameter of fields. This parameter is used for specifying: Archetypes is all about Schemas. Schemas are used to describe the various facets of your content type – the structure and type of your fields, where the values will be stored and also the widgets that will be used to present them. Schemas also hold some very low-level properties of your content type, which are mainly used as “system metadata” for bootstrapping and fine-tuning some underlying features. A different way of doing this, which might work best if you don't want to/cannot change the schema definition is to just name your template 'at_widget_string' instead of 'reversed_string_widget'. This way, Archetypes will automagically pick your template instead of the default string widget template. However, be aware that this will affect all string widgets. Here you can see a screenshot of the resulting view if we rename 'reversed_string_widget' to 'at_widget_string'. Note that all StringWidgets are rendered backwards. Also note that 'Attendees' is not rendered backwards. This is because it is not a StringWidget, but a LinesWidget. Spicing up your widgets: Adding Javascript and CSSOnce you get comfortable with modifying existing widgets, and as the need for more complex user interaction grows on your application, you will tend towards a richer UI, where the user can interact with dynamic elements on a page. For creating and manipulating such dynamic elements, you are probably going to use Javascript. You may also add some special styling to your widgets using CSS. However, when you start adding things like this, the size of your page keeps growing. And even worse: If you add these resources to your site template directly, they will be loaded with every page of your site, regardless of whether they are actually used or not. A good example of this problem was Plone itself. During the development phase of the 2.0 release a dynamic calendar popup widget was introduced. The calendar was amazing, and it was very well-skinned, too. However, the Javascript and CSS for just the calendar were more than one-third of the total page size of an out-of-the-box Plone site. This was fixed quickly by enabling the inclusion of the Javascript and CSS on just the pages where it was actually needed. The problem with this is that you have to add the resources manually on each template where it is needed. For Archetypes, we needed to come up with a more generic solution, one which was suitable not only for the CalendarWidget but also for other widgets that could make use of Javascript and CSS in the future. The solution was to add two properties to widgets, named 'helper_css' and 'helper_js' . These two properties take a list of names, which are then looked up in the skins and included dynamically in the <head> part of the generated page. It is obvious that even if you have multiple widgets of the same kind on a page, the Javascript and CSS only have to be included once for the page. In the case of CalendarWidget the order in which the Javascript files were included did matter. So, in order to fulfill these requirements, the complete list of files to be included is computed once, and then uniqueified. Here's the listing for the CalendarWidget definition, so you can see for yourself how to make use of this feature:
class CalendarWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/calendar",
'format' : '', # time.strftime string
'helper_js': ('jscalendar/calendar_stripped.js',
'jscalendar/calendar-en.js'),
'helper_css': ('jscalendar/calendar-system.css',),
})
Conclusion
Through this article you have learned how to make simple and effective modifications to Archetypes views and widgets without modifying the original templates. These are very powerful features which have been present in Archetypes for a while, but highly under-documented. With the experience you have gained through this article, you should now be able to build truly custom solutions that will be more robust to changes and last longer through subsequent software upgrades. I hope you enjoyed the article. Many thanks to ZopeMag for making this possible.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ZopeMag is committed to bringing you the best in Zope Documentation. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
Reproduction of material from any of ZopeMag's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 ZopeMag |
|