Plonemall and mxmDynamicpages, a winner
The Problem:
In a standard PloneMall installation, purchaseable items are created in normal folders and then linked to categories. This gives you a nice-looking view of your products. However, clicking on the product image takes the customer directly to the product, leaving him/her in a different folder altogether. After looking at the product details, for many people the first response would be to go one up on the breadcrumb trail, but this leaves them in a bland-looking folder, not back in the category. Very confusing.
You could customise the folder view templates for the folder, but that means customising all the input folders through the ZMI, which is something you may not want the mall administrator to have to do.
Also, for a simple shop with fewer than about 50 products, the whole category/folder setup is too difficult to customise.
The answer:
mxmDynamicPage and some customised templates gives you a nicely formatted view on your product folders and includes some features that makes it easy to integrate products in your normal site views. Look at the end of the howto for some extra ideas.
How-to (short version)
- Install the mxmDynamicPage product into your PloneMall site.
- Customise
/portal_skins/mxmDynamicPage/list_viewsand add the two big blocks of code below to it. - Create DynamicPages as default documents for your product folders with the extra list styles as the set style.
- Read the long version for info on why it doesn't look like you want it to ;)
How-to (long version)
First, a little background: mxmDynamicPages (http://www.mxm.dk/products/public/mxmDynamicPage/) look like normal Plone Documents, but with up to 5 configurable lists added at the end. There are a couple of standard list views, but the beauty of the product is that, with some simple copy and paste coding in the list view template, you can create other views that can be accessed from a simple dropdown when the dynamicpage is created. Each view is simply a macro in the template.
For this example, I wanted the 2-column view code from the categories, as the site that I was developing was a fixed-width portal with about 580 pixels of viewing area. I also did not like the radiobuttons next to the simple items and the "add to cart" button at the bottom of the screen, as the items for this portal were all in the $100-$200 range and did not really lend themselves to "one of this, one of that" type of purchasing. The "add to cart" button also appeared even if the only items were AdvancedItems (not purchaseable) and could thus cause confusion. A good compromise is to add a text "Add to cart" link in the category-like view if the item is purchaseable.
On to code:
To create a new list view, we need to customise the dynamicpage list_views template. In the ZMI, go to /portal_skins/mxmDynamicPage/list_views and customise it.
The template consists of a couple of macros, each creating a specific list style. We'll be adding another one at the bottom. We use the normal list macro, copy and paste it at the bottom of the template, and replace its guts with some code from the category view templates.
For the purpose of this howto, we want to use the mall 2-column view, so we get code from /portal_skins/PloneMallItem/mall_browse_macro. I basically used the code from the "Subcategories and Items" part, integrating the "mall_browse_item" macro (We don't want to have that show up in the choice of list styles) and hard-coding it for 2 columns. I also removed all the form elements and added an "Add to Cart" link if the item was purchaseable. Here's the result:
<div metal:define-macro="mall2columns"
tal:define="list_style python:here.get_list_styles(list_name);
width python:list_style[0];
clear python:list_style[1];
padding_right python:list_style[2];"
tal:attributes="style string:margin: 0px;;
margin-bottom: 0ex;;
padding: 0px;;
padding-right: ${padding_right}%;;
width: ${width}%;;
float: left;;
clear: ${clear};;"
>
<h3 style="padding-bottom: 4px;"
tal:condition="list_title"
tal:content="list_title">List Title</h3>
<table>
<tal:block tal:repeat="batch python:zip(*[the_list[n::2] for n in range(2)])+[the_list[len(the_list)-len(the_list) % 2:]]">
<tr >
<tal:block tal:repeat="obj batch">
<tal:block tal:define="url python:obj.absolute_url()">
<td style="vertical-align: top;">
<a href="#"
tal:attributes="href url">
<img tal:condition="python:hasattr(obj,'photo_small') and obj.photo_small"
tal:replace="structure python:hasattr(obj,'photo_small') and obj.photo_small and obj.photo_small.tag()" />
<img tal:condition="python:not (hasattr(obj,'photo_small') and obj.photo_small)"
src="iconplaceholder.png"/></a>
</td>
<td style="vertical-align: top;">
<h5><a href="#"
tal:attributes="href url"
tal:content="obj/title_or_id" /></h5>
<span tal:content="python:obj.getShort_description() or ' '"> description </span><br/>
<span tal:content="obj/getSKUCode | string:" /><br/>
<div tal:content="obj/getPrice | string:" />
<a tal:condition="obj/isPurchaseableProduct | nothing"
tal:attributes="href python:obj.absolute_url()+'/addtocart'">Add to Cart</a>
<br />
<a href="#"
tal:attributes="href url"
i18n:translate="more...">
more ...
</a>
</td>
</tal:block>
</tal:block>
</tr>
</tal:block>
</table>
</div>
And that's it! Now, you can go to a folder with SimpleItems and AdvancedItems, add a DynamicPage as the default document and put a little folder blurb into the document part of the DynamicPage. In the list tab, choose a list that shows only this folder, only SimpleItems and AdvancedItems, and with a style of "mall2columns". You'll get a nicely formatted view on your items, ready for a simple shop setup.
A word of warning: This newly added list style is designed to pick up attributes of mall items, so if you do not restrict the "types to view" to those items, it will probably give you AttributeErrors.
But what about subfolders and subcategories?
Warning: The concept outlined below is not really intuitive, so please be careful when using it. You need to plan out your keywords to make it work.
Of course, nowhere in this whole setup do we allow for a nice category-like subfolder view. Since we want the subfolder view to be similar to our item view, we want some nice photos to go with it. However, Plone folders do not show a photo. To get around this, we create a SimpleItem in the subfolder and tag it with a keyword such as "mallfolderlevel2". We then need to tag all the normal purchaseable items in the folder with a different keyword such as "mallitemlevel2" and restrict the folder view list to only show those items with that keyword. We can then create a second list showing all items with a keyword "mallfolderlevel2" in the folder and subfolders. A diagram to show a suggested keyword scheme (keywords in brackets):
main folder |-item 1 (mallitemlevel1) |-item 2 (mallitemlevel1) |-item 3 (mallcatlevel1) |-subfolder 1 | |-item 3 (mallitemlevel2) | |-item 4 (mallitemlevel2) | |-item 5 (mallcatlevel2)
To make this work, we also need to create another list view macro that will show the item, but the clickable link needs to take us to the containing folder, not to the item itself. Here is the code:
<div metal:define-macro="mall2colcategories"
tal:define="list_style python:here.get_list_styles(list_name);
width python:list_style[0];
clear python:list_style[1];
padding_right python:list_style[2];"
tal:attributes="style string:margin: 0px;;
margin-bottom: 0ex;;
padding: 0px;;
padding-right: ${padding_right}%;;
width: ${width}%;;
float: left;;
clear: ${clear};;"
>
<h3 style="padding-bottom: 4px;"
tal:condition="list_title"
tal:content="list_title">List Title</h3>
<table>
<tal:block tal:repeat="batch python:zip(*[the_list[n::2] for n in range(2)])+[the_list[len(the_list)-len(the_list) % 2:]]">
<tr >
<tal:block tal:repeat="obj batch">
<tal:block tal:define="url python:'/'.join(obj.absolute_url().split('/')[:-1])+'/'">
<td style="vertical-align: top;">
<a href="#"
tal:attributes="href url">
<img tal:condition="python:hasattr(obj,'photo_small') and obj.photo_small"
tal:replace="structure python:hasattr(obj,'photo_small') and obj.photo_small and obj.photo_small.tag()" />
<img tal:condition="python:not (hasattr(obj,'photo_small') and obj.photo_small)"
src="iconplaceholder.png"/></a>
</td>
<td style="vertical-align: top;">
<h5><a href="#"
tal:attributes="href url"
tal:content="obj/title_or_id" /></h5>
<span tal:content="python:obj.getShort_description() or ' '"> description </span><br/>
<a href="#"
tal:attributes="href url"
i18n:translate="more...">
more ...
</a>
</td>
</tal:block>
</tal:block>
</tr>
</tal:block>
</table>
</div>
Some further ideas:
- Products related to content
- By replacing normal pages in your site with mxmDynamicPages and using keywords, you can easily integrate products applicable to the page content right on the page. This is a great selling tool.
- Workflow
- I haven't really looked into this in any depth, but a DynamicPage allows you to only view Published content, and should thus also filter out content before the effective date and after the expiry date. This allows you to use the normal workflow tools to create limited-time specials.
To-do list:
- Code cleanup
- There are some things in here with no right to be there. This is because of the copy/paste method of coding. I'll clean it up later. Make it work, make it fast, make it right. I'm at step one.
- Batching
- Currently, the Plone batching mechanisms are used to provide 2 columns for the tables, but a large amount of objects would make it necessary to have overall batching built into the macro.
- Better styling
- Currently, the list views have some inline styles. It would be much better to give them a couple of "class" attributes and put the styles into a stylesheet.
- Anything else
- Please email me at jbeyers at juizi dot com if you can think of more uses. I will not be surprised to see that I missed something, as the combination is so flexible that almost anything is possible.
And lastly:
If you want this type of functionality and want someone to help set it up, let me know! I don't claim to be a guru, but my past mistakes could save you some time and money ;-)
I really hope this howto is of use to you, even if only as a starting point for further development.
Johan