Control Flow

Revision: $Id$


Warning: This document is not up to date. Many details are wrong.

This document describes the "flow" in the NuxCPS3Document for the complicated cases, namely data retrieval, document rendering and data editing. It describes it step by step as it happens, in the following order:

  1. Retrieving the data
  2. Displaying a form with the data
  3. Saving the updated data

To make it all somewhat clear (or maybe not) there is the associated control_flow.dia document that outlines what's in here graphically, but in much less detail.

1   Introduction

Each document will have an associated portal_type, defined in the portal_types tool. The type defined will probably be defined by a new type of portal type (instead of like Factory Type Information). The type definition is referred to throughout this document as a template.

2   Retrieving the data

The document's data is accessed through a DataModel object. This object is created by calling the document's makeDataModel() method (This is actually currently done by the Template, but when document-specific schemas are implemented, it will move to the Document). This method creates a DataModel object by fetching all the schemas that are valid for the document and adding them to the DataModel. The schemas can be either local to the document, local to the template, or some sort of global schemas (the exact way of setting up the global schemas have yet to be determined).

A schema is a collection of fields, and the DataModel is basically a temporary collection of schemas, specific for each document. Each schema also has it's own storage settings, that determine where the data for the fields are to be stored. This could be an SQL database, or object properties, or something else. The data is accessed through StorageAdapters which are created by StorageAdapterFactories. Each schema has it's own StorageAdapterFactory with it's own settings.

One StorageAdapter is created for each schema and kept in the DataModel for access to the data. Each StorageAdapter is also called upon to fetch the data. The data is also stored in the DataModel as a data cache so that the external storage isn't called upon each time you make a change.

The makeDataModel() method then returns the new DataModel object.

2.1   Data access

You can access the data in the DataModel in two ways. First, the DataModel has a dictionary type interface, so to get the data for a field you just access the DataModel with datamodel['fieldname'] and the data will be returned. You can also create DataStructures. A DataStructure is a dictionary-type object that is especially designed for usage with forms. It keeps the data rendered into a format that is directly displayable, i.e. a text string (or possibly a number). This is used when rendering documents.

(And it just struck me that if we are to have different format of one field in different Layouts, we need to call the FieldWidget to render, which means another refactoring...)

3   Displaying a form with the data

Displaying a document is done by calling the document's render() method, with a parameter naming the Layout to be used when rendering. Layouts are collections of FieldWidget; each FieldWidget has a corresponding Field that the widget is responsible for rendering.

The document fetches the Layout in question, either locally, if allowed, or from the template, which in turn gets it locally or from the global storage. The document then creates a DataModel (see above) and calls the DataModels makeDataStructure method to create a DataStructure that is then passed to the Layout's render() method.

The Layout will then in turn go through each FieldWidget, pick out the relevant data from the DataStructure and call each FieldWidget's render() method with that data, and with the layouts Renderer.

A Renderer is an object that knows how to render basic graphical elements, such as bits of text, and editboxes. The FieldWidget in turn knows which of these graphical elements it needs in order to render the field. A text field will either be rendered as a string or as an editbox, and the FieldWidget know which, while the renderer in turn knows how to create strings and editboxes. It's important to note here that different layouts may render the same field differently. For example most documents will have both view and edit layouts, one for viewing the document and one for editing it, where the view layout will render almost everything as text, while the edit layout will render everything as HTML fields.

Each Layout will have it's own renderer, depending on which target the layout is created for. There may be a CmfRenderer that uses METAL macros defined in portal_skins to render, and a HtmlRenderer to render into pure HTML, and a PDF renderer to render to PDF-files, and TextRenderer to render into pure text (useful if you want to send document as e-mail, for example) and a StructuredText renderer to make the whole document into a STX file, etc.

So the Layout calls render() on each FieldWidget who in turn calls the methods it needs on the Renderer, such as text() and editbox(). The Renderer returns a text-string that represents the rendering, which is concatenated together, and Layout.render() finally returns the full rendition for display.

4   Saving the update data

The procedure outlined in the previous section is exactly the same no matter whether it is a form or just an HTML page, or for that matter a PDF file. Assuming that it is a form used for updating the document's data, this is the procedure to update the data from this form.

When a document is updated, a DataStructure needs to be updated from the REQUEST object. The DataStructure could be created at this point, but to improve performance it may be beneficial to store the DataStructure as well as the DataModel created when the document form was rendered in a volatile attribute on the document.

The DataStructure will go through it's fields to see if they exist in the REQUEST and update it's data accordingly. It also keeps track of which fields have been changed. After the update, the document passes the updated DataStructure to the DataModel's commitDataStructure method.

CommitDataStructure first validates the data in the DataStructure by calling the fields and validating the data with them. If all Fields validate properly, it updates it's internal storage of data. It uses the DataStructure's modified flags to keep track of which schemas that have been affected by modified data. It then calls it's own _commitData method to commit the data for storage for each affected schema. It does this by using the writeData() method on the StorageAdapter connected to that Schema.

Thats it, I think. Hope this helps.