HOW TO Create Portlets with Plomino

Eric BREHAULT edited this page Sep 23, 2016 · 1 revision

It is possible to create context-aware portlets that use any of the fields and documents in our Plomino database in interesting ways. Portlets provided by Plomino.

When Plomino is installed, two new portlets show up in Plone's portlet managers: Plomino design portlet, and Plomino element portlet. The first portlet is always visible when we are in the context of a Plomino database, or any of its children. Indeed, it is assigned to the PlominoDatabase content type, as you can verify by going to the view @@manage-content-type-portlets?key=PlominoDatabase. The second type of portlet, Plomino element portlet, is one that offers us immense flexibility in the way we can integrate data from any Plomino database with the rest of the Plone site. See the documentation for more info on this type of portlet. In the following How-To we are going to explore some ways to put the Plomino element portlet to use.

The main steps

A Plomino element portlet needs a form to display its contents, and more specifically, a "page form", i.e. a form with the Page checkbox selected, which can not be saved and does not provide an action toolbar. The layout of this "page" dictates what the body of the portlet will contain. If we do not add any Plomino fields to the layout, we can basically reproduce Plone's default static portlet. For example:

  • Create a new form, call it frm_portlet_static_example. Give it any title you want.
  • In the Form layout wysiwyg editor, enter and format any text you want to display in the portlet.
  • Scroll down and check the box for Page.
  • Save
  • Navigate to the section of the Plone site where you want your portlet to appear, keeping in mind the normal portlet inheritance mechanism.
  • Click Manage portlets and add a Plomino element portlet.
  • Enter a Portlet header. This will be displayed as the title of the portlet. The title of the "page form" created in step 1 is not used by the portlet.
  • Enter the Database path. You can make use of Zope's acquisition and just enter the id of the database, instead of an absolute path.
  • Enter the Element ID you gave the form in step 1 (frm_portlet_static_example).
  • Save and return to the page you started from in step 5.
  • Congratulations! You just created a static portlet, even though it took many more steps than it would have taken for a standard Plone static portlet. Needless to say, we do not recommend this for regular portlets that don't use any Plomino data. However, the above recipe is the basis that you will follow for more sophisticated portlets. We will only add more details to step 2, the rest stays the same.

Showing a search in a Plomino element portlet

Now we will create a portlet that returns the first 10 results from a search.

For this example, we assume that the documents you search for are generated by a form named frmProject; furthermore, we assume that this form has a field called project_name. Adjust as necessary to fit your application. Go back to the root of your Plomino database. Create a new form and call it portlet_all_names. Give this new form any title you want. In the Form layout wysiwyg editor, in addition to any static formatted text you may want to show, enter the id of a new field, called display_html.

Scroll down and check the box for Page.

Save, and go back to Edit the same form. (This is so when that we create the new display_html field, the form already exists.) In the wysiwyg editor, select the display_html id, and click the Add/edit a Plomino Field toolbar button. Under Edit Field Properties, select Rich Text for the Field type. This is important if we want to use HTML markup in our dynamic portlet content, instead of just plain text.

For the Field mode, select Computed for display. This is because we don't want to save any data with this form, we just want to show information generated on the fly.

For the Formula, use the following code, taking care of replacing frmProject with the name of the main form of your application, and project_name with the name of the field you want to display in your results:

results = plominoDocument.getParentDatabase().getIndex().dbsearch({"Form": "frmProject"})
plominoDocument.plone_log("My results:"+str(len(results)))
names = ["<p>" + obj.project_name + "</p>" for obj in results][:10]
return ''.join(names)

Now save and go back to the portlet_all_names form. It should show the first 10 names of the documents created by the form whose name you entered in line 1 of the above code snippet.

You are now ready to create a new portlet, or you can edit the portlet you created in the previous example. You just need to edit the Portlet header and the Element ID, if the database is still the same. TheElement ID will take the value portlet_all_names.

How does this work? You added a display_html field in the page form's layout.

This field is rich text and computed on display.

The formula that computes this value searches for all documents in your database that were created by a particular form, and then iterates over the resulting list, picks just the name field out of each document, and limits the list to just the first 10.

It wraps each name inside an HTML paragraph tag, and unpacks the list into a single string, which it returns.

Also, it logs the overall count of how many results the search found, so if you are running your Zope instance in the foreground, you can see a log entry like the following:

012-10-09 00:13:47 INFO Plone Debug: My results:402

An example using AJAX calls in a Plomino element portlet

We can now build on the previous examples to show how, simply changing the formula that computes the display_html field, we can extract weather forecast information from the response to an AJAX call.

The location for which we display the weather forecast could be a field of the current Plomino document, or we could do a search for a Plomino document that has some kind of relationship with the current context, and read the location name from it.

This is the code for display_html:

# First, we identify the current document for which the portlet is being displayed.
# The context of a portlet is always the PlominoForm object that renders the portlet,
# so we need to look at the HTTP request to determine the actual context.
url = context.REQUEST.get('ACTUAL_URL')
docid = url.split('/')[-1]
db = context.getParentDatabase()
doc = db.getDocument(docid)
if not doc:
  return ""

# Now that we have a Plomino document, we can look up the "city" item, if it exists.
city = doc.getItem("city")
if not city:
  return "No city"

# Finally we make our AJAX call to retrieve the weather forecast, and we return some
# suitable HTML to the portlet's form.
html="""
<script>
$(document).ready(function() {
    url = "http://query.yahooapis.com/v1/public/yql?" +
    "q=select%20*%20from%20weather.forecast%20where%20woeid%20in" +
    "%20(select%20woeid%20from%20geo.placefinder%20where%20text%3D%22""" \
    + city + """%22)&format=json"
    $.getJSON(url, '', 
          function(data) {
                var forecast = data.query.results.channel.item.forecast[1];
                var city_name  = data.query.results.channel.location.city;
                var image_src = "http://l.yimg.com/a/i/us/we/52/"+forecast.code+".gif";
                $("#weather_div").html("<h2>"+city_name+"</h2><img src='" 
                                       + image_src + "'/><BR/>"+forecast.text)
          });
});
</script><div id="weather_div"></div>
"""
return html

Conditionally hiding a portlet

Obviously, just like any other portlet, the previous portlet can be assigned to any piece of content anywhere on the site. However, it will simply display the header with an empty portlet body if it is on any page other than a Plomino document.

At least on a Plomino document it will display the text "No city", if the document itself does not contain a "city" item.

It is possible to devise a formula that returns either True or False and use it to display the portlet conditionally.

To do so, just add a field called Plomino_Portlet_Availability to the portlet layout form.

This field does not need to be part of the layout, so just use the "Add field" menu item, without editing the layout.

Make the field of type Text, and mode Computed for display. Here is an example formula, similar to the logic used in the display_html field above:

url = context.REQUEST.get('ACTUAL_URL')
docid = url.split('/')[-1]
db = context.getParentDatabase()
doc = db.getDocument(docid)
if not doc or not doc.isDocument():
  return False
else:
  return True