Custom implementations and component parsers

Implementations

The implementation attribute of screens, fragments and tabsextensions can be set to use a standard implementation, like rowedit. Modules can now provide custom base implementations for screens, fragments and tabsextensions, so an implementation can be shared. A custom implementation is defined in a component definition file (see Custom components for a basic setup of a custom component defintion file) by adding a ComplexType with a tolliumimplementation annotation. For example:

<xs:complexType name="myimplementation">
  <xs:annotation>
    <xs:appinfo>
      <t:tolliumimplementation objecttype="/path/to/my/customparsing.whlib#myimplementation" />
    </xs:appinfo>
  </xs:annotation>
</xs:complexType>

Add a library with the implementation object type:

<?wh

LOADLIB "mod::tollium/lib/screenbase.whlib";


PUBLIC OBJECTTYPE MyImplementation EXTEND TolliumScreenBase
<
  // Object type implementation
>;

Then you can use the custom implementation in a screens file (if the my prefix is bound to the custom component definition file's targetNamespace):

<screen name="myscreen" implementation="my:myimplementation">
  <!-- Screen contents -->
</screen>

Extra parsing

Sometimes it can be helpful if custom components can contain custom nodes. A component can already define extra parsers to parse tollium nodes, but now modules can define custom extra parsers. Like a custom implementation, a custom extra parser is defined in a custom component definition file by adding a ComplexType, but with a tolliumextraparser annotation. For example:

<xs:complexType name="myextraparser">
  <xs:annotation>
    <xs:appinfo>
      <t:tolliumextraparser parsefunction="/path/to/my/customparsing.whlib#myparser"
                            processfunction="/path/to/my/customparsing.whlib#myprocessor" />
    </xs:appinfo>
  </xs:annotation>
</xs:complexType>

The parsefunction is the function that is called to parse the matching XML nodes. The result of the parsefunction is cached. The processfunction is the function that is called to process the parsed data into a definition record field.

To better understand what is going on, here is an example component definition that uses the example parser:

<xs:element name="mycustomcomponent">
  <xs:annotation>
    <xs:appinfo>
      <t:tolliumcomponent placement="block" fragment="/path/to/my/components.xml#mycustomcomponent" />
      <t:extraparser field="customsubs" type="myextraparser" target="./my:customsub" />
    </xs:appinfo>
  </xs:annotation>
  <xs:complexType>
    <xs:sequence>
      <xs:choice>
        <xs:element name="customsub" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:attribute name="mytext" type="xs:string" />
            <xs:attribute name="mytexttid" type="sc:Tid" />
            <xs:attribute name="myint" type="xs:integer" />
            <xs:attribute name="mycomp" type="tc:ComponentRef" />
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:sequence>
    <xs:attributeGroup ref="tc:ComposableComponentBase" />
    <xs:attributeGroup ref="sc:TidOrTitle" />
  </xs:complexType>
</xs:element>

Here the myextraparser extra parser is used to parse the customsub child nodes of the component and store the parsed nodes into the customsubs definition record field.

Now the function implementations can be added:

<?wh

LOADLIB "mod::tollium/lib/componentbase.whlib";
LOADLIB "mod::tollium/lib/gettid.whlib";


PUBLIC RECORD ARRAY FUNCTION MyParser(OBJECT nodeset, RECORD field)
{
  // This function receives a NodeSet object containing the matched 'customsub'
  // nodes. We'll read the attributes and return them in a record array. The
  // 'mycomp' attribute contains a component reference, which we cannot resolve
  // while parsing and will have to be resolved in MyProcessor.
  RETURN
      SELECT mytext := ReadTidAttr(node, "mytext")
           , myint := ReadIntAttr(node, "myint", 0)
           , mycomp := ReadComponentAttr(node, "mycomp")
        FROM ToRecordArray(nodeset->GetCurrentElements(), "node");
}

PUBLIC RECORD ARRAY FUNCTION MyProcessor(OBJECT screenbuilder, OBJECT obj, RECORD scope, RECORD ARRAY data)
{
  // This function receives the parsed data (as returned by MyParser) and does
  // additional processing, like component reference resolving.
  RETURN
      SELECT *
           , mytext := GetTid(mytext)
           , mycomp := mycomp != "" ? screenbuilder->GetCheckComponent(mycomp) : DEFAULT OBJECT
        FROM data;
}

The custom component can now be added to a screen (note the xml node prefixes):

<screen name="myscreen">
  <body>
    <my:mycustomcomponent name="test">
      <my:customsub mytexttid=".teststring" myint="1234" mycomp="another" />
    </my:mycustomcomponent>
    <textedit name="another" />
  </body>
</screen>

And the component implementation now receives the parsed and processed data:

<?wh

PUBLIC OBJECTTYPE MyCustomComponent EXTEND TolliumFragmentBase
<
  PUBLIC RECORD ARRAY subnodes;

  UPDATE PUBLIC MACRO StaticInit(RECORD description)
  {
    TolliumFragmentBase::StaticInit(description);
    this->subnodes := description.customsubs;
  }
>;

The subnodes member of the component now contains one record: [ mytext := "This is a string", myint := 1234, mycomp := ^another ].