Kanban - Hierarchical Data Model

XML documents are already structured data of course, but putting XML documents into hierarchical structures has proven to be very beneficial for many applications. While XML documents are great for collecting individual related data points and processing them as a unit, there are some real-world considerations. The main one is in the security model where it is impractical to make only some parts of a document readable or writeable while still maintaining working XPath expressions, XSLT, etc. Besides access meta-data, other meta-data like timestamps are better managed outside of the document itself. While it isn’t always clear-cut where the line should go between XML elements and individual documents, there is also great value in arranging the data in a conceptually different hierarchy despite being able to in practice put everything in a giant XML document.

This illustrates the need to have a data model outside of the XML documents, that brings additional meta-data, security, and possible sharing of XML documents into the picture. Being able to put several XML documents into a container, having sub-containers to classify different XML documents as something else and being able to move documents across containers to denote different meanings to them are all features desirable.

In our development tutorial we have our version of creating a Todo app, a drag-and-drop Kanban board application. For this application we decided to break out the data model into Board containers, and List containers in the CloudBackend database. Each Board represents one kanban view, each List represents the different swim lines on the Board. The XML files stored in the List containers represent the individual tasks. This makes it easy to move a task (card) between two lists, it is a matter of only moving the document to a new container.

Since XIOS/3 is built on the premise of using XML for all data interactions the containers residing in the CloudBackend database are exposed to the developer as XML atom feeds with container elements containing links to other container nodes in a tree representing the data mode of the database. This allows the "Board" list to bind to the node set of Board container nodes, the "List" list to the node set of the container for the selected Board, and finally the swim line to the respective Lists. Add to that a simple form dialog to create new tasks (cards) and a delete action and we have a rudimentary Kanban application in 100-200 lines of code.

Figure 3. The tutorial Kanbon application.

The tutorial Kanbon application.

The following is the entire code for the applicatio above.

