• Octopus

last modified October 16, 2007 by nickyg622

What is octopus?

Our system for processing form requests (Ajax and otherwise)

When should I use octopus?

Maybe you should use octopus if you're in opencore and writing a form and a view class to handle it. In particular, think about using it if some of the following circumstances exist:
  • Your form needs to do a bunch of different things depending on how it's submitted (that is, which submit button is clicked)
  • Your form handler needs to be able to act on one of many different items or on a collection of items
  • Your form can be submitted via AJAX but will also function well without javascript
  • You don't like writing a lot of custom javascript code or, indeed, any

How do I use octopus?

There are four distinct components to octopus: javascript code, server code, JSON response syntax, and form markup syntax.

The Javascript code

You shouldn't need to write any custom javascript code. Seriously. I really mean this. It's currently all contained in oc-behaviors.js in opencore.nui.javascripts so as long as that file is included in your document you will be fine.

The server code

You'll need octopus.py and formhandler.py, both in opencore.nui.

Your view class should extend formhandler.OctopoLite. At present, if you're trying to use octopus, you're probably in opencore.nui, so your view class should also extend nui.BaseView first. Note that the order here matters:
class MyView(BaseView, formhandler.OctopoLite):
Your class will have to have a `template` attribute which is intended to replace the ZCML `template` attribute on a view definition. So when defining your view in ZCML, omit the `template` attribute, and instead define the template in the view class's python code. If you need multiple conditional templates, use a property.
  template = ZopeTwoPageTemplateFile('myform.zpt')
For each distinct action which the server might take when the form is submitted, define a method to handle that action and decorate it with a label describing that action. The function needs to take two arguments besides `self`: one which describes the list of items that should be acted on (in a list), and one which describes the fields that should be updated for those items (in a list of dictionaries). The action, targets and fields will all be determined by the parameters of the form submission. Neither the `targets` nor the `fields` is necessary -- you still have access to request.form and can use that directly -- but the arguments have to be there in the signature. Yes, this is stupid.
  @action('delete')
  def delete_items(self, targets, fields)
    ...

  @action('update')
  def update_items(self, targets, fields)
    ...

  @action('resort')
  def resort_items(self, targets, fields)
    ...

So now you can actually write the code in the method that handles the form submission. If you want and if it's applicable, you can use targets and fields to figure out what to do with a bunch of items. Or you can not use them. If you do, they might look something like

> targets
['item1', 'item2']
> fields
[{'name': 'foo', 'color': 'red'},
 {'name': 'bar', 'email': 'bar@example.com'}]
The dictionaries in fields are ordered the same way as the targets, so this might mean that the item identified by 'item1' should have its name set to 'foo' and its color set to 'red', while 'item2' should have its name to 'bar' and email to 'bar@example.com'. You get the idea.

As its return value, each of your methods should return some glitzy AJAX effects for the client to perform, in the form of a dictionary (JSON object) of commands. This return value will only be used for AJAX requests; in a standard request, your method's return value will be ignored and instead the page containing the form (as defined by the class's `template` attribute) will be re-rendered and sent to the client.

JSON response syntax

The JSON response sent by your server method should contain zero or more commands for the client to run to tell it how to manipulate particular sections of the DOM. Each command object is keyed to a particular DOM element by id and contains an action attribute and, optionally, html and effects attributes. For example:

{
 element_id1: {
               action: "append",
               html: "<div>Changes saved</div>",
               effects: "highlight"
              },
 element_id2: {
               action: "delete",
               effects: "fadeout"
              }
}
  • `action` is one of the following strings: append, copy, delete, replace, or (not yet implemented) prepend. For definitions see Specification for Deliverance from which these names were derived.
  • `html` is a string expressing valid HTML (or plain text) with a single root node; it is required with an append, copy, replace or prepend action, and it is ignored with a delete action.
  • `effects` is a string instructing the client to use an effect on the target DOM element; currently supported effects are highlight and (for a copy only) fadein. It is always optional.

Form markup syntax

All octopus behaviors can be correctly applied by using the right markup; all the javascript is inside the JS file.

