blog

Slides and Video for my talk on XMPP+Plone at Plone Conf 2012

| categories: backbone.js, xmpp, javascript, ploneconf2012, strophe.js, plone

The video of the talk I gave at in Arnhem at the 2012 Plone Conf has been uploaded to Youtube. Check it out if you are interested in integrating XMPP with Plone.

Thanks a lot to the person who uploaded this video! After seeing Cillian de Roiste spending much of his free time uploading the videos of the Munich Plone Konf, I realise how much effort this is!

Click here to view all the Plone Conf 2012 videos uploaded so far.

Follow-up info on XMPP-stuff after the conf

In the talk, I mention collaborative editing and showed demos of it (with TinyMCE). Unfortunately soon afterwards I came upon a significant flaw in the current approach. If you are interested in the collaborative editing possibilities for Plone, please read Collaborative editing of HTML with TinyMCE not going to happen soon.

Also since this talk, I've been quite busy improving collective.xmpp.chat to get it production ready. I've made an initial release to Pypi, which was actually supposed to be a 0.1 alpha release (but which I messed up somehow). Please consider collective.xmpp.chat to still be alpha software and not yet production ready! The most notable issue at the moment is poor support for having multiple tabs open. I'm aware of this and will work on a solution soon. Update: Fixed in the latest release: http://plone.org/products/collective.xmpp.chat/releases/0.2a2

I'm also in the process of extracting the Javascript files from collective.xmpp.chat and putting them in their own repository, called converse.js. The idea behind this is to completely decouple the JS from Plone and in doing so allow one to implement/deploy something like collective.xmpp.chat for other non-Plone backends.

That's it for now. Thanks for the support and encouragement from everyone in the Plone community. You guys rock!

Real-time, collaborative applications in Plone by jcbrand


Collaborative editing of HTML with TinyMCE not going to happen soon

| categories: ploneconf2012, xmpp, tinymce, plone

TL:DR: Collaborative editing doesn't work well with XML/HTML. Right now, the solution seems to be to use Markdown instead.

During my talk on XMPP applications in Plone, someone asked whether I was aware of any issues with regards to collaborative editing in TinyMCE.

I don't exactly remember my reply, but it came down to "no", I wasn't aware of any problems with it. Just the next day however, at the sprints, I encountered a fundamental problem with regards to collaborative editing of Rich Text through TinyMCE.

The problem lies in the fact that the Diff-Match-Patch is meant for plain text only. It has no concept of structured content such as the a DOM-tree and can therefore create problems when one tries to patch HTML content. It seems that mainly two things can happen. Firstly, because your HTML nodes are not atomic, they can be broken by the patching algorithm inserting text inside them. Secondly, the patching can result in HTML nodes appearing in the wrong order. (See what the author of Diff-Match-Patch had to say about it on the project page and his excellent explanation on stackoverflow).

Ideally one should use a patching algorithm that is tree-based and which will maintain the integrity of the DOM-tree. When using the plain text algorithm, there are however two approaches to try and mitigate the above issues.

  • Strip all HTML tags, diff-match-patch the resulting plain text and re-insert the original tags.
  • Replace all tags with Unicode chars outside of the used range (thereby making them atomic), apply diff-match-patch and then restore all tags.

The problem with the first approach is that your markup is now immutable. So any markup changes a collaborator makes (e.g bold, italics, heading or other formatting) will be discarded. I think this is a dealbreaker and rules out this approach.

The problem with the second approach is that even though your HTML nodes are now atomic, they might still appear in the wrong order.

Because of this, I think trying to do collaborative editing via an HTML-based Wysiwyg editor is not the way to go. Instead, a Markdown editor (such as Epic Editor) seems to be the way to go. Markdown is plain text, and therefore doesn't suffer from the problems mentioned above.

I think I have created the impression at the Plone conf that production-ready collaborative editing via TinyMCE will soon be a reality, and for that I would like to apologize.


Instant Messaging for Plone with javascript and XMPP

| categories: backbone.js, xmpp, javascript, screencast, strophe.js, plone

Late last year Yiorgis Gozadinos who at the time was still working for Jarn, released a video showing his work in integrating XMPP (eXtended Messaging and Presence Protocol) into Zope/Plone.

