r5 - 10 Aug 2005 - 10:14:59 - AlecFlettYou are here: OSAF >  Journal Web  >  ContributorNotes > AlecFlettNotes > XmlForCpia

The basic idea is instead of parcel.xml, we derive a simple hierarchical XML schema that is only for Blocks. These files are loaded in installParcel(). The XML files define multiple arbitrary trees of blocks.

For now, the easiest thing to define is Menus because they are obviously hierarchical.. but you could imagine defining other elements of the UI since the .xml files can contain any arbitrary fragments.

Why do this?

We're moving away from parcel.xml, hopefully eliminating it entirely. The current alternative is to use python itself to make all of our item declarations.

This works very well for most independent objects that don't have complex relationships with other objects. For instance, wakeup callers, WebDAV accounts, and so forth.

But for UI, where the structure is hierarchical and there are lots of "little" items with a hierarchical relationship, it is still very tedious to define stuff in python. In fact in every UI toolkit, its always been possible to use a native language to create UI - and yet in just about every toolkit someone invents a resource-based, declaration-style resource format to make life easier for those writing UI. Further, folks are used to designing ui/presentation with hierarchical XML and HTML.

Lets look at some sample code. We'll take chandler's File menu as an example. The examples below will demonstrate the File, a submenu, and two sub items:

Here's what it would look like in parcel.xml:

ugh, it might not be worth trying to import this into the wiki.

Here's what it would look like in python:


newNoteEvent = \ 
    KindParameterizedEvent.update(parcel, 'NewNote',
                                  blockName='NewNote',
                                  dispatchEnum='SendToBlockByName',
                                  dispatchToBlockName='MainView',
                                  methodName='onNewEvent',
                                  kindParameter=osaf.pim.notes.Note.getKind(parcel.itsView))

newMessageEvent = \  
    KindParameterizedEvent.update(parcel, 'NewMessage',
                                  blockName='NewMessage',
                                  dispatchEnum='SendToBlockByName',
                                  dispatchToBlockName='MainView',
                                  methodName='onNewEvent',
                                  kindParameter=osaf.pim.mail.MailMessage.getKind(parcel.itsView))
Menu.update(parcel, 'FileMenu', 
            blockName='FileMenu',
            title=_("&File"),
            childrenBlocks=[
            Menu.update(parcel, 'NewItemMenu',
                        blockName='NewItemMenu',
                        title=_('New Item'),
                        helpString=_('Create new Content Item'),
                        childrenBlocks=[
                        MenuItem.update(parcel, 'NewNoteItem',
                                        blockName='NewNoteItem',
                                        title=_('Note'),
                                        accel=_('Ctrl+N'),
                                        eventsForNamedLookup=[newNoteEvent],
                                        event=newNoteEvent),
                        MenuItem.update(parcel, 'NewMessageItem',
                                        blockName='NewMessageItem',
                                        title=_('Message'),
                                        accel=_('Ctrl+M'),
                                        eventsForNamedLookup=[newMessageEvent],
                                        event=newMessageEvent),
                        (...etc... with more MenuItems)
                        ]),
           MenuItem.update(...),
           MenuItem.update(...),
           ...
           (...etc... with more MenuItems)
           ])   
                          

Note that temporaries, newNoteEvent and newMessageEvent, have been created so they can be referenced later - even though they have already been declared in the parcel. Also note the amount of redundancy because of the hierarchy:

  • the parcel always has to be passed into the first parameter of update()
  • childrenBlocks is constantly declared, even though it is the only form of hierarchy in this code fragment.
  • The blockName and the repository name are constantly duplicated - they're always the same! (They don't have to be but do we really need that flexibility?)
  • Note the use of _() for pygettext. Developers have to remember which attributes actually need it, and it clutters up what is otherwise a straightforward declaration of attributes.

Here's what it would look like in CPIA XML:

<CPIA>
  <Events>
  <KindParameterizedEvent 
    id="NewNote"
    dispatchToBlockName="MainView"
    methodName="onNewEvent"
    dispatchEnum="SendToBlockByName"
    commitAfterDispatch="True"
    kindParameter="osaf.pim.notes.Note"/>
  <KindParameterizedEvent 
    id="NewMailMessage"
    dispatchToBlockName="MainView"
    methodName="onNewEvent"
    dispatchEnum="SendToBlockByName"
    commitAfterDispatch="True"
    kindParameter="osaf.pim.mail.MailMessage"/>
  </Events>

  <Blocks>
    <Menu 
      id="FileMenu"
      title="&File">
      <Menu 
        id="NewItemMenu"
        helpString="Create a new Content Item"
        title="New Item">
        <MenuItem 
          id="NewNoteItem"
          accel="Ctrl+N"
          helpString="Create a new Note"
          event="#NewNote"
          title="Note">
        </MenuItem>
        <MenuItem 
          id="NewMessageItem"
          accel="Ctrl+M"
          helpString="Create a new Message"
          event="#NewMailMessage"
          title="Message">
        </MenuItem>
        ....etc...
      </Menu>
      <MenuItem ...>
      <MenuItem ...>
      ...etc...
   </Menu>