<application name="myKanban3" icon="icon://rocket" instances="0" theme="fabric">
  <view name="Kanban" title="Kanban v3 - Drag and Drop" width="600" height="400" icon="icon://clipboard_checks" winstate="false">
    <style>
      #boards.iconlist .layout_submenu .text { color: #FFF; }
      #boards.iconlist .marble .listselected .text { color: #000; }
      #lists.iconlist .layout_sectionlist .text { margin-top: 4px; font-weight: normal; }
    </style>
    <toolbar name="kanbanBar">
      <group name="leftBar">
        <buttonbox name="addBoard" text="Add Board" icon="icon://plus"/>
        <buttonbox name="addList" text="Add List" icon="icon://plus"/>
      </group>
      <group name="cardBar">
        <input name="addCard" placeholder="New Card" icon="icon://plus"/>
      </group>
      <group name="rightBar" align="right">
        <buttonbox name="deleteCard" text="Delete" icon="icon://delete"/>
      </group>
    </toolbar>
    <panel name="MainPanel" type="column">
      <iconlist name="boards" width="150" height="100%" layout="submenu" deselect="false" scroll="false" iconsize="24" style="background-color: #555;">
        <rule match="fs:folder">
          <item text="{@name}" icon="icon://folders2?color=fff"/>
        </rule>
      </iconlist>
      <panel type="row" width="100%" bgcolor="#fafaff">
        <iconlist name="lists" width="100%" height="70" layout="sectionlist" scroll="true" deselect="false" iconsize="32">
          <rule match="fs:folder">
            <item text="{substring-after(@name, ' ')}" icon="icon://clipboard_checks"/>
          </rule>
        </iconlist>
        <panel name="todoPanel" type="flow" height="100%" width="100%" bgcolor="#ccc">
          <grid name="cards" height="100%" draggable="true">
            <row match="atom:entry">
              <column name="subjectCol" match="atom:title" display="substring-before(.,'.xml')" label="Task" filter="true" width="100%"/>
              <column name="dateCol" match="atom:updated" display="translate(., 'TZ', '  ')" label="Date" filter="true" width="140"/>
            </row>
          </grid>
        </panel>
      </panel>
    </panel>
  </view>

  <process name="Kanban - Process">
    <trigger view="Kanban" event="Loaded" step="init"/>
    <trigger view="Kanban" component="boards" event="Select" step="selectBoard"/>
    <trigger view="Kanban" component="lists" event="Select" step="selectList"/>
    <trigger view="Kanban" component="addCard" event="Select" step="addCard"/>
    <trigger view="Kanban" component="addCard" event="Enter" step="addCard"/>
    <trigger view="Kanban" component="addBoard" event="Select" step="addBoard"/>
    <trigger view="Kanban" component="addList" event="Select" step="addList"/>
    <trigger view="Kanban" component="deleteCard" event="Select" step="deleteCard"/>
    <trigger view="Kanban" component="lists" event="Drop" step="dropList"/>
    <trigger view="Kanban" component="boards" event="Drop" step="dropBoard"/>

    <step id="init">
      <operation name="bind" value="tenant://Kanban">
        <component view="Kanban" name="boards" select="/atom:feed"/>
      </operation>
      <operation name="setSelection" value="">
        <component view="Kanban" name="boards">
          <item select="/atom:feed/fs:folder[1]"/>
        </component>
      </operation>
    </step>
    <step id="selectBoard">
      <operation name="bind" value="{#Kanban#boards#@path}">
        <component view="Kanban" name="lists" select=""/>
      </operation>
      <operation name="setSelection" value="">
        <component view="Kanban" name="lists">
          <item select="/atom:feed/fs:folder[1]"/>
        </component>
      </operation>
    </step>
    <step id="selectList">
      <alias name="testAlias" value="#Kanban#boards"/>
      <operation name="bind" value="{#Kanban#lists#@path}">
        <component view="Kanban" name="cards" select=""/>
      </operation>
    </step>
    <step id="addBoard">
      <operation name="confirm">
        <type value="input"/>
        <message>Please enter the name of the new Kanban board.</message>
        <modal>false</modal>
        <title>Enter board name</title>
        <ok step="createBoard" text="OK"/>
        <cancel step="cancel" text="Cancel"/>
      </operation>
    </step>
    <step id="addList">
      <operation name="confirm">
        <type value="input"/>
        <message>Please enter the name of the new board list.</message>
        <modal>false</modal>
        <title>Enter list name</title>
        <ok step="createList" text="OK"/>
        <cancel step="cancel" text="Cancel"/>
      </operation>
    </step>
    <step id="createBoard">
      <alias name="containerName" value="{!}"/>
      <operation name="filesystem" value="tenant://Kanban/">
        <create type="folder" name="{$containerName}"/>
      </operation>
    </step>
    <step id="createList">
      <alias name="containerName" value="{!}"/>
      <operation name="filesystem" value="{#Kanban#boards#@path}">
        <create type="folder" name="{$containerName}"/>
      </operation>
    </step>
    <step id="addCard">
      <alias name="cardName" value="{!}"/>
      <operation name="filesystem" value="{#Kanban#lists#@path}">
        <create type="file" name="{$cardName}.xml" rename="false">
            <content type="text/xml">
                <card deadline="" completed="false">{$cardName}</card>
            </content>
        </create>
      </operation>
    </step>
    <step id="deleteCard">
      <alias name="url" model="string" value="{#Kanban#cards#atom:content/@src}"/>
      <operation name="decision" value="$url">
        <when test="'{$url}' != ''">
          <operation name="filesystem" value="$url">
            <delete permanent="true"/>
          </operation>
        </when>
        <otherwise>
          <operation name="confirm">
            <type value="message"/>
            <message>Please select a card first to delete.</message>
            <modal>false</modal>
            <title>No card selected</title>
            <icon>icon://delete</icon>
          </operation>
        </otherwise>
      </operation>
    </step>
    <step id="dropList">
      <alias name="dropSource" value="!"/>
      <alias name="dropSourceDoc" value="!" model="value"/>
      <alias name="dropTargetFolder" value="{#Kanban#lists#@path}"/>
      <operation name="decision" value="$dropSource">
        <!-- Check that we are not dropping a board or list, i.e. a container -->
        <when test="local-name() != 'folder'" step="handleDrop"/>
      </operation>
    </step>
    <step id="dropBoard">
      <alias name="dropSource" value="!"/>
      <alias name="dropSourceDoc" value="!" model="value"/>
      <alias name="dropTargetFolder" value="{#Kanban#boards#@path}"/>
      <operation name="decision" value="#Kanban#boards">
        <!-- Check that we are dropping a list and not a board into another board, only accept lists -->
        <when test="not(../fs:folder[@id = '{$dropSource#@id}']) and '{local-name($dropSource)}' = 'folder'" step="handleDrop"/>
      </operation>
    </step>
    <step id="handleDrop">
      <operation name="decision">
        <!-- Check that what we are dragging is not the same as where we are dropping -->
        <when test="'{$dropSourceDoc}' != '{$dropTargetFolder}' and '{$dropSource#@path}' != '{$dropTargetFolder}'">
          <operation name="filesystem" value="$dropSource">
            <move to="{$dropTargetFolder}" silent="false" progress="true"/>
          </operation>
        </when>
      </operation>
    </step>
  </process>
</application>