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.
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:
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:
- 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.
- 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.
- 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.
- 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:
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.