r24 - 22 Feb 2006 - 08:47:51 - AlecFlettYou are here: OSAF >  Documentation Web  >  DevelopmentHome > DeveloperPlatformProject > ZaoBaoTutorial

This tutorial is obsolete and written for Chandler 0.5!

For an updated version of the tutorial, please see the updated Developer Documentation. There is a version 0.6 version of this tutorial there.

Chandler Extension Tutorial

...starring.. ZaoBao!

Introduction

This tutorial will step you through the creation of a Chandler extension. An extension allows you to develop fully fledged applications that take advantage of the Chandler data-centric model of development.

This document will introduce new concepts and definitions as needed to develop the application shown here. It is not intended to be a definitive reference, but rather a way to get started with Projects. For reference documentation, please see http://chandler.osafoundation.org/docs/ and DeveloperDocumentation

Terminology

There are a few simple concepts that must be understood in order to develop applications for Chandler:
  • The Repository is a single database that stores all information about all data used in Chandler. The database is hierarchical, rather than relational.
  • An Item is the simplest object that can be stored in the Repository. Every piece of user data is stored as an Item.
    • For example, an appointment stored a calendar is an Item.
  • A Kind is the definition of an object. It defines attributes and their types. Any application or extension can define new a new Kind.
    • For example, =CalendarItem= is a Kind that defines a set of attributes such as startTime, endTime, priority and so forth.

All Items are automatically serialized and stored in the Repository. Once they are created, they will persist across all invocations of Chandler until they are explicitly deleted.

ZaoBao

For this tutorial, we will be developing the ZaoBao extension. This is an RSS feed reader that retrieves multiple RSS feeds and displays their contents to the user.

Getting Started

To start developing your own parcel, create a directory called 'samples' and create a subdirectory with the name of your application. Next, set an environment variabled called PARCELPATH to the "samples" directory. Chandler will load all parcel.xml files in subdirectories of PARCELPATH. These parcel.xml files define all data that your application uses.

For example, if you created c:\samples, and put ZaoBao in c:\samples\zaobao. Then you would set PARCELPATH to c:\samples

Note: If you are getting Chandler using CVS, you'll find a good "skeleton" application in the "samples" directory. You can build on this sample by copying the contents of the skeleton directory into your own directory. Alternatively, you can set PARCELPATH to the samples directory in the CVS tree, and build your parcel there.

Data Description

You may be curious and excited to jump write in and write some code. But in the world of Chandler, data is king. The sooner you define your data types, the more easily you can build a user interface around it. Don't worry, you can change your data types later, but you can't develop any meaningful application without at least some scaffolding.

The first thing to begin thinking about is the units of information that your extension will manage. This will help develop your schema. A schema is a description of your data and how the different data items relate to each other.

Here are some questions that may help you get started:

  • What are the base units or items that your application will use?
  • What attributes are stored on those items?
  • Are there many objects of the same type? How are they grouped or organized?
  • Are there relationships between different data items? Does one data item "contain" another one?
When the schema is well defined, then Chandler will be able to easily aggregate one application's data with another. Chandler is very good at managing lists and hierarchies of different kinds of data.

A data schema is defined with XML. The XML contains descriptions of Item types, called Kinds. An application may define one, two, or even a hundred different Kinds, depending on what data it will be managing.

For ZaoBao, there are a few key pieces of data:

  • An RSS feed itself - the data that you download from a webserver. A feed has a title, a description and maybe a opyright. Each RSS feed contains one or more news items. A feed is sometimes called a channel.
  • A news item - this is a single entry retrieved via RSS. News items have the content of the news item, as well as a date, author, URL, and so forth. Not all RSS feeds contain authors or even dates but they all contain content.

Defining the data

We'll start by looking at the guts of the schema parcel.xml and then look at how to load it into Chandler. This file exist as zaobao/schema/parcel.xml.

