Welcome, guest | Sign In | My Account | Store | Cart

Notice! PyPM is being replaced with the ActiveState Platform, which enhances PyPM’s build and deploy capabilities. Create your free Platform account to download ActivePython or customize Python with the packages you require and get automatic updates.

Download
ActivePython
INSTALL>
pypm install collective.multimodeview

How to install collective.multimodeview

  1. Download and install ActivePython
  2. Open Command Prompt
  3. Type pypm install collective.multimodeview
 Python 2.7Python 3.2Python 3.3
Windows (32-bit)
0.1 Available View build log
Windows (64-bit)
0.1 Available View build log
Mac OS X (10.5+)
0.1 Available View build log
Linux (32-bit)
0.1 Available View build log
Linux (64-bit)
0.1 Available View build log
 
License
GPL
Dependencies
Depended by
Lastest release
version 0.1 on Feb 26th, 2011

Introduction

collective.multimode view is a Plone package to ease creation of views (or viewlets) which can be in several states, for example a page containing a form or a guide with several steps.

This products can not be used alone, you need to manually define the pages has you would do usually when creating browser views.

This README will show three simple examples on how to use the product. All samples can be found in the sources in the samples directory.

Samples of views

Sample 1: a simple view with two states

Let's say you want to define a view that displays the conditions to use the site or the engagments you are taking with the data provided by the user.

First we need to define the Python view:

System Message: WARNING/2 (<string>, line 27)

Literal block expected; none found.

from collective.multimodeview.browser import MultiModeView

class Sample1View(MultiModeView): modes = ['conditions', 'data_use'] default_mode = 'conditions' view_name = 'multimodeview_sample1'

