XRX/Metadata Shopper
Motivation
editYou want to allow non-programmers (Business Analysts, Project Managers, Business Units, Subject Matter Experts etc.) to be able to create a precise list of data elements from a metadata registry. This tool has a search function that allows these users to find data elements and then add them to a list that can be saved for future use. This list (called a wantlist) can then be used to generate sub-schemas that are imported into XML exchange documents.
Metadata shoppers are one of the key tools that allow non-programmers to create precise data exchange specifications. Anyone with a few hours of training can now create precise lists of data elements that will be semantically consistent with other data exchanges. In general, shopping carts help the users find the "leaves" on the trees of a data exchange. The exact order of these elements, what elements are required, the nesting structure, and the number of elements in an exchange are not usually considered part of the metadata shopping process. These are considered the "constraints" of the data exchange, not the semantics of the data exchange. The semantics are driven by the wantlist and the constraints are usually incorporated into an XML Schema of the exchange.
This pattern of separating the semantics of a data exchange from the constraints of the exchange is known as Separation of Concerns and is one of the principal forces that allow non-programmers to have increased participation in the creation of data exchanges processes.
Method
editWe will create a XForms application that has two "submission" elements. One that gets search results and another that saves a wantlist. When a user sees a search result that they want to keep, it is added to the wantlist. At the end of the session the user typically saves their wantlist in a wantlist registry.
The wantlist created by this program is designed to be used by the subschema generator discussed later in this book.
Screen Images
editHere is a sample screen image of a shopping cart before you begin searching for data elements:
Here is the result of putting a search term such as "last name" in the search field and getting a single hit. After you get this hit you can click the add button and watch the results get added to the wantlist on the right hand side.
Here is a sample screen image showing the shopping cart after several metadata elements have been added to it.
Data Element Search Results
editThe metadata shopper depends on a data element search service that will return a list of data elements that match a query.
For example, if the URL of the data element query was the following:
BASENAME/search/search.xq?q=birth
The format of the result set would be the following:
<results>
<DataElement>
<DataElementName>PersonBirthDate</DataElementName>
<NamespaceURI>http://www.example.com/registry<NamespaceURI>
<DataElementDefinitionText>The date a person was born.</DataElementDefinitionText>
</DataElement>
<DataElement>
<DataElementName>PersonGivenName</DataElementName>
<NamespaceURI>http://www.example.com/registry<NamespaceURI>
<DataElementDefinitionText>The name given to a person at birth. Also referred to as a first name in western cultures.</DataElementDefinitionText>
</DataElement>
</results>
After a search submission is sent to the server, the search result comes back and automatically populates the search results instance in the form. This is done without reloading the form.
Program Source
editThe following XQuery dynamically generates an XForms application. We use XQuery to generate the form because we will frequently want to parameterize it with a default wantlist.
xquery version "1.0";
declare option exist:serialize "method=xml media-type=text/xml indent=yes";
let $title := 'Basic Metadata Shopper Version 1'
return
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events">
<head>
<title>{$title}</title>
<link rel="stylesheet" type="text/css" href="shopping-cart.css"/>
<xf:model>
<!-- here is where you store the search query before you hit the "search button". -->
<xf:instance xmlns="" id="search-criteria">
<data>
<q/>
</data>
</xf:instance>
<xf:instance xmlns="" id="shopping-cart">
<data>
<FileName>test.xml</FileName>
<!-- Thing from the OWN namespace -->
<DataElement>
<DataElementName>Thing</DataElementName>
<NamespaceURI>http://www.w3.org/2002/07/owl</NamespaceURI>
<definition>The root of all data elements.</definition>
</DataElement>
</data>
</xf:instance>
<xf:instance xmlns="" id="search-response">
<data/>
</xf:instance>
<xf:instance xmlns="" id="save-wantlist-response">
<data/>
</xf:instance>
<xf:submission id="search" ref="instance('search-criteria')" method="get" action="search-elements.xq" replace="instance" instance="search-response" separator="&">
<xf:toggle case="case-busy" ev:event="xforms-submit"/>
<xf:toggle case="case-submit-error" ev:event="xforms-submit-error"/>
<xf:toggle case="case-done" ev:event="xforms-submit-done"/>
</xf:submission>
<xf:submission id="save-wantlist" ref="instance('shopping-cart')" method="post" action="../edit/save-new.xq" replace="instance" instance="save-wantlist-response" separator="&">
<xf:toggle case="save-wantlist-case-busy" ev:event="xforms-submit"/>
<xf:toggle case="save-wantlist-case-submit-error" ev:event="xforms-submit-error"/>
<xf:toggle case="save-wantlist-case-done" ev:event="xforms-submit-done"/>
</xf:submission>
<!-- just for testing if you don't have an instance inspector in the browser like XForms buddy -->
<xf:submission id="echo-search-criteria" ref="instance('search-criteria')" method="post" action="../xqueries/echo-test.xq" replace="all"/>
<xf:submission id="echo-wantlist" ref="instance('shopping-cart')" method="post" action="../xqueries/echo-test.xq" replace="all"/>
</xf:model>
</head>
<body>
<a class="breadcrumb" href="../index.xhtml">Metadata Registry Home</a> >
<a class="breadcrumb" href="index.xhtml">Shopping Cart Home</a>
<div class="search">
<h1>Metadata Shopper</h1>
<xf:input ref="instance('search-criteria')/string" incremental="true">
<xf:label>Search:</xf:label>
</xf:input>
<xf:submit submission="search">
<xf:label>Search</xf:label>
</xf:submit>
<xf:switch>
<xf:case id="ready">
<!-- <xf:submit submission="echo-search-criteria">
<xf:label>Echo Search Criteria</xf:label>
</xf:submit> -->
</xf:case>
<xf:case id="case-busy">
<p>Waiting for response...</p>
</xf:case>
<xf:case id="case-submit-error">
<p>The server has returned a submit error event.</p>
</xf:case>
<xf:case id="case-done">
<div class="search-results">
<h3>Search Results:</h3>
<xf:repeat id="search-results-repeat" nodeset="instance('search-response')/DataElement">
<div class="result">
<xf:trigger>
<xf:label>Add</xf:label>
<xf:action ev:event="DOMActivate">
<xf:insert nodeset="instance('shopping-cart')/DataElement" at="last()" position="after"/>
<!-- the nth one selected -->
<xf:setvalue ref="instance('debug')/search-index" value="index('search-results-repeat')"/>
<xf:setvalue ref="instance('debug')/item-to-add" value="instance('search-response')/DataElement[index('search-results-repeat')=position()]/DataElementName"/>
<!-- set the last element in the cart to the selected item -->
<xf:setvalue ref="instance('shopping-cart')/DataElement[last()]/DataElementName" value="instance('search-response')/DataElement[index('search-results-repeat')=position()]/DataElementName"/>
</xf:action>
</xf:trigger>
<div class="result-text">
<b>
<xf:output ref="DataElementName"/>
</b>
<i>
<xf:output ref="DataElementDefinitionText/text()"/>
</i>
</div>
</div>
</xf:repeat>
</div>
</xf:case>
</xf:switch>
<xf:switch>
<xf:case id="ready"/>
<xf:case id="save-wantlist-case-busy">
<p>Waiting for response...</p>
</xf:case>
<xf:case id="save-wantlist-case-submit-error">
<p>The server has returned a submit error event.</p>
</xf:case>
<xf:case id="save-wantlist-case-done">
<div class="search-results">
<xf:repeat id="search-results-repeat" nodeset="instance('save-wantlist-response')/results">
<xf:output ref="Message/text()"/>
</xf:repeat>
</div>
</xf:case>
</xf:switch>
</div>
<div class="shopping-cart-sidebar">
<img src="shopping-cart.jpg" height="50"/>
<h3>Shopping Cart Contents:</h3>
<ul>
<xf:repeat id="shopping-cart-repeat" nodeset="instance('shopping-cart')/DataElement">
<li>
<xf:output value="concat(prefix,':', DataElementName)" class="url"/>
<!-- TODO figure out how to bind an output to a URL<xf:output
value="concat(
'http://dlficsb501:8080/exist/rest/db/mdr/data-elements/views/view-data-element.xq?id=',
DataElementName,
prefix,':', DataElementName
)"
class="url" /> -->
</li>
</xf:repeat>
<br/>
<xf:input ref="instance('shopping-cart')/FileName">
<xf:label><b>Wantlist name:</b></xf:label>
</xf:input>
<xf:submit submission="save-wantlist">
<xf:label>Save Wantlist</xf:label>
</xf:submit>
<!--
<xf:submit submission="echo-wantlist">
<xf:label>Echo Wantlist</xf:label>
</xf:submit>
-->
</ul>
</div>
</body>
</html>
Discussion
editYou may also want to copy more than a single element into the shopping cart, such as a book title and the price of the book. To do this you can use the new "origin" attribute of the XForms insert to copy not just a single element but an entire complex node into the shopping cart.