Let's define a channel (or feed) for ZaoBao:

  <Kind itsName="RSSChannel">
    <displayName>RSS Channel</displayName>

    <!-- define new attributes -->
    <Attribute itsName="url">
      <displayName>URL</displayName>
      <cardinality>single</cardinality>
      <type itemref="String"/>
    </Attribute>
  
    <Attribute itsName="items">
      <displayName>Items</displayName>
      <cardinality>single</cardinality>
      <type itemref="content:ItemCollection"/>
    </Attribute>

    <!-- insert these attributes into the current object -->
    <attributes itemref="doc:RSSChannel/url"/>
    <attributes itemref="doc:RSSChannel/items"/>
  </Kind>

The <Kind> tag indicates that we are defining a new Kind, a Repository data type. We give it a unique name within this file using the itsName attribute, in this case RSSChannel.

The work with the attributes can be confusing* but for now, simply accept that we're defining two attributes on the object, url and items. The items attribute is actually a collection of RSSItem objects. ItemCollections can actually contain many different types of Items but for now we'll assume that they contain a homogenous group of RSSItems.

Behind the scenes: We're defining two attributes with the <Attribute> tag, url and items. The important thing to understand here is that these <Attribute> XML nodes are not actually part of the RSSChannel Kind. Instead the itsName attribute indicates that this attribute is globally defined within this file. It is later referred to using the path doc:RSSChannel/<attrname>.

Now lets define an RSSItem, which will represent a single news item in an RSS feed. This will also be in zaobao/schema/parcel.xml:

  <Kind itsName="RSSItem">
    <superKinds itemref="content:ContentItem"/>
    <displayName>RSS Item</displayName>

    <!-- define new attributes -->
    <Attribute itsName="channel">
      <displayName>Channel</displayName>
      <cardinality>single</cardinality>
      <type itemref="doc:RSSChannel"/>
    </Attribute>

    <Attribute itsName="author">
      <displayName>Author</displayName>
      <cardinality>single</cardinality>
      <type itemref="String"/>
    </Attribute>

    <Attribute itsName="date">
      <displayName>Date</displayName>
      <cardinality>single</cardinality>
      <type itemref="DateTime"/>
    </Attribute>

    <Attribute itsName="content">
      <cardinality>single</cardinality>
      <type itemref="Lob"/>
    </Attribute>
  </Kind>

As you can see, RSSItem contains four attributes: channel, author, date, and content. channel refers back to the original RSSChannel, and content is a Lob - or Large Object. We'll be storing potentially large chunks of HTML in this attribute so we'll use Lob rather than String.

Both of these Kinds derive from ContentItem. This is actually a subKind of the base Item. As you'll see, Items are used all over Chandler and so it is helpful to distinguish user data from other Items with ContentItem.

Behind the scenes: In fact, everything in the Repository is an Item or a new Kind derived from an Item.

Loading data with =parcel.xml

There is a parcel.xml file in the root of each application, as well as in each subdirectory, within the Chandler directory. Chandler reads these files at startup to load data into the Repository.

Parcels can also be loaded from external directories as well by setting the PARCELPATH environment variable to a directory containing parcel.xml files. Chandler will recursively search this directory and load all parcel.xml files found there.

Note: parcel.xml is merely a bootstrapping mechanism to get data loaded into the Repository. The Repository is the primary data store that Chandler uses to keep all user data, schemas, and more. If data that is declared in your parcel.xml is changed within the Repository, those changes will not be serialized back into parcel.xml.

Structure of a typical =parcel.xml file

Let's take a look at the structure of a typical parcel.xml file. This one exists in parcels/osaf/examples/zaobao/schema:

<?xml version="1.0" encoding="iso-8859-1"?>

<Parcel xmlns="http://osafoundation.org/parcels/core"
        xmlns:doc="http://osafoundation.org/parcels/osaf/examples/zaobao/schema"
        xmlns:content="http://osafoundation.org/parcels/osaf/contentmodel"


  <Kind itsName="RSSChannel">
    ... RSSChannel definition ...
  </Kind>

  <Kind itsName="RSSItem">
    ... RSSItem definition ...
  </Kind>