'modes' is the list of modes that the view can take. For simple cases, a list is enough. The next samples will show the use of a dictionnary for more complex cases. 'default_mode' is, as you can guess, the mode that will be displayed by default for this page. 'view_name' is the name of the view as defined in the zcml file (we'll see it after). It is needed to be able to define the base url for the page or when using Ajax to fetch the content (mainly for viewlets).

The second step is to define a template for our page:

<html xmlns="http://www.w3.org/1999/xhtml"

System Message: ERROR/3 (<string>, line 47)

Inconsistent literal block quoting.

xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" metal:use-macro="here/main_template/macros/master" xml:lang="en" lang="en" i18n:domain="collective.multimodeview"> <body> <div metal:fill-slot="main"> <div tal:condition="view/is_conditions_mode"> <p>By using this site, you agree on the fact that you will not do stupid things.</p>

<p class="discreet"> <a tal:attributes="href view/data_use_link">See how we use your data</a> </p> </div> <div tal:condition="view/is_data_use_mode"> <p>We will sell your email to all known spam database, we need money.</p>

<p class="discreet"> <a tal:attributes="href view/conditions_link">See the conditions to use the site</a> </p> </div> </div> </body> </html>

With this example, we can see two examples of auto-generated attributes for the multimodeviews. 'is_conditions_mode': provides a boolean telling if the view is in the 'conditions' mode. For each mode you defined, you can use this shortcut ('is_xxx_mode', where 'xxx' is the name defined for you mode). 'conditions_link': provides a link to swtich the page in 'conditions' mode. This can be used for any mode, except if you have a mode called 'make' (it conflicts with the 'make_link' method). If you have a 'make' mode, then you'll have to manually use 'make_link' (that will be described later).

Now you can define your view in the zcml file:

<browser:page

System Message: ERROR/3 (<string>, line 90)

Inconsistent literal block quoting.

for="*" name="multimodeview_sample1" class=".views.Sample1View" template="sample1.pt" permission="zope2.View" />

And that's all, you can now access this view and switch between the two modes.

Now let's go for something a bit more interresting.

Sample 2: playing with forms

The first sample was pretty basic and could have been simply done by using two pages or browser views. The second example will show how to manage some data with a view. We will add some annotations on the portal object (basically a simple list of string). The view will be able to list, add, edit and delete those notes. We consider we have a view called 'multimodeview_notes_sample', that provides an API to list, add, edit and delete notes (see samples/notes_view.py).

As usual, we first define the view:

System Message: WARNING/2 (<string>, line 117)

Literal block expected; none found.

class Sample2View(MultiModeView): """ A view that adds annotations on the portal. """ modes = ['list', 'add', 'delete'] default_mode = 'list' view_name = 'multimodeview_sample2'

@property def notes_view(self): return self.context.restrictedTraverse('@@multimodeview_notes_sample')

def _get_note_id(self): """ Extracts the note_id from the form, cast it to an int. Returns None if there is no corresponding note. """

def _check_add_form(self): if not self.request.form.get('title'): self.errors['title'] = 'You must provide a title'

return True

def _check_edit_form(self): if self._get_note_id() is None: return

return self._check_add_form()

def _check_delete_form(self): return self._get_note_id() is not None

def _process_add_form(self): self.notes_view.add_note(self.request.form.get('title'))

def _process_edit_form(self): self.notes_view.edit_note( self._get_note_id(), self.request.form.get('title'))

def _process_delete_form(self): self.notes_view.delete_note(self._get_note_id())

Like for the previous example, we have defined our list of modes, the default mode and the name of the view. We also defined some helpful functions (see the source for the complete code, I removed it from here to focus on the important part) to manage the notes.

The important functions are _check_xxx_form and _process_xxx_form.

The first one (_check_xxx_form) checks if the form submitted does not contain errors. If an error is found, it is added to the 'errors' dictionnary of the class, as we can see in '_check_add_form' if the title is empty. The method always returns True, except if something wrong hapenned to the form (some fields have not been submitted, or a value that the user can not change in normal use case is wrong). In this case, the method returns 'False' or None. A different message will be shown to the user. We can see an example in '_check_delete_form', which only checks that the note_id provided is correct.

The second one (_process_xxx_form) executes the code for the given mode. It is only called if the corresponding check method returned True and did not find any error. If needed, it can return a 'mode' name so the view switch back to this mode once the form is proceeded. By default, it switches to the default mode.

The second step is to define the template for this view. We first create the div (or whatever else) that is shown by default:

<div tal:condition="view/is_list_mode">
<tal:block tal:define="notes view/notes_view/get_notes;

System Message: ERROR/3 (<string>, line 193)

Inconsistent literal block quoting.

note_exists python: bool([n for n in notes if n])"> <table class="listing" tal:condition="note_exists"> <thead> <tr> <th colspan="3"> Notes </th> </tr> </thead> <tbody> <tal:block tal:repeat="note python: enumerate(notes)"> <tr tal:define="note_id python: note[0]; note_text python: note[1]" tal:condition="note_text"> <td tal:content="note_text" /> <td> <a tal:attributes="href python: view.make_link('edit', {'note_id': note_id})" title="edit this note"> <img tal:attributes="src python: '%s/edit.gif' % context.absolute_url()" alt="edit" /> </a> </td> <td> <a tal:attributes="href python: view.make_link('delete', {'note_id': note_id})" title="delete this note"> <img tal:attributes="src python: '%s/delete_icon.gif' % context.absolute_url()" alt="delete" /> </a> </td> </tr> </tal:block> </tbody> </table>

<p tal:condition="not: note_exists"> You do not have any notes for the moment. </p>

<a tal:attributes="href view/add_link"> Add a new note </a> </tal:block> </div>

In this short sample, we can see the use of the 'make_link' method. We use it to create the link to edit or delete a note. We could not use 'edit_link' or 'delete_link', as we also need to specify the note we want to edit or delete. using view.make_link('edit', {'note_id': note_id}) will generate a link like this: http://..../multimodeview_sample2?mode=edit&note_id=2.

Now let's complete our template with the form to add a note:

<div tal:condition="not: view/is_list_mode">
<form name="manage_notes_form"

System Message: ERROR/3 (<string>, line 249)

Inconsistent literal block quoting.

method="POST" tal:define="notes view/notes_view/get_notes; note_id view/_get_note_id; note_text python: (note_id is not None) and notes[note_id] or '';" tal:attributes="action view/get_form_action"> <tal:block tal:condition="view/is_add_mode"> <div tal:attributes="class python: view.class_for_field('title')"> <label for="title">Title</label> <div class="error_msg" tal:condition="view/errors/title|nothing" tal:content="view/errors/title" /> <input type="text" name="title" tal:attributes="value view/request/form/title | nothing" /> </div>

<span tal:replace="structure view/make_form_extras" />

<input type="submit" name="form_submitted" value="Add note" /> <input type="submit" name="form_cancelled" value="Cancel" /> </tal:block> </form> </div>

In this code we can see a few usefull methods provided by multimodeview:

  • 'view/get_for_action': provides the action that should be used for

System Message: WARNING/2 (<string>, line 281)

Bullet list ends without a blank line; unexpected unindent.

the form.

  • 'view.class_for_field(field)': this methods returns 'field' if

System Message: WARNING/2 (<string>, line 284)

Bullet list ends without a blank line; unexpected unindent.

there is no error found for this field, or 'field error' if an error was found. Those class names are the default ones provided by Archetype, so an error will appear in red with a default Plone theme.

  • 'view/make_form_extras': this method should be used in every form

System Message: WARNING/2 (<string>, line 289)

Bullet list ends without a blank line; unexpected unindent.

in multimode page. It adds some hidden fields such as the mode currently is use.

We can also see some specificities in the form:

  • the method should always be 'POST': if you do not use a 'POST'

System Message: WARNING/2 (<string>, line 295)

Bullet list ends without a blank line; unexpected unindent.

method, the form will not be processed.

  • the submit input to process the form is called 'form_submitted'.
  • the sumbit input to cancel is called 'form_cancelled'. If you use

System Message: WARNING/2 (<string>, line 300)

Bullet list ends without a blank line; unexpected unindent.

other names, the form will not be processed.

We can now complete the template to also be able to manage the 'edit' and 'delete' modes:

<tal:block tal:condition="view/is_edit_mode">
<div tal:attributes="class python: view.class_for_field('title')">
<label for="title">Title</label>
<div class="error_msg"

System Message: ERROR/3 (<string>, line 309)

Inconsistent literal block quoting.

tal:condition="view/errors/title|nothing" tal:content="view/errors/title" /> <input type="text" name="title" tal:attributes="value view/request/form/title | note_text" /> <input type="hidden" name="note_id" tal:attributes="value note_id" /> </div>

<span tal:replace="structure view/make_form_extras" /> <input type="submit" name="form_submitted" value="Edit note" /> <input type="submit" name="form_cancelled" value="Cancel" /> </tal:block>

<tal:block tal:condition="view/is_delete_mode"> <p>Are you sure you want to delete this note ?</p> <p class="discreet" tal:content="note_text" />

<input type="hidden" name="note_id" tal:attributes="value note_id" />

<span tal:replace="structure view/make_form_extras" /> <input type="submit" name="form_submitted" value="Delete note" /> <input type="submit" name="form_cancelled" value="Cancel" /> </tal:block>

Nothing really new in this new code but at least we are now able to manage the notes.

Now that the system is complete, we can see some problems incoming:

  • there is some repetitions in the template code, mainly for the

System Message: WARNING/2 (<string>, line 351)

Bullet list ends without a blank line; unexpected unindent.

submit buttons. The one to cancel could be factorized but the one to process the form has a different name everytime.

  • the messages always say 'Your changes have been saved', whatever

System Message: WARNING/2 (<string>, line 355)

Bullet list ends without a blank line; unexpected unindent.

you do.

Let's improve this quiclky.

Sample 2.1: using a dictionnary for modes

The two problems seen before can be quickly fixed when defining a list of modes with a dictionnary.

Let's define the new view, inheriting from the prevous one:

System Message: WARNING/2 (<string>, line 367)

Literal block expected; none found.

class Sample21View(Sample2View): """ A view that adds annotations on the portal. """ modes = {'list': {}, 'add': {'success_msg': 'The note has been added', 'error_msg': 'Impossible to add a note: please correct the form', 'submit_label': 'Add note'}, 'edit': {'success_msg': 'The note has been edited', 'submit_label': 'Edit note'}, 'delete': {'success_msg': 'The note has been deleted', 'submit_label': 'Delete note'} }

view_name = 'multimodeview_sample21'

As you can see, for each mode, a dictionnary is provided with three values:

  • success_msg: the message displayed when the form is successfuly

System Message: WARNING/2 (<string>, line 386)

Bullet list ends without a blank line; unexpected unindent.

processed.

  • error_msg: the message shown when errors are found in the form.
  • submit_label: the title for the button to submit the form.

Now we can also update our template. The part for listing the notes does not change, we only update the form:

<form name="manage_notes_form"

System Message: ERROR/3 (<string>, line 396)

Inconsistent literal block quoting.

method="POST" tal:define="notes view/notes_view/get_notes; note_id view/_get_note_id; note_text python: (note_id is not None) and notes[note_id] or '';" tal:attributes="action view/get_form_action"> <tal:block tal:condition="view/is_add_mode"> <div tal:attributes="class python: view.class_for_field('title')"> <label for="title">Title</label> <div class="error_msg" tal:condition="view/errors/title|nothing" tal:content="view/errors/title" /> <input type="text" name="title" tal:attributes="value view/request/form/title | nothing" /> </div> </tal:block>

<tal:block tal:condition="view/is_edit_mode"> <div tal:attributes="class python: view.class_for_field('title')"> <label for="title">Title</label> <div class="error_msg" tal:condition="view/errors/title|nothing" tal:content="view/errors/title" /> <input type="text" name="title" tal:attributes="value view/request/form/title | note_text" /> <input type="hidden" name="note_id" tal:attributes="value note_id" /> </div> </tal:block>

<tal:block tal:condition="view/is_delete_mode"> <p>Are you sure you want to delete this note ?</p> <p class="discreet" tal:content="note_text" />

<input type="hidden" name="note_id" tal:attributes="value note_id" /> </tal:block>

<span tal:replace="structure view/make_form_extras" /> </form>

As we can see, this version is much shorter than the previous one. We could even have factorized the input for the title, but this has nothing to see with multimodeview, it is normal Zope/Plone/TAL coding.

The question you may have now is "Where are my input defined ?". It is the view/make_form_extras that creates them. If no label for the submit button is found, it will not show any button. If a label is found, it automatically generates the two submit buttons.

Sample 3: Creating a multi-step form

This last example shows how to handle a form in multiple steps. The method used here is not the best one, as we pass the data from one page to the other using hidden input. It would be better to use session, cookies or even local storage for HTML5 fans, but the goal here is more to shown how to navigate from one mode to another.

As usual, we first define the view:

System Message: WARNING/2 (<string>, line 460)

Literal block expected; none found.

class Sample3View(MultiModeView): modes = {'step1': {'submit_label': 'Go to step 2'}, 'step2': {'submit_label': 'Go to step 3'}, 'step3': {'submit_label': 'Go to step 4'}, 'step4': {'submit_label': 'Go to step 5'}, 'step5': {}}

default_mode = 'step1' view_name = 'multimodeview_sample3'

def check_form(self): return True

def _process_step1_form(self): return 'step2'

def _process_step2_form(self): return 'step3'

def _process_step3_form(self): return 'step4'

def _process_step4_form(self): return 'step5'

def _process_step5_form(self): return 'step5'

@property def cancel_mode(self): mapping = {'step1': 'step1', 'step2': 'step1', 'step3': 'step2', 'step4': 'step3', 'step5': 'step4'} return mapping.get(self.mode)

We have overriden the 'check_form' method so it always returns True (we do not really care about the values here). The _process_xxx_form methods now returns the step to which the user is sent when completing the step. So once the 1st step is done, the second one is displayed and so on.

The 'cancel_mode' attribute has been defined has a property, so the value can change depending on the current mode used by the view. You can also define it has a simple attribute, but in this case it will always return to the same mode when cancelling.

Now we can define a simple template for our view:

<form method="POST"

System Message: ERROR/3 (<string>, line 512)

Inconsistent literal block quoting.

tal:attributes="action view/get_form_action"> <input type="hidden" name="step1_value" tal:attributes="value view/request/form/step1_value|nothing" tal:condition="not: view/is_step1_mode" />

<input type="hidden" name="step2_value" tal:attributes="value view/request/form/step2_value|nothing" tal:condition="not: view/is_step2_mode" />

<input type="hidden" name="step3_value" tal:attributes="value view/request/form/step3_value|nothing" tal:condition="not: view/is_step3_mode" />

<input type="hidden" name="step4_value" tal:attributes="value view/request/form/step4_value|nothing" tal:condition="not: view/is_step4_mode" />

<div class="field" tal:condition="view/is_step1_mode"> <label for="step1">What is your name?</label> <input type="text" name="step1_value" tal:attributes="value view/request/form/step1_value|nothing" /> </div>

<div class="field" tal:condition="view/is_step2_mode"> <label for="step1">What is your quest?</label> <input type="text" name="step2_value" tal:attributes="value view/request/form/step2_value|nothing" /> </div>

<div class="field" tal:condition="view/is_step3_mode"> <label for="step1">What is your favorite color?</label> <input type="text" name="step3_value" tal:attributes="value view/request/form/step3_value|nothing" /> </div>

<div class="field" tal:condition="view/is_step4_mode"> <label for="step1">What is the air-speed velocity of an unladen swallow?</label> <input type="text" name="step4_value" tal:attributes="value view/request/form/step4_value|nothing" /> </div>

<div tal:condition="view/is_step5_mode"> <p>Yer answers to the questions were:</p> <ul> <li>What is your name? <span tal:replace="view/request/form/step1_value|nothing" /></li> <li>What is your quest? <span tal:replace="view/request/form/step2_value|nothing" /></li> <li>What is your favorite color? <span tal:replace="view/request/form/step3_value|nothing" /></li> <li>What is the air-speed velocity of an unladen swallow? <span tal:replace="view/request/form/step4_value|nothing" /></li> </ul> </div>

<span tal:replace="structure view/make_form_extras" /> </form>

As told previously, this code is far from perfect, but shows how easy it is to navigate from one form to the other by returning the next mode in '_process_xxx_form' and overriding the 'cancel_mode' property.

But let's make it cleaner (again).

Sample 3.1: Navigating between mode again

We'll use the same template than for the previous view, but update a few things:

  • the cancel message wil differ in each mode.
  • the cancel mode will be defined in the 'modes' dictionnary
  • the next mode to use will also be defined there.

As previously, we override the 'check_form' to avoid having to define a _check_stepx_form method for each step. We define empty methods to process each step:

System Message: WARNING/2 (<string>, line 601)

Literal block expected; none found.

class Sample31View(MultiModeView): modes = {'step1': {'submit_label': 'Go to step 2', 'cancel_label': 'Cancel', 'success_mode': 'step2', 'cancel_mode': 'step1', 'cancel_msg': 'You can not go back, mwahaha'}}, 'step2': {'submit_label': 'Go to step 3', 'cancel_label': 'Back to step 1', 'success_mode': 'step3', 'cancel_mode': 'step1'}, 'step3': {'submit_label': 'Go to step 4', 'cancel_label': 'Back to step 2', 'success_mode': 'step4', 'cancel_mode': 'step2'}, 'step4': {'submit_label': 'Go to step 5', 'cancel_label': 'Back to step 3', 'success_mode': 'step5', 'cancel_mode': 'step3'}, 'step5': {}}