I had some experience in creating a chat client for Plone before and was inspired by the possibility of porting it to XMPP. In the end it wasn't so much of a port but rather a complete rewrite.

Today, I'm relieved and happy that it's finally in a state where I can show it to you. I've been spending most of my free time the last two months writing collective.xmpp.chat.

It's been both frustrating and fun (mostly fun) and a very valuable learning experience. Like that one week where I rewrote the entire codebase to make use of backbone.js. Aaah, fun times.

Some features already implemented:

Single user chat Roster support (adding/removing contacts) (XEP-0144) Setting presence status (online, offline, away, busy) Custom status messages Multi-user chatrooms (XEP-0045) Chat windows stay open upon page reload Offline storage of messages using HTML5 localstorage (still needs to be secured) Message actions (/me) Setting the topic of a chatroom (/topic)

Because almost all the code is written in Javascript (converse.js is about 1500 LOC), you can even stop the Zope server and still continue to chat. Unfortunately I forgot to show this in the screencast.

If anyone has any questions about the code, how to install, configure and use it, please don't hesitate to contact me. The add-on includes a buildout.cfg file that will set up Zope/Plone, Nginx and ejabberd for you. But please beware, the code is still in a pre-alpha state.

And then lastly, I plan to give a talk at the upcoming Plone Conference in Arnhem. This talk will focus on collective.xmpp.chat, the underlying technologies and how to write XMPP-enabled add-ons.

If you consider this an interesting topic, please go and vote for this talk to ensure that it gets accepted.

Screencast

Note: If you have trouble reading the test, click on the gear icon and set the video to High Definition.


Plone and Dexterity: Enable behaviors per content type instance

| categories: zope, dexterity, adapters, plone, behavior

plone.behaviors are great, but because they are stored in the FTI, they are enabled for all instances of a content type. This blogpost will show how you can enable a plone.behavior for a specific instance.

Note

This blogpost assumes that you are already familiar with a lot of the Zope/Plone programming technologies and concepts. Every time I mention a specific add-on or technology, I've added a link to documentation.

Note

UPDATE: There is now a add-on for Plone based upon the approach taken in this blog post, please see: collective.instancebehavior.

Behaviors, provided by the plone.behavior package, provide a very useful way of extending the functionality of a Dexterity content type. Unfortunately, the fact that a content type's behaviors are stored in its factory contents information (FTI) inside the portal_types tool, means that they are class (or type) specific, and not instance specific. This means that behaviors can only be enabled globally, in other words only for all the instances of a specific type.

So what can we do when we want to enable behaviors only for a specific instance of a content type? At Syslab we for example had a client who wanted a basic folderish Workspace content type, that can be extended into a more featureful custom Project type.

To enable per instance behaviors, we would need a way to store those behaviors on the instance. I decided to use annotations for this purpose. Additionally we must create our own IBehaviorAssignable adapter that overrides the default one to not only look for an object's FTI registered annotations, but also look for the behaviors stored in the current instance's annotations.

I created this adapter in a module named behavior.py:

from zope.annotation import IAnnotations
from zope.component import adapts, queryUtility

from plone.behavior.interfaces import IBehavior
from plone.dexterity.behavior import DexterityBehaviorAssignable

from myproject.types.workspace import IWorkspace
from myproject.types.config import INSTANCE_BEHAVIORS_KEY as KEY

class DexterityInstanceBehaviorAssignable(DexterityBehaviorAssignable):
    """ Support per instance specification of plone.behavior behaviors
    """
    adapts(IWorkspace)

    def __init__(self, context):
        super(DexterityInstanceBehaviorAssignable, self).__init__(context)
        annotations = IAnnotations(context)
        self.instance_behaviors = annotations.get(KEY, ())

    def enumerateBehaviors(self):
        self.behaviors = self.fti.behaviors + self.instance_behaviors
        for name in self.behaviors:
            behavior = queryUtility(IBehavior, name=name)
            if behavior is not None:
                yield behavior

Now register this adapter via ZCML in configure.zcml:

<adapter factory=".behavior.DexterityInstanceBehaviorAssignable" />