</Parcel>

As you can see above, this file contains the definitions of RSSChannel and RSSItem. This also contains a few other important pieces:

  • <Parcel> tag: This tag is the root XML node in all parcel.xml files. Note that the itsName parameter needs to match the name of the directory that this parcel lives in. [This will hopefully change after 0.5]
  • xmlns attributes: These define XML namespaces. Each parcel has a namespace, and you can refer to any item in another parcel.xml by using the namespace prefix that you chose here.

Note that the xmlns:doc attribute is similar to the path of this parcel.xml file. This allows the current xml document to be self-referential. To refer to items within the current document, (i.e. within the current namespace) attributes or tags must be prefixed with "doc:".

When using parcels loaded from PARCELPATH, the XML namespace for the root of your parcel will be http://osafoundation.org/parcels/<application>. For example, if your PARCELPATH is set to c:\samples\zaobao then the XML namespace will begin with http://osafoundation.org/parcels/zaobao. The namespace for the file c:\samples\zaobao\schema will be http://osafoundation.org/parcels/zaobao/schema.

Note: The use of "doc" to refer to the current document is convention. It can be any arbitrary value. If you define your local namespace with xmlns:foo, then you could refer to items within the current document with "foo:".

Directory Structure

parcel.xml files can contain other kinds of information besides just descriptions of data. They can also contain the data itself, as well as references to Python code. Since Chandler will recursively search for parcels in all subdirectories, it makes sense to break up your definitions among multiple parcel.xml files as appropriate.