Some conventions you'll see here:

  • Classes  that trigger javascript are intentionally kept separate from classes we style against.  That way, we don't accidentally add behaviors to an element when all we're looking for is style.  Therefore, all classes that trigger javascript are prefixed with "oc-js-"
  • You can use multiple class names to trigger multiple behaviors / styles.

The following example will illustrate several octopus concepts.  This is the "project contents" table, and in it, you can:

  • Resort the table using Ajax
  • Edit any individual item using Ajax
  • Delete an individual item using Ajax
  • Delete a batch of items, not using Ajax

<form name="pages_contents" action="contents" id="ext-gen24">
  <input type="hidden" value="70454793A3EM8uFdbwA" name="authenticator"/>
  <input type="hidden" value="pages" name="item_type"/>
  <table cellspacing="0" cellpadding="0" class="oc-dataTable">
    <thead id="oc-pages-thead">
      <tr>
        <th class="oc-dataTable-checkBoxColumn">
          <input type="checkbox" class="oc-checkAll" />
        </th>
        <th scope="col">
          <a class="oc-columnSortable oc-selected oc-js-actionLink" href="contents?task|resort=Resort&item_type=pages&sort_by=title&sort_order=ascending">Title ↓</a>
        </th>
        <th scope="col">
          <a class="oc-columnSortable oc-js-actionLink" href="contents?task|resort=Resort&item_type=pages&sort_by=obj_date">Last Modified</a>
        </th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody id="oc-pages-tbody">
      <tr id="streetswiki" class="oc-js-liveEdit">
        <td>
          <input type="checkbox" value="streetswiki" name="deletes:list"/>
        </td>
        <td>
          <div class="oc-js-liveEdit-value" id="ext-gen30">
            <a href="/projects/streets/streetswiki">
              StreetsWiki
            </a>
          </div>
          <div class="oc-js-liveEdit-editForm oc-liveEdit-editForm" style="display: none;">
            <input type="text" value="StreetsWiki" name="streetswiki_title" />
            <input type="submit" name="task|streetswiki|update" value="Save" class="oc-js-actionButton" />
            or
            <a class="oc-js-liveEdit_hideForm" href="">
              Cancel
            </a>
          </div>
        </td>
        <td>
          July 11 by <a href="/people/nickyg622/profile">nickyg622</a>
        </td>
        <td>
          <ul class="oc-actions oc-dataTable-row-actions">
            <li>
              <a class="oc-js-liveEdit_showForm oc-actionLink" href="#">Rename</a></li>
            <li>
              <a href="contents?task|streetswiki|delete=Delete&item_type=pages&authenticator=70454793A3EM8uFdbwA" class="oc-js-actionLink oc-actionLink">Delete</a>
            </li>
          </ul>
        </td>
      </tr>
    </tbody>
  </table>
    <ul class="oc-actions oc-dataTable-actions">
      <li>
        <input type="submit" class="oc-button" value="Delete" name="task|batch_deletes|delete"/>
      </li>
    </ul>
</form>

­


ActionLink
Sends an Ajax request when a link is clicked

  • ActionLinks are not required to be inside of a <form> element
<a class="oc-columnSortable oc-selected oc-js-actionLink" href="contents?task|resort=Resort&item_type=pages&sort_by=title&sort_order=ascending">Title ↓</a>

­

 

ActionSelect
Sends an Ajax request when a select box is changed

  • ActionSelects are assumed to be inside a <form>
  • The markup above is controversial and should probably be reexamined
<div class="oc-js-actionSelect" >
  <select name="opencore_listing">
    <option value="public">Yes</option>
    <option selected="selected" value="private">No</option>
  </select>
  <input type="submit" name="task|opencore|change-listing" value="Go" />
</div

­


ActionButton
Use this to submit a button using Ajax

  • ActionButtons are assumed to be inside a <form> 
<input type="text" value="StreetFilms" name="streetfilms_title" />
<input type="submit" name="task|streetfilms|update" value="Save" class="oc-js-actionButton" />

­

 

What's next for octopus?

See Octopus Tickets and please feel free to give feedback, requests and suggestions on this wiki page or in trac tickets -- remember, it's horrible and ugly and I think I hate it.