We know what we want to use as a storing mechanism (Annotations) for per instance behaviors, and we have a custom adapter that knows to look for them, but we still need a way to save new behaviors to a specific instance's annotations.

In my case, I wanted to extend the Workspace content type with a Project behavior that provides additional fields. Refer to my blogpost on schema-extending a dexterity type for an explanation on how to create such a custom behavior. This was however only my specific use-case, and any behavior can be applied in this way.

To provide a mechanism for saving new per instance behaviors, I created a browser view @@enable_project, which can be called on any Workspace instance. When it gets called, my custom behavior (with full path myproduct.types.project.IProject) gets saved in the annotations of the specific Workspace.

class EnableProject(grok.View):
    grok.context(IWorkspace)
    grok.name('enable_project')
    grok.require('cmf.ModifyPortalContent')

    def render(self):
        context = aq_inner(self.context)
        annotations = IAnnotations(context)
        instance_behaviors = annotations.get(KEY, ())
        instance_behaviors += ('myprodcouct.types.project.IProject',)
        annotations[KEY] = instance_behaviors

        IStatusMessage(self.request).add(
            _(u"This Workpace has now been turned into a Project."),
            u"info")
        return self.request.RESPONSE.redirect(
                '/'.join(context.getPhysicalPath()))

You may want to provide a UI mechanism for enabling per instance behaviors. I decided to do this by adding a new object_buttons action in the actions.xml Generic Setup file:

<?xml version="1.0"?>
<object name="portal_actions" meta_type="Plone Actions Tool"
   xmlns:i18n="http://xml.zope.org/namespaces/i18n">
 <object name="object_buttons" meta_type="CMF Action Category">
  <property name="title"></property>
  <object name="enable_project" meta_type="CMF Action" i18n:domain="plone">
   <property name="title" i18n:translate="">Enable project</property>
   <property name="description" i18n:translate=""></property>
   <property
      name="url_expr">string:$object_url/@@enable_project</property>
   <property
      name="available_expr">python:context.portal_type == 'myproject.types.workspace' and not context.is_project() or False</property>
   <property name="permissions">
    <element value="Modify portal content"/>
   </property>
   <property name="visible">True</property>
  </object>
 </object>
</object>

This provides a convenient way (via the actions dropdown) for the end user to turn a Workspace into a Project.


The difference between BrowserSkins and BrowserLayers

| categories: grok, browserlayer, zca, zope, plone, skinlayer

In this blogpost I'd like to shed some light on two concepts that sound similar and can be quite ambiguous and confusing when first encountered.

When we register certain Zope Components (via ZCML or Grok), we can specify a layer attribute for which we would provide an Interface. This will restrict these components to be available only when that Interface is provided by the Request object.

In ZCML it looks like this:

<browser:page
    name="foo"
    for="*"
    template="my-template.pt"
    permission="zope.Public"
    layer=".interfaces.IMyLayer"
/>

And with Five.Grok:

class Foo(BrowserView):
    """ """
    grok.template("my-template")
    grok.name("foo")
    grok.layer(interfaces.IMyLayer)

How would the Request provide an interface? Well, with the Zope Component Architecture, you can make any object provide any interface, by using the alsoProvides method. In this way we can use interfaces as markers, to mark or identify certain objects.

In Plone this is very handy, because it allows us to only enable or disable certain functionality, by registering it against a specific Interface and then marking the Request or not. This "marking" of the request is done when your Plone add-on product is installed or uninstalled in the configuration panel.

How this is done, depends on two different implementations, one in plone.theme (called BrowserSkins), and one in plone.browserlayer (called BrowserLayers).

Both approaches do the same thing, they mark the Request with an Interface, but they way they do it differs, and that can be bewildering to the uninitiated.

BrowserSkins

plone.theme provides a way mark the request with an interface when it's associated with a skin installed in the portal_skins tool. Plone 4 comes with two skins, the Classic skin and the newer Sunburst. In other frameworks or environments, a skin could be called a theme.

Lets pretend we were creating a skin with a new egg product called collective.kickasstheme. If you've ever generated a Plone theme package with PasteScript and ZopeSkel, then you've now doubt seen the following.

In collective.kickasstheme.browser.interfaces.py we have this interface declared:

from plone.theme.interfaces import IDefaultPloneLayer

class IThemeSpecific(IDefaultPloneLayer):
    """Marker interface that defines a Zope 3 browser layer.
       If you need to register a viewlet only for the
       "Custom Theme" theme, this interface must be its layer
       (in kickasstheme/viewlets/configure.zcml).
    """

Registered with this ZCML:

<interface
  interface=".interfaces.IThemeSpecific"
  type="zope.publisher.interfaces.browser.IBrowserSkinType"
  name="Kickass Theme"
  />

And then with browser resources registered against it, for example:

 <browser:resourceDirectory
   name="collective.kickasstheme.images"
   directory="images"
   layer=".interfaces.IThemeSpecific"
   />

<browser:viewlet
   name="collective.someviewlet"
   manager="plone.app.layout.viewlets.interfaces.IPortalFooter"
   class=".viewlets.MyViewlet"
   layer=".interfaces.IThemeSpecific"
   permission="zope2.View"
   />

The skin is installed with GenericSetup, so we need to inform it about it in collective/kickasstheme/profiles/default/skins.xml

<object name="portal_skins"
        allow_any="False"
        cookie_persistence="False"
        default_skin="Kickass Theme">

    <-- Here comes a list of registered folders that are registered in portal_skins -->

</object>

plone.theme registers an event subscriber (also known as an event listener) for the IBeforeTraverseEvent. In this subscriber, it queries for the BrowserSkin that is installed with the current Skin in portal_skins and then uses directlyProvides, to mark the Request object with this BrowserSkin. If our skin/theme product is installed, then the IThemeSpecific interface declared in our egg collective.kickasstheme will be the BrowserLayer and will mark the Request.

You can see the code for yourself in plone/theme/layer.py and plone/theme/configure.zcml.

This means that all components (such as BrowserViews, ResourceDirectories, viewlets, etc.) registered against our BrowserSkin will now appear. As soon as we uninstall our theme egg, they will again disappear, because the BrowserSkin is again removed.

Drawbacks

BrowserSkins have two major drawbacks.

  • You can only have one of them active at any time. If you need more than one BrowserSkin (which is just an Interface) on the request (i.e to render portlets registered against another one) you will have to subclass that other BrowserSkin.
  • You also need to create a theme product (with a skin registered in portal_skins) every time you want to create your own BrowserSkin.

So, what do we do if we for example want to create a new viewlet or portlet in a non-theme product, but don't want it to render unless the product is installed?

BrowserLayers

plone.browserlayer was developed to solve this problem.

So, now we can create any egg, it doesn't have to be a theme egg, and the declare inside it our BrowserLayer.

We start off by creating the marker Interface that will be given to the layer attribute:

from zope.interface import Interface
class IMyProductLayer(Interface):
    """Browser layer for this particular product
    """

Then add a browserlayer.xml file to the GenericSetup profile directory, with the following:

<?xml version="1.0"?>
<layers>
    <layer name="my.product"
           interface="my.product.interfaces.IMyProductLayer" />
</layers>

When we now install our egg in Plone's control panel, our BrowserLayer will also added to plone.browserlayer's persistent registry of "installed" layers and applied to the Request object during runtime.

All components and resources registered against this BrowserLayer will be activated and visible. Once we uninstall our egg, they will disappear again.

Benefits:

The benefits of using BrowserLayers are basically the inverse of the drawbacks of using BrowserSkins.

  • Any egg can have a dependency on plone.browserlayers and create BrowserLayers, not just theme eggs.
  • You can have multiple BrowserLayers currently on the Request.

Conclusion

Both BrowserSkins and BrowserLayers allow you to mark and unmark the Request object and thereby switch components on or off. They also allow you to override existing components, by simply registering your custom components against a specific layer. Previously, you had to override them in overrides.zcml and this could only be done once.

BrowserSkins had some drawbacks and BrowserLayers where developed to address these. That said, both are still around and both are still in use.

Lastly, please use them! It's bad practice to register components for Plone sites that are not tied to a layer. If you do. they will be active in the Plone site, regardless of the egg being installed or not, potentially causing havoc when serving multiple Plone sites.


« Previous Page -- Next Page »