</Blocks>

And then just one extra line from __init__.py's installParcel():

from osaf.framework.blocks import ResourceLoader

def installParcel(parcel, oldVersion=None)
     ResourceLoader.LoadIntoParcel(parcel, "menus.xml")

Since the parcel is more or less just a small soup in the larger soup of the repository, all the items get loaded relative to the parcel. The "id" attribute is hooked up to the 'name' in the parcel. So you could say

   
    MenuItem.update(parcel, 'FileMenu', foo=bar)

to update the FileMenu? item that was loaded via XML.

Comparison

Here are some of the basic advantages of the XML format over the Python format:
  • More familiar to users of HTML, XUL, XAML, MXML
  • Simpler, terser syntax - you don't have to think about where blocks/events exist in the parcel, declare temporaries (like newNoteEvent and so forth)
  • hierarchy is evident in the xml schema, no need to know that 'childrenBlocks' is a list
  • No need to redundantly declare both the name in the parcel, and the blockName, everything is encapsulated in the xml-standard "id" attribute
  • Automatic hierarchy matching childrenBlocks maintained in the repository (note that self.parcel is passed to xxx.update() even when we use childrenBlocks=[...] to define the hierarchy. So you get paths like //parcels/foo/bar/FileMenu/NewMenu/NewNoteItem for free.
  • Automatic publishing of event names by automatically populating eventsForNamedLookup based on event
  • Automatic localization - the author doesn't have to wonder, should I be using _() for this attribute here or not? (maybe this is a non-issue with LocalizedStrings? - need to investigate)
  • Localizers get better tags in po files - rather than just a python line number (parcels/osaf/views/main/__init__.py#443), they get the actual location in the UI (like 'osaf/views/main/menu.xml:FileMenu#title')
  • If we ever want to switch to another xml-based resource format, converting this would be as simple as an XSLT transformation.

Here are some disadvantages:

  • Another syntax to learn
  • Two ways to declare items (might be confusing since you can also define blocks in python)
  • Another file to edit

Old brainstorming below - probably want to ignore this as its not all current..

Some of the basic ideas:

  • Keep it simple! parcel.xml's downfall is that it was trying to accomplish too much and became too abstract to use for any one specific purpose. The purpose of this is JUST to define block trees
  • XML parent/child relationships correspond directly to the childBlocks attribute
  • items with an "id" attribute get some predictable itsName/blockName
  • Tag names are correspond directly to Kind names, but in a sort of flat namespace - accomplished by some sort of "search path" of modules (so even though Table is in ControlBlocks? and BoxContainer? is in ContainerBlocks?, they could be used side-by-side and would be found automatically in the module search path)
  • extensibility is not a priority for 0.6 - no obvious way to create new tags not in the search path, etc.

  • the "id" attribute defines both the 'itsName' (in the parcel) and the 'blockName' (in the block tree)
  • all other attributes are just attributes on the tag, i.e. <Button id="mybutton" displayName="Ok">
  • any special type conversion happens based on the datatype of the attribute - using type.makeValue()
  • do we need more complex datatypes like lists or dicts? Hopefully not.
  • Need to make sure that the xml parser only accepts attributes part of the Kind
  • i18n will use something similar to how Brian wanted to do parcel.xml - english goes in the xml, and well known attributes will get .po references
  • Arbitrary block trees can be built using these xml files - so the xml files just define a tree or subtree, but don't have to define a whole window
  • complex attribute setting can be done in python before or after the parcel has been loaded
  • maybe support forward references of some sort, but hopefully there is no need
  • How are events declared? hopefully in the xml, but they aren't really part of parent/child relationships.. this may be the main reason we need some sort of external-reference system. May want to special case event= so that eventsForNamedLookup gets set automatically as well.
  • Loading block trees has got to be very, very easy.. something along the lines of LoadBlockTree?(parcel, "foo.xml")
  • might be nice if some sort of parent/child relationship gets set up in the repository, but not crucial - turns out this is very easy.

Some example code:

<MenuBar id="MenuBar">
  <Menu id="FileMenu" title="&File">
    <MenuItem id="NewItemCollection" title="New Collection" helpString="..." event="???" />
  </Menu>
</MenuBar>

Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r5 < r4 < r3 < r2 < r1 | 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.