Typically an application stores its parcel files as follows:

  • <application>/parcel.xml - meta information about the application
  • <application>/*.py - All Python source files
  • <application>/schema/parcel.xml - Kind definitions
  • <application>/blocks/parcel.xml - User interface items such as menu items, events, widgets, etc
  • <application>/agents/parcel.xml - objects that will run in the background, i.e. "wakeupCallers" (optional)
  • <application>/data/parcel.xml - Application data (optional)

Adding behavior with Python

This data definition is useful, but we probably want to define some behavior on the object itself. We can define methods and implement them using Python. All we need to do is add a <classes> tag to our Kind definition. For example, we might want to be able to call a method, Update() on the channel which updates the RSS info from the server.

Again, looking at zaobao/schema/parcel.xml:

  <Kind itsName="RSSItem">
    <superKinds itemref="content:ContentItem"/>
    <displayName>RSS Item</displayName>
    <classes key="python">osaf.examples.zaobao.RSSData.RSSChannel</classes>
    ... rest of RSSChannel definition ...
  </Kind>

We can then define the corresponding class in the file zaobao/RSSData.py:

import feedparser

class RSSChannel(Item):
    myKindID = None
    myKindPath = "//parcels/osaf/examples/zaobao/schema/RSSChannel"

    def Update(self):
        rssdata = feedparser.parse(self.url)
        for entry in rssdata:
            self.createRSSItem(rssdata)

    def createRSSItem(self, data):
        ... extract data and create an RSSItem ...

The two class attributes, myKindID and myKindPath are special attributes which connect this class back to the original Kind definition in the repository. myKindPath should begin with //parcels and then reflect your application's location in relation to the Chandler directory or PARCELDIR environment variable.

Note: We're using feedparser.py from http://www.feedparser.org/

Running and testing

We've now defined enough information about our data to start chandler and check if our data is being loaded.

The easiest way to look at some of the data in Chandler is to start it with the "-W" flag. This starts the "Repository Viewer" which is a web-based back door into the repository. After starting Chandler, go to the url http://localhost:1888/repo/ and take a look around. You'll find lists of all the Kinds that have been defined. Make sure the Kind you defined above is visible from the front page. Don't procede any further in this tutorial until you've confirmed that your Kind(s) have been defined.

There are a few common pitfalls:

  • Typos in parcel.xml: often you'll get an actual error on startup when this happens.
  • PARCELPATH not set correctly: Make sure PARCELPATH is set to the directory ABOVE your own parcel.xml file.
  • Missing parcel.xml files: If a directory does not contain a parcel.xml, Chandler will not descend into its subdirectories looking for more parcel files. This means that the root directory for your parcel must contain a parcel.xml file, even if it is almost blank.

In addition, you can use the --create command line parameter to start with a fresh repository. You may find that you use --create often when developing a new application.

The Chandler UI

Now that we have defined some of the datatypes that ZaoBao will manage, we will look at how they integrate into the existing user interface. There are many ways to integrate into the Chandler UI, but the simplest (and potentially richest) way is to work within the framework of the Sidebar, Summary View, and Detail View.

The Basic Chandler UI:
Chandler UI

The Sidebar is a list of ItemCollections. An item collection is just a container for Items.

The Summary View is a table that lists the items in the ItemCollection that is currently selected in the Sidebar.

The Detail View displays the Item that is currently selected in the summary view.

ZaoBao's two level hierarchy lends itself well to Chandler's existing user interface. Feeds (RSSChannel) contain News Items (RSSItem) and News Items have details. (including a body, url, author, and so forth)

In the simplest case, an RSSChannel is a collection similar to the All, In, or Out collections. These collections are implemented with the ItemCollection Kind.

Note: An ItemCollection is just that: a generic collection of Items. ItemCollections can contain any kind of Item, including mail messages, calendar events, and or even RSS news items. An ItemCollection is not a folder however, as a single Items can actually be a member of multiple ItemCollections.

Since the Chandler UI understands these basic constructs, we can create an ItemCollection for each RSSChannel and Chandler will do its best to display the members of that collection.

Creating a menu item

First we'll need to create a menu item so that the user can enter a URL for an RSS feed. From there, we'll create an ItemCollection containing the items from that feed. This Itemcollection will appear in the user interface as an entry in the Sidebar.

We'll use the Event system to recieve notification that our menu item has been selected, and that in turn will run some Python code that we write.

We'll add the "Add RSS Channel" menu item to the menu NewItemMenu in the file zaobao/blocks/parcel.xml. To do this, we'll exploit the Repository's "inverseAttribute" capability. We'll declare the menu item and set its parentBlock attribute to point to this item:

This parcel code lives in the "blocks" directory of your application and uses the blocks namespace to define new blocks.

<Parcel 
    xmlns="http://osafoundation.org/parcels/osaf/framework/blocks"
    xmlns:main="http://osafoundation.org/parcels/osaf/views/main" ... >
  ...
  <MenuItem itsName="NewZaoBaoChannel">
    <blockName>NewZaoBaoChannelItem</blockName>
    <title>New ZaoBao Channel</title>
    <event itemref="doc:NewZaoBaoChannelEvent"/>
    <parentBlock itemref="main:NewItemMenu"/>
  </MenuItem>
  ... 
</Parcel>

The Repository will automatically add this menu item to the childrenBlocks attribute of NewMenuItem. Now we need to declare the NewZaoBaoChannelEvent? in this file. The event will direct this event to a specific controller; this controller will handle the event and create the sidebar item.

  <BlockEvent itsName="NewZaoBaoChannelEvent">
    <blockName>NewZaoBaoChannel</blockName>
    <dispatchEnum>SendToBlockByReference</dispatchEnum>
    <destinationBlockReference itemref="doc:ZaoBaoController"/>
    <commitAfterDispatch>True</commitAfterDispatch>
  </BlockEvent>

Next, we will create a reference to the controller, which will allow the event to run our Python code.

  <Block itsName="ZaoBaoController" itemClass="osaf.examples.zaobao.blocks.ZaoBaoController"/>

Finally we write the controller:

class ZaoBaoController(Block.Block):
    def onNewZaoBaoChannelEvent(self, event):
        # for debugging: 
        # print "Creating a new channel"
        RSSData.CreateChannel(self.itsView, Global.views[0])

Note that the method onNewZaoBaoChannelEvent is simply the blockName of the event (NewZaoBaoChannel) prefixed with on and suffixed with Event.

This may seem like a lot of work, but the many levels allow for great flexibility in handling of events. For example:

  • Multiple UI elements may evoke the same Event, such as a regular menu item and a context menu item
  • Events can be directed to specific controllers, or just handled by the default controller in the current view.

The above technique allows each application to have its own controller to handle all of its own events.

Start up Chandler and ensure that your menu item has been created. Try it out to see if your Python code is run.

Getting feeds

RSS channels need to be polled periodically for new data. We'll use WakeupCallers, which are a sort of agent that runs in the background to do work in chandler.

Creating a WakeupCaller

We will declare a special object in our zaobao/agent/parcel.xml that references the code we wish to run.

<Parcel 
    xmlns:wakeupCall="http://osafoundation.org/parcels/osaf/framework/wakeup" 
    ...>

   ...
   <wakeupCall:WakeupCall itsName="ZaoBao">
       <wakeupCallClass>
           osaf.examples.zaobao.ZaoBaoWakeupCall.WakeupCall</wakeupCallClass>
       <callOnStartup>True</callOnStartup>
       <enabled>True</enabled>
       <repeat>True</repeat>
       <delay>00:00:30:00</delay>
   </wakeupCall:WakeupCall>
   ...

The code here actually generates an instance of the WakeupCall object in the repository with the given attributes set.

The tag <wakeupCall:WakeupCall> actually refers to a Kind called WakeupCall that has been declared in the parcel.xml referred to by the given namespace. Lets take a look at this syntax in a little more detail:

           <wakeupCall:WakeupCall itsName="ZaoBao">
            \________/ \________/          \_____/
                 |         |                  |
                 |         |                  |
 xml namespace --+         |                  +-- instance name
                           |
            name of Kind --+

The attribute itsName should be familiar from our earlier Kind definitions. What's different here is that we're now referring to an external Kind (WakeupCall), from an external parcel. (parcels/osaf/framework/wakeup/parcel.xml) By actually using the WakeupCall tag, we're creating an instance of this Kind, rather than defining the Kind itself.

The WakeupCaller system will automatically find all objects in the repository of type WakeupCall and run them as often as they are specified. The above code will run every 30 minutes.

Waking up and retrieving feeds

The connection to your code begins at the wakeupCallClass tag. This tag refers to a Python object which will be instantiated and run at the times specified. The class is referred to with standard Python syntax.

Each time our object is "Woken Up" we'll want to tell each RSSChannel to go update its feed, by calling the Update method we defined above.

We'll connect this WakeupCall Item with some python by creating the class WakeupCall in the file zaobao/ZaoBaoWakeupCall.py:


import osaf.framework.wakeup.WakeupCaller as WakeupCaller
import osaf.examples.zaobao.RSSData as RSSData
from repository.item.Query import KindQuery

class WakeupCall(WakeupCaller.WakeupCall):

    def receiveWakeupCall(self, wakeupCallItem):

        # We need the view for most repository operations
        view = wakeupCallItem.itsView

        # We need the Kind object for RSSChannels
        channelKind = RSSChannel.getKind(view)
        
        # Find all objects with the corresponding kind
        for channel in KindQuery().run([channelKind]):
            channel.Update()

        # We want to commit the changes to the repository
        view.commit()

receiveWakeupCall is a special method that will be called whenever the WakeupCaller activates your class. Lets break down what is happening here:

  1. The view is a contextual object that is needed for many repository operations. The details are not tremendously important, but in general it is available when you need it through some parameter, in this case via wakeupCallItem.
  2. The channelKind object represents the Kind that you defined much earlier. All Items have the class method getKind() which retrieves the Kind object associated with that class.
  3. You can run a simple query for "all objects of a specific kind" using KindQuery. This retrieves all instances of the given Kind, in this case RSSChannel.
  4. Finally, we call Update() on each channel.

Adding a new Collection

We've now hooked up a menu item that retrieves data

Creating a Sidebar item

Now that we've connected the menu item to some python, we need to implement CreateChannel in RSSData.py:

def CreateChannel(repView, cpiaView):
    """
       create the channel, and add it to the sidebar
    """
    url = application.dialogs.Util.promptUser(wx.GetApp().mainFrame, 
                                              "New Channel",
                                              "Enter a URL for the RSSChannel",
                                              "http://")
    # create the channel
    channel = NewChannelFromURL(view=repView, url=url, update=True)

    # Add the channel to the sidebar
    cpiaView.postEventByName('AddToSidebarWithoutCopying', 
                             {'items': [channel.items]})

def NewChannelFromURL(view, url, update = True):
    """
    Create the channel in the repository
    """

    # Since RSSChannel subclasses Item, the constructor for RSSChannel
    # will ensure that the item is created in the repository
    channel = RSSChannel(view)
    channel.url = url

    if update:
        data = feedparser.parse(url)
        channel.Update(data)

    return channel    

The guts of the work is happening after the user is prompted using wxWidgets.

First the RSSChannel object is created and populated with data from the RSS feed.

In the next step, the list of RSSItems is added to the sidebar. channel.items is an ItemCollection that contains the RSSItem objects. This collection is managed by the RSSChannel: items will be added each time the RSS feed is updated. We add this ItemCollection to the sidebar by posting an event to the view. This ItemCollection will appear as a single entry in the Sidebar.

Displaying items in the Summary View

When the user clicks on a collection in the Sidebar, they summary view automatically populates with the Items in that collection. In ZaoBao collections the Items are usually RSSItems.

There is a schema that defines which attributes should appear in each column of the sidebar. The default attributes are who, about, and date.

As we have defined it, the RSSItem does not define all of these attributes. This is normal as these are very generic attributes. Typically a Kind will define a series of "redirect" attributes with these names. These "redirect" attributes are like virtual attributes that actually refer to values in other attributes. Typically, about refers to the title of an Item, who refers to an author or creator of an Item, and date refers to some relevant date stored in the item such as the due date or the start date.

In the case of RSSItem, about should redirect to the title of the article, and who should redirect to the author.

We will define these "redirect" attributes in the original definition for the Kind, back in zaobao/schema/parcel.xml:

<Kind itsName="RSSItem">
    ... definition for RSSItem ...

    <Attribute itsName="about">
        <redirectTo value="displayName"/>
    </Attribute>

    <Attribute itsName="who">
        <redirectTo value="author"/>
    </Attribute>

</Kind>

This is all the code that is required to display items in this list. Chandler will take care of all the rest.

Displaying an item's detail

When the user clicks on an item in the Summary View, the Detail View will display details about that particular item. Similar to the summary view, the Detail View knows about certain attributes and can display them automatically. Unlike the Summary View, each Kind can display a different set of attributes in the Detail View, in its own unique way.

In the case of an RSSItem, the body of the item is typically the most important. You may also want the user to see other attributes such as the category, and URL for the expanded article.

A Kind can define the user interface for the Detail View by providing a "Trunk Subtree" which defines a list of user interface elements to display. Since we're defining new user interface, we'll use the "blocks" parcel in zaobao/blocks/parcel.xml

<Parcel ...
        xmlns:detail="http://osafoundation.org/parcels/osaf/framework/blocks/detail"
        ... >

<detail:DetailTrunkSubtree>
  <!-- this DetailTrunkSubtree is for RSSItems -->
  <key itemref="zbSchema:RSSItem"/>

  <!-- define UI Elements -->
  <rootBlocks itemref="detail:MarkupBar"/>
  <rootBlocks itemref="doc:LinkArea"/>
  <rootBlocks itemref="doc:AuthorArea"/>
  <rootBlocks itemref="doc:CategoryArea"/>
</detail:DetailTrunkSubtree>

The DetailTrunkSubtree Kind has one key attribute: rootBlocks. This is a list of UI elements that should be displayed for this Kind. This attribute is of list cardinality, so that each time a <rootBocks> tag is encountered, that value is added to the list.

Each rootBlocks attribute above contains an itemref attribute. You may have notice this attribute in earlier definitions. Similar to a simple pointer value in Java or Python, it means that the current attribute should actually have the value of the referenced item. Since there is no such thing as a pointer in XML, we need to refer to each item by name, which may include a namespace such as doc or detail.

Note: You may notice the unique rootBlock here, detail:MarkupBar. This is an example of parcels referencing UI elements (or any other Items) in other parcels. itemref can refer to any element in any parcel as long as the target item has an itsName attribute.

Now that we have set up references to these items, we need to actually define the UI elements themselves. Lets define a simple static text display of the RSSItem's author:

<ContentItemDetail itsName="AuthorArea"
    itemClass="osaf.framework.blocks.detail.Detail.DetailSynchronizedLabeledTextAttributeBlock">
    <position>0</position>
    <selectedItemsAttribute>author</selectedItemsAttribute>
    <childrenBlocks itemref="doc:AuthorLabel"/>
    <childrenBlocks itemref="doc:AuthorAttribute"/>
    <stretchFactor>0</stretchFactor>
</ContentItemDetail>

<StaticText itsName="AuthorLabel" 
    itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttributeLabel">
    <title>author</title>
    <characterStyle itemref="detail:LabelStyle"/>
    <stretchFactor>0.0</stretchFactor>
    <textAlignmentEnum>Right</textAlignmentEnum>
    <minimumSize>70, 24</minimumSize>
    <border>0.0, 0.0, 0.0, 5.0</border>
</StaticText>

<StaticText itsName="AuthorAttribute" 
    itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttribute">
    <title>author</title>
    <characterStyle itemref="detail:LabelStyle"/>
    <stretchFactor>0.0</stretchFactor>
    <textAlignmentEnum>Left</textAlignmentEnum>
</StaticText>

Here we are defining a simple static text area with a Label and the value of the current attribute. For instance, this might look like:

Author timothy

We're again using itemref to reference other UI elements within the current document. We use some well-defined UI elements like <StaticText> but we assign special behavior to them by controlling them with special Python using the itemClass tag. These classes will ensure that the UI stays up to date as the user clicks on different items in the summary view.

Note: In future versions of chandler, some of the behavioral work will be done automatically with special UI elements called Attribute Editors.

Similar XML is used to describe the rest of the items in the Detail View, including the HTML body of the RSSItem. Chandler will handle all updates to the Detail View and even allow the user to edit certain items.

Conclusion and Next Steps

In this tutorial, you've learned how to:

  • Define new data types
  • Populate the repository with new data
  • Display that data in the Sidebar, Summary View, and Detail View

The next step is to spend some time developing your own data types and application behavior. The tutorial will get you some basic functionality out of your data but Chandler's true potential comes to light when you begin to explore some of its more advanced capabilities. When items have been properly defined, many of these capabilities "just work" for your new Kind.

SharingGlossary?

Chandler has a built in infrastructure for sharing individual Items or entire ItemCollections through WebDAV. Users can their data with others and allow them to collaborate and make changes to these items. Chandler will try to keep these items in sync.

Stamping and dynamic types

Chandler leverages Python's unique typing to allow Items to take on attributes and behavior of multiple items at runtime. For example, you could turn an RSS news item as created above into a mail message to send the news with your collegues. Or you can put a mail message on your calendar by just "stamping" it as an event.
Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r24 < r23 < r22 < r21 < r20 | More topic actions
 
Open Source Applications Foundation
Except where otherwise noted, this site and its content are licensed by OSAF under an Creative Commons License, Attribution Only 3.0.
See list of page contributors for attributions.