default_mode = 'step1' view_name = 'multimodeview_sample31'

def check_form(self): return True

def _process_step1_form(self): pass

def _process_step2_form(self): pass

def _process_step3_form(self): pass

def _process_step4_form(self): pass

You might have seen that for step1, we also defined a 'cancel_msg'. This has the same effect than 'success_msg' or 'error_msg' shown in sample 2.1, except it is shown when the user cancels.

Samples with viewlets

There is currently no samples with the viewlets, for the good reason that they work the exact same way than the views, except for two points:

  • the class must inherit

System Message: WARNING/2 (<string>, line 651)

Bullet list ends without a blank line; unexpected unindent.

collective.multimodeview.browser.MultiModeViewlet instead of collective.multimodeview.browser.MultiModeView. - you must define a 'widget_id' attribute for the class, so there is no conflict when processing the form on page that have multiple viewlets defined.

Samples will be added when the automated Ajax version for viewlets will be integrated.

Changelog

0.1 (2011-02-25)
  • added the possibility to define a custom label for the cancel button

System Message: WARNING/2 (<string>, line 667)

Bullet list ends without a blank line; unexpected unindent.

and a custom cancel message for each mode. [vincent]

  • you can now defined the modes to swtich to once form is processed or

System Message: WARNING/2 (<string>, line 670)

Bullet list ends without a blank line; unexpected unindent.

user cancelled in the 'modes' dictionnary. [vincent]

  • added samples + README. [vincent]
  • extracted code from Products.plonehrm. [vincent]

Subscribe to package updates

Last updated Feb 26th, 2011

Download Stats

Last month:1

What does the lock icon mean?

Builds marked with a lock icon are only available via PyPM to users with a current ActivePython Business Edition subscription.

Need custom builds or support?

ActivePython Enterprise Edition guarantees priority access to technical support, indemnification, expert consulting and quality-assured language builds.

Plan on re-distributing ActivePython?

Get re-distribution rights and eliminate legal risks with ActivePython OEM Edition.