XRX/Print version

< XRX
XRX

Current, editable version of this book is available in Wikibooks, collection of open-content textbooks at URL:
http://en.wikibooks.org/wiki/XRX

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be obtained here.


Section 0: Overview and TOC edit

Web Development with XRX
 

XRX or XForms/REST/XQuery is a simple and elegant web application architecture that leverages modern declarative and functional programming systems. XRX allows the developer to create rich-client web applications that perform complex functions without the need for middle-tier objects, relational databases or client-side JavaScript.

XRX is based on three standards:

  1. XForms on the client
  2. REST interfaces
  3. XQuery on the server

These three standards have been created by the W3C standards organization and represent their vision of the future of web application development. For discussions on alternate definitions of the XRX web application architecture see What is XRX.

This wikibook is intended as an example that specifically uses all three of these technologies to create small applications that work together.

Related Wikibooks edit

There are two sibling Wikibooks that this Wikibook is designed to complement.

The XForms Tutorial and Cookbook Wikibook has over 90 sample XForms application to help you become familiar with the XForms model and XForms controls. Although XForms has only 21 elements they can be combined in many different ways to build very complex web clients.

The XQuery Wikibook is focused on using the XQuery language with almost all of the sample programs using the eXist native XML database.

The XForms Wikibook has minimal dependencies on which server you use to host your web forms. Almost all of the XQuery Wikibook does not assume any prior knowledge of XForms. This book, on the other hand, assumes that you will be using both XForms and XQuery to create a complete web application development environment.

Subversion Repository edit

Many of the example programs in this cookbook are now being stored in a Subversion repository on GoogleCode. The URL for the XRX GoogleCode is here:

http://code.google.com/p/xrx

If you are using an IDE with a Subversion client such as Eclipse or oXygen, the URL for the repository is:

https://xrx.googlecode.com/svn/trunk

If you would like a read-only copy, you can use the non-SSL URL

http://xrx.googlecode.com/svn/trunk

Table of Contents edit

Introduction edit

  1. Introduction - an overview of the goals of this Wikibook and the intended audience  
  2. Benefits of XRX - an analysis of the technical benefits of the XRX web application architecture  
  3. XRX Application Server - how XRX has allowed the XQuery language to move from a database language to an application language  
  4. Building your First XRX Application - some suggestions on how to get started building your first XRX application  
  5. Background Technologies - a summary of the background technologies you will use to build XRX applications  
  6. Patching you Browser to Support XForms - how to add functionality to support the W3C XForms standards  
  7. XSLTForms and eXist - getting XSLT forms to work with eXist  

Common Patterns edit

  1. Application Modularity - XRX applications can be managed and reused if they have modular structure  
  2. Data Encapsulation - XRX applications contain and manage their own data sets and provide XQuery functions as interfaces  
  3. Standard Views - XRX applications contain standardized views of their data  
  4. Searchability - XRX applications provide tools to search their own data and interfaces to allow it to participate in site-wide search functions  
  5. Code Table Management - XRX applications manage code tables (selection lists) and make it easy for non-programmers to maintain these codes  
  6. Server Field Validation - XRX can use server-side XQuery to validate a field  
  7. Breadcrumb Navigation Bar - XRX applications are nested in a hierarchical structure an navigation breadcrumbs are context aware  

Sample Applications edit

  1. Configuration File Editor - a simple file, single user XML configuration file editor using XForms and eXist  
  2. Dictionary Editor - a simple round-trip create/update edit using XForms and eXist  
  3. Regular Expression Builder - a demonstration of using regular expressions in XQuery  
  4. Autoincrement File ID - save an instance from a form into a collection and have the id automatically created by the server  
  5. Move a Resource - simple resource move utility  
  6. Save File Dialog - saving a file to a collection similar to a save dialog panel  
  7. Login and Session Management - a login panel and methods of authenticating users  
  8. File Locking - strategies to prevent multiple users from overwriting each others updates 
  9. Selection List Generator - a tool to generate selection lists from code tables  
  10. Glossary Term Editor - a tool to manage specialized business vocabularies  
  11. FAQ Manager - a tool to manage frequently asked questions  
  12. Detecting Duplicates - checking for duplicates as you type  
  13. Data Element Editor - a tool to manage ISO/IEC 11179 data elements  
  14. Selection List Management - tools to manage selection list codes in your XForms  
  15. Customizing Selection Lists - customize the selection list based on role or other session variable  
  16. Table Sorting - customization of table sort order  
  17. NIEM Services - tools to create NIEM web services  
  18. Product Ratings - allows users to assign a rating of one to five stars to an item in a collection  
  19. Business Rules Template - a sample of a simple business rules template  
  20. Metadata Shopper - a shopping cart tool for your metadata elements  
  21. Subset Generator - a program that generates a subset of a metadata registry that is imported into an XML Schema  
  22. XForms Generator - convert an XML Schema directly into an XForms application  
  23. XForms Instance Generator - convert an XML Instance directly into an XForms application  
  24. Large XForms Generator - create large XForms using a single REST parameter  
  25. User Manager - track users and manage their login attempts, session timeouts and roles  
  26. Map Navigation - add map navigation to your XRX applications  

XRX Patterns edit

  1. Content Routing - inspecting the content of an XML document to apply save rules  
  2. URL Rewriting - allow URIs to reflect the logical structure of a service, not the collection structure of the database  

Related Technologies edit

  1. LAMP - Linux, Apache, MySQL and PHP
  2. AJAX - Asynchronous JavaScript and XML
  3. Adobe Flex - Adobe's system for building rich-client interfaces (Now Apache Flex)
  4. Microsoft Silverlight - Microsoft's strategy for putting XML in the browser (Discontinued)

References edit

  1. Related Wikibooks
    1. XForms
    2. XQuery


Please add {{alphabetical}} only to book title pages.




Section 1: Introduction edit

Background edit

This cookbook was created by Dan McCreary in the Spring of 2008. The XRX label was created by Dan soon after the December 2007 XTech meeting in Boston. Dan found many people there that all came to the same conclusion: using XForms and a native XML database offered some huge advantages. He also found that his related cookbook (the XForms cookbook) started to have many examples that used not just XForms but also XQuery as a back end. In order to keep the XForms cookbook general and still appeal XForms developers, many of the XForms/XQuery examples were migrated to this Wikibook.

Many XRX fans feel XRX to be a superior web application architecture, a natural consequence of XML being used both in the client, as a data transmission standard, and in the server.

We hope you find this book useful and we hope you contribute to the examples.



Next: Benefits of XRX

Benefits of XRX edit

Benefits of XRX Architecture edit

XRX benefits from each of its three underlying standards:

  • XForms
    • Unlike technologies such as AJAX, XRX assumes a model-view-controller architecture
    • XForms supports a dependency graph that allows the right views to be automatically updated
    • All interactions between the client and the server are sent through the model via submission elements
  • REST
    • XRX takes advantage of web infrastructure such as multi-level caching and technologies that are collectively known as deep REST systems.
  • XQuery
    • XQuery is a functional XML-oriented query and transform language that can perform the business logic for many systems. In addition to its XML transform capabilities, it has advantages over older query languages such as SQL designed in the 1970s and 1980s. XQuery has the expressive power of SQL (joins etc) and can also manipulate recursive hierarchical data structures. Unlike SQL, XQuery is a W3C standard with very strong standardization across vendors and over 14,000 tests to ensure cross vendor compatibility. Developers frequently develop XQuery applications on low-cost OpenSource databases and port them to commercial systems without significant changes.

Together, these three standards allow developers to use a single data format (XML) on the client, middle tier and the databases server. This means there is no translation of the data formats between layers. In contrast to this, many other modern systems communicate using XML, process business logic in an object-oriented layer, store data in a relational database, and render a GUI as HTML.

Consider all the "glue" code in a typical modern system, which serves no purpose other than to translate from one representation to another: object-relational mappings, HTML rendering code, XML import/export code. These web application architectures take HTML forms (which use flat key-value pairs), convert these data structures to middle tier objects such as Java or .Net and then transform the objects into tabular data streams so they can be stored in relational databases. Once in the relational database the data must then be re-serialized by doing SELECT statements, converted into objects and the objects then converted back into HTML forms. This is a four-step translation architecture.

In contrast, XRX uses a zero-translation architecture. Zero translation implies that XML is stored in the web client, transmitted to a middle tier validation rules engine in XML and then stored in its entirety in an XML database. Typically, XML is also the format that data is delivered or streamed in from other systems. The storage as XML is also known as a zero-shredding process since the data files are not separated into Third Normal Form (3NF) data structures.

This relational (3NF) storage is often not helpful. XML data has the relationships built into the data, whereas relational data is stored in "dumb" tables and allows/requires the applications to specify the relationships. For applications where the relationships are well known (e.g. a user has a collection of blog posts, and those posts have collections of comments and replies) XML is better. When a "data warehouse" is needed and the relationships will vary widely based on the calling application, a relational store may be better because RDBs are optimized for ad-hoc joins where XML DBs are generally not. (e.g. a group of users are in a social network and there are a variety of ad-hoc relationships that *all* must be queried quickly - use an RDB). XML databases are optimized for the relationships built into the XML, relational databases are optimized for easy programming and performance on one preferred set of relationships.

The biggest difference, though, is zero-translation vs. "translation everywhere."

1+1+1=10 edit

Using a combination of all these three standards gives web developers a tenfold increase in productivity.


Back: Introduction Next: XRX Application Server

References edit

XRX: Simple, Elegant, Disruptive

XRX Application Server edit

XRX as an Application Server edit

XQuery is a transform language in addition to a query language. Its original name was "Quilt" reflecting its ability to knit together different pieces of (XML) content.

This means that it is good at querying XML data from a data store and transforming it into an XHTML web page. Any XQuery-capable XML data store that accepts HTTP requests can talk to your web browser and send it an arbitrarily complex, valid (X)HTML document.

Here is a short example of how XQuery can transform XML database records into valid HTML:

 <customer>
   <name>bob</name>
   <orders>
     <order qty="1">Jumbo Can SPAM</order>
   </orders>
 </customer>

can be "transformed" to HTML:

 <div id='cust'> <b>bob</b> 
   Orders:
   <ul>
     <li>Jumbo Can SPAM (1)</li>
   </ul>
 </div>

with XQuery something like (not tested):

 let $customer := //customer[name='bob']
 return
 <div id='cust'> <b>{$customer/name/text()}</b>
   Orders:
   <ul>
   {
     for $o in $customer/orders/order
     return <li>{$o/text()} ({$o/@qty})</li>
   }
   </ul>
 </div>

Many free and commercial XQuery-enabled databases are available. They include: BaseX, DB2, eXist, MarkLogic, Oracle, and xHive. BaseX and eXist are free, and MarkLogic (and perhaps others) has a free community edition. Many if not all of them support HTTP requests, and therefore can be used as web/app servers.

BaseX:

BaseX is both a light-weight, high-performance and scalable XML Database and an XQuery 3.1 Processor with full support for the W3C Update and Full Text extensions. It focuses on storing, querying, and visualizing large XML and JSON documents and collections. A visual frontend allows users to interactively explore data and evaluate XQuery expressions in realtime. BaseX is platform-independent and distributed under the free BSD License.(from their website)

It also has a stand-alone mode and a web-server mode.

eXist:

Although the eXist database was originally created to simply store XML data like a traditional database, the rich REST interfaces that have been added to eXist have transformed it into a web server. The eXist database is now able to provide a rich set of functions that are only available in other application servers. What remains is the application development standards that would allow a large number of people developing XRX applications to all work together.

MarkLogic:

MarkLogic is perhaps the most capable XML data management product at this writing. It combines XML/XQuery with text indexing and retreival, geospatial, alerting, and enterprise class scaling and performance.

xHive (or xDB):

xHive is a state-of-the-art native XML database with XQuery support, transaction control, extensive indexing features and top-class scalability.

DB2, Oracle, PostgreSQL:

DB2, Oracle and PostgreSQL support XML by incorporating it as a large CLOB field, and support XQuery on this data. PostgreSQL currently supports only XPath (as a subset of XQuery). It checks the input values for well-formedness, and there are support functions to perform type-safe operations on it.

XRX Application Server Vision edit

The XRX application server can now be set up to allow a wide variety of applications to be installed that all work together.


Back: Benefits of XRX Next: Building your First XRX Application

Building your First XRX Application edit

How Build Your First XRX Application edit

Although there are many different ways you can build an XRX application, there are two vital ingredients to get you started. First you will need a way to create XQueries with RESTFul interfaces. The OpenSource native XML database eXist seems to be the favorite of most people although the MarkLogic server has a community version that is free as long as your data sets are under 10GB which seems to be enough for most pilot and learning projects.

The second component you will need is some client that will take an XForms data stream intended for a browser and build applications with it. Because the XSLTForms client is now bundled with both eXist and MarkLogic and runs on all browsers many people are using this client.

If you need forms that need to load quickly without the overhead of loading the large XSLT transforms and JavaScript libraries, the FireFox XForms addon is also a very mature product, but does not run on FireFox versions higher than 3.0.12.

The Picoforms XForms client for IE and Chiba forms will also work very well. If you are looking for a pure server-side transform you may want to look into Orbeon Forms or the IBM Workplace Forms tools.

There are five architectural tasks that you will need to master. They go by phrase CRUDS: Create, Read, Update, Delete and Search. Having a tool to quickly import XML data from spreadsheets is also very useful as you migrate more data into your XRX application server. The oXygen XML editor is a favorite because is has strong support for the eXist database.

Once you have understood the basic concepts in the CRUDS cycle, you can then explore ways to optimize reports using XQuery and XQuery functions and modules.


Back: XRX Application Server Next: Background Technologies

Background Technologies edit

Standing on the Shoulders of Giants edit

The XRX architecture did not appear overnight. It was only made possible by the diligent work of many standards organizations over many years. Clearly the XML standard is one that enabled most of the key technologies. But the role of HTML, HTTP, CSS, XPath, XML Schema, XSLT, XForms, REST, Schematron and XQuery and other standards created by the W3C really must be appreciated to understand the true scope of the standardization efforts. Here is a brief overview of these technologies and a rough order that they were created and could be learned.

XML Syntax edit

The syntax of using "less than" and "greater than" characters to bracket data elements embedded in a text document goes back to early markup languages such as SGML. SGML was the language that the HTML standard was modeled on. SGML was a complex standard that required the user to be familiar with many technologies, so in 1997 an effort was made to simplify the standard and the XML standard was first published by the W3C.

XPath edit

The XPath language is a smaller data-selection language that is used extensively within other XML standards. Both our client technology (XForms) and our server technology (XQuery) heavily depend on using XPath to surgically remove the elements we want from much larger XML tree structures. If you are familiar with UNIX or web file system paths you may already have a good mental model for how XPath expressions work. For the purposes of this book we will be using mostly very simple XPath expressions that merely narrow XML data sets using "child" paths and "where" paths called predicates.

XForms edit

Although this wikibook is not intended to be a book on XForms, the use of XForms makes client application development very consistent with the rest of the material in this book. We will cover just enough background to help you understand the key architectural features of XForms and how it leverages REST interfaces to get and submit data to the server. Technically you can still benefit from the XRX architecture without using any XForms applications on the client but you may find that you duplicate most of the components that are already in XForms when you do this. Central to the concepts in XForms are the MVC architecture, Binding, the dependency graph and declarative submission elements. If you are a fan of JavaScript and AJAX, you can implement much of the functionality of XForms using these tools. But this topic is not covered in this wikibook. In fact many people use XRX architecture specifically to avoid having to learn JavaScript and the myriad of JavaScript frameworks that are popular today.

XQuery edit

Anyone who is familiar with the SQL language understands the role of the SELECT statement in selecting the appropriate tables, columns and rows from a relational database. XQuery on the surface may seem only like a version of SQL for XML databases. Because XQuery can be easily extended, however, it has taken on a much larger role then just a reporting language for XML databases. XQuery may be the only server language that a developer will ever need. Because it can be easily extended, it has already been extended in hundreds of useful ways.

The first major extension has been the ability of XQuery to update XML data. This was considered important to the original XQuery authors, but they decided to focus on data selection first and data updating second.

CSS edit

One of the most elegant aspects of the XForms model is that it was designed from the ground up to work with the W3C's Cascading Style Sheet (CSS) standard. Thus, almost every aspect of how web application controls are arranged on the screen can be managed by a single site-wide CSS file. The consequence is that if you decide to make a site-wide change, only a single file needs to be altered. The downside is that the level of CSS support varies dramatically in many browsers. Since XForms is implemented as a separate namespace and CSS is designed to be namespace aware, most modern style sheets for XForms leverage this basic feature. The challenge is that not all browsers have implemented the CSS namespace functionality. This issue is complex and any XRX architecture that is building applications to support older browsers must take these caveats into consideration.


Back: Building your First XRX Application Next: Patching your Browser to Support XForms

Patching you Browser to Support XForms edit

Motivation edit

Your browser does not support the W3C XForms standards.

Method edit

Browsers should detect the XForms namespace and just render the XForms controls per the XForms specification. But many browsers vendors do not do this, perhaps because they feel that supporting their own standards is in the best interest of their stockholders. Whatever the reason, it can make life difficult for the website developer.

If you are running Firefox, you can load an extension that will correctly recognize the XForms namespace and do the right thing.

Some XForms implementations, such as XSLTForms, require you to add an XML Processing instruction to correctly convert XForms controls into HTML+JavaScript.

Here is an example of how to do this using the XML processing-instruction function within eXist:

let $my-form :=
<html>
  ....
</html>

let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="/rest/xsltforms/xsltforms.xsl"'}

return ($xslt-pi, $my-form)

This will return a sequence of two items, the Processing instruction and the form itself.

You can also build your own "form assembler" that uses a title, model and content.


Back: Background Technologies Next: XSLTForms and eXist

XSLTForms and eXist edit

Motivation edit

You want to port your standard XForms applications to work with XSLTForms and to be able to dynamically generate XForms from eXist XQueries.

Method edit

XSLTForms is a very lightweight XForms processor that can be configured to run either on the web server or the web client. XSLTForms transforms each XForms element in the XForms application into HTML with some JavaScript. If you are using the client to do the transform, you must be aware of some limitations in client side-browsers.

Client-Side or Server-Side Transform: Design Tradeoffs edit

The design decision of where to execute the transform, on the client or on the server, is one that may vary based on your situation. Transforming the form on the server can sometimes avoid the transmission of the full XSLT transform to the client and it also avoids browser-dependent variations. This process also may work more naturally for pipeline operations that use tools like XProc on the server.

On the other hand, once the first form is loaded, the XSLT transform can be read from the local browser cache. This allows you to only have to transmit the actual XForms application specification and not the much larger JavaScript and HTML code. So this option can minimize the network traffic as well as minimize the delay in displaying the changes.

The XSLTForms application is a library for doing XSL transforms based on the XForms input file. The output is a combination of HTML and JavaScript that will run across many browsers (Firefox, Internet Explorer 6, 7 and 8, Apple Safari, Google Chrome, and Opera).

Note that for many internal forms on an intranet, it is possible to request that all your users use a web browser that supports in-client plugins. This means that your XForms load times are very fast and your testing process is simple. For public forms, you often can not specify what browser the users must use, so the forms developer is required to test on many different versions of many different browsers. This can dramatically slow the forms development process and can cause testing and development costs to grow.

Disabling betterFORM on eXist 2.0 and 2.1 edit

eXist 2.0 and 2.1 come with the server-side XForms processor betterFORM installed as default. In order to allow XSLTForms to process XForms, you should do one of the following:

To use XLSTForms in a certain context, but allow the use of betterFORM in other contexts, set the following dummy attribute in your XQuery:

   let $attribute := request:set-attribute('betterform.filter.ignoreResponseBody', 'true')

in the XQuery that generates your form.

In order to disable betterFORM completely, change

<property name="filter.ignoreResponseBody" value="false"

to "true" in $EXIST_HOME/webapp/WEB-INF/betterform-config.xml

or comment out the entries for XFormsFilter in $EXIST_HOME/webapp/WEB-INF/betterform-config.xml and/or in $EXIST_HOME/webapp/WEB-INF/web.xml and restart.

Browser Portability Issues edit

One of the large challenges attendant on supporting multiple versions of browsers from many vendors is that the way they render CSS can be dramatically different. Since XForms frequently leverage advanced CSS structures for form layout, this can be a large concern for XForms application developers. IE 6 can be especially troublesome due to its lack of full support for the CSS box model. This is especially true of forms that do not use HTML Table layouts to render forms. What was a simple style sheet for a single browser may often grow to be very complex and require many days of testing. For example the use of inline-block has many variations on many different browsers.

The best defense against the browser incompatibilities is to keep your forms very small and simple. Use many different class attributes and don't use repeating structures unless absolutely necessary.

Problem with Namespace Axis in Firefox 3.0 edit

There is currently a namespace bug in the Mozilla Firefox browser that prevents any instances from being loaded that use the non-null namespaces. For example the following XML file cannot be loaded into an instance using the src attribute.

<ex:root xmlns:ex="http://www.example.com">
    <ex:string>This is a test string.</ex:string>
</ex:root>

The workaround is to put a dummy namespace prefix reference in the HTML root element, such as:

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xf="http://www.w3.org/2002/xforms"
    xmlns:ex="http://www.example.com"
    ex:dummy="dummy" >
    <head>

The ex:dummy="dummy" will fix the problem and the instances will load correctly into the form. This patch may not work on other versions of Firefox. Note that the instance can be loaded inline to get around this bug. Also note that using the default namespace in the instance will also not work.

This is known as the Firefox Namespace Axis Problem. This is not a problem in Internet Explorer, Safari, Opera or Chrome. Firefox is the only browser that does not support namespace axis functions. XML support appears to be a low priority within Mozilla. We encourage anyone that feels that Firefox should support XML namespaces to vote for this bug fix on the Mozilla web site.

See Mozilla Bug ID 94270. The comment by C. M. Sperberg-McQueen is very informative.

The article by Uche Ogbuji discusses the new features Firefox 3.0 offers for XML processing.

How XSLTForms works edit

To convert your standard XForms to work with XSLTForms you only have to add the following statement as the first line of your XForms file.

  <?xml-stylesheet type="text/xsl" href="/exist/rest/db/xforms/xsltforms/xsltforms.xsl"?>

The actual URL to use on your eXist installation may vary depending on your version. This document is large for an XSLT stylesheet (currently about 114KB and 4,500 lines long), but much smaller than many JavaScript libraries that run over 50,000 lines of code. The XSLT process also loads a 363KB JavaScript file. The key is that once it is loaded for the first form it can be stored in the web cache. A compressed version of this file may provide faster loading times in the future. This process will always be slower than a native XForms processor like the Firefox XForms extension, but the benefit is that it will work on multiple browsers once the XForms stylesheets have been tested on one browser.

Controlling Client and Server XSLT Execution edit

By default, eXist will always attempt to perform server-side XSLT processing whenever it encounters an XML Processing Instruction that signals that it should perform a transform.

If your XForms are static, meaning that they do not change at all based on the context of the situation, you can store the forms anywhere on a web file system. They do not need to be stored in eXist.

If you store the XForms in eXist, there are two items you must be aware of.

By default, eXist processes all XML XSL processing instructions and runs them on the server BEFORE they ever reach the browser. This is not the way that XSLTForms was originally designed to work. It also requires a lot more bandwidth between the client and the server.

To disable server-side XSLT processing you will need to add the following instruction to your XQuery that generates the XForms:

  declare option exist:serialize "method=xhtml media-type=text/xml";
  declare option exist:serialize "indent=no";
  declare option exist:serialize "process-xsl-pi=no";

The first line sets the XHTML serialization type and also sets the standard XML mime type.

Note: Many browsers such as IE do not recognize the proper XForms mime type of application/xhtml+xml. To get around this problem you can use the incorrect mime type of text/xml. There are ways to fix IE to recognize the correct mime types by making changes to the Windows registry but these changes are difficult to make for general users. For more details see here

The second line is required by the current release of XSLTForms. The last line tells the server-side to not process the transform.

These three options can be combined into a single statement:

  declare option exist:serialize "method=xhtml media-type=text/xml indent=no process-xsl-pi=no";

Note that in the context of eXist, process-xsl-pi=no means "do not process this on the server before you send it to the browser".

One approach to generating dynamic forms is to use the following style:

let $form :=
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:ev="http://www.w3.org/2001/xml-events" 
   xmlns:xs="http://www.w3.org/2001/XMLSchema" 
   xmlns:xf="http://www.w3.org/2002/xforms">
    <head>
      <title>XForms Template</title>
      <xf:model>
         <xf:instance xmlns="" id="save-data">
            <data/>
        </xf:instance>
      </xf:model>
   </head>
   <body>
      <h1>XForms Template</h1>
      <p>This XForms application has been dynamically generated from an XQuery</p>
   </body>
</html>

You can then return the following at the end of your form:

  let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="/exist/rest/db/xforms/xsltforms/xsltforms.xsl"'}
  return ($xslt-pi,$form)

For debugging you can also use:

  let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="/exist/rest/db/xforms/xsltforms/xsltforms.xsl"'}
  let $debug := processing-instruction xsltforms-options {'debug="yes"'}
  return ($xslt-pi, $debug, $form)


Here the href points to the location you have installed your XSLTForms library.

Server-side Transformation using the transform Function edit

eXist provides a method for doing a server-side XSLT transform using the transform module. The following is an example of using this transform function to perform server-side transformation of the form.

xquery version "1.0";
declare option exist:serialize "method=xhtml media-type=text/html indent=no";

let $form :=
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:ev="http://www.w3.org/2001/xml-events" 
   xmlns:xf="http://www.w3.org/2002/xforms">
    <head>
      <title>XForms Template</title>
      <xf:model>
         <xf:instance xmlns="" id="save-data">
            <data>
               <name>John Smith</name>
            </data>
        </xf:instance>
      </xf:model>
   </head>
   <body>
      <h1>XForms Test Program</h1>
      <xf:input ref="name">
         <xf:label>Name: </xf:label>
      </xf:input>
   </body>
</html>

let $transform := '/exist/rest/db/xforms/xsltforms/xsltforms.xsl'

let $params := 
<parameters>
   <param name="omit-xml-declaration" value="yes"/>
   <param name="indent" value="no"/>
   <param name="media-type" value="text/html"/>
   <param name="method" value="xhtml"/>
   <param name="baseuri" value="/exist/rest/db/xforms/xsltforms/"/> 
</parameters>

let $serialization-options := 'method=xml media-type=text/html omit-xml-declaration=yes indent=no'

return 
   transform:transform($form, $transform, $params, $serialization-options)

Using XSLTForms on the Server Side in a URL Rewrite Filter edit

It is also possible to perform server-side transformation using eXist's URL Rewrite function.


Sample from /eXist/webapp/xforms/controller.xql

<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
   <view>
      <forward servlet="XSLTServlet">
         (: Apply xsltforms.xsl stylesheet :)
         <set-attribute name="xslt.stylesheet" value="xsltforms/xsltforms.xsl"/>
         <set-attribute name="xslt.output.omit-xml-declaration" value="yes"/>
         <set-attribute name="xslt.output.indent" value="no"/>
         <set-attribute name="xslt.output.media-type" value="text/html"/>
         <set-attribute name="xslt.output.method" value="xhtml"/>
         <set-attribute name="xslt.baseuri" value="xsltforms/"/>
      </forward>
   </view>
   <cache-control cache="yes"/>
</dispatch>

See Example from eXist subversion

Modifying your CSS to work with XSLTForms edit

Because XSLTForms uses XSLT on the web client, it only knows how to parse XML files. To get your CSS files to work you will have to make each of your CSS files a well-formed XML document. This can be easily done by simply wrapping your CSS file with a root data element such as:

<css>
   /* empty rule to recover from parse errors */
   empty {}
   body {font-family: Arial, Helvetica, sans-serif;}
   label {font-weight: bold;}
</css>

If you wish your CSS to be both valid CSS and well-formed XML, you can use a special prolog <css><![CDATA[/**/ and epilog /*]]>*/</css> as shown in the two examples below. As interpreted by an XML parser:

<!--/*--><css><![CDATA[/**/

h1 {
   body {font-family: Arial, Helvetica, sans-serif;}
   label {font-weight: bold;}
}

/*]]>*/<!--/*--></css><!--*/-->

The same file interpreted by a CSS parser:

<!--/*--><css><![CDATA[/**/

h1 {
   body {font-family: Arial, Helvetica, sans-serif;}
   label {font-weight: bold;}
}

/*]]>*/<!--/*--></css><!--*/-->

You can then just import the CSS file into your XForms file using the link element:

  <link type="text/css" rel="stylesheet" href="style-with-root-wrapper.css"/>

XQuery can also be used to automatically generate the CSS file from the server. On the server the CSS file can be wrapped, but for non XSLTForms clients the root element can be removed.

If there are small changes in the CSS that are not global to all other forms, you can insert them directly into the <style> tag of the form. Note that if your XForms are dynamically generated from an XQuery you must enclose the style with CDATA wrappers or use double curly braces {{ and }} to escape the XQuery processing.

<style type="text/css">
    <![CDATA[
       @namespace xf url("http://www.w3.org/2002/xforms");
          
       .block-form xf|label {
          width: 15ex;
       }
          
       .PersonGivenName .xforms-value {width:20ex}
       .PersonSurName .xforms-value {width:25ex}
   ]]>
</style>

Note that in the Firefox plugin the CSS class for changing the value was .xf-value. In XSLTForms the class is .xforms-value.

Disabling the CSS Conversion Option edit

XSLTForms also allows you to add the following XML Processing Instruction flag to your XForms file.

  <?css-conversion no?>
  <?xsltforms-options debug="no"?>

If you add these lines the CSS files will not need to be converted to XML files. You can not use the @namespace xf features within the CSS, and you may have to add class attributes to tags, but some users prefer this method.

Formatting XForms Elements edit

The following syntax should be used for styling XForms elements:

 @namespace xf url("http://www.w3.org/2002/xforms");
 
 xf|label {font-weight: bold;}

Note that this format has been tested with XSLTForms on IE 8, Firefox, Opera and Safari running on Windows, the Mac and Linux.

Rendering Block Forms edit

Block forms are forms that use a block layout for each control so each control appears on a different line. Block forms have a consistent layout and have room for hint text, required field labels and help icons. Inline forms that have multiple fields on a line should be used only when you have screen area constraints.

 
Example of a block form

To render block forms correctly we suggest you wrap all block forms in a standard div tag with a class of "block-form":

   <div class="block-form">
      ...put the controls here...
  </div>

This allows you to use a CSS file where the block display items are only triggered if they are decendants of the block form div element.

Here are some sample CSS rules for block forms.

/* formatting rule for all XForms controls elements within a block form.  Each input control is on a separate line */
.block-form xf|input,
.block-form xf|select,
.block-form xf|select1,
.block-form xf|textarea {
     display: block;
     margin: 1ex;
}

/* formatting rule for all labels in a block form */
.block-form xf|label {
    display: inline-block;
    width: 15ex; /* fix the width of all the labels in the block form */
    float: left;
    text-align: right;
    margin-right: 1ex; /* margin to the right of the label */
}

The most risky part of this is the inline-block rule. This is required to allow you to fix the width of the label.

Aligning select1 with appearance=full edit

If you want to display all the items in a selection list you can add the attribute

  <xf:select1 appearance="full">

To keep the item values together you must add the following to your CSS file:

 xf|select1 xf|item {
   margin-left: 21ex;
 }

Using xf:load in place of xf:submission edit

The current release of XSLTForms does not support the xf:submission element with the correct URL rewrite when the replace="all" attribute is used. This structure is used in many search forms, when a form is used to gather a set of parameters, such as keywords and date ranges that are used to limit the search. These search parameters are traditionally stored in a single instance in the model and used to construct a URL for a restful search service.

Here is an example of search parameters stored in an instance in your model:

<xf:instance xmlns="" id="search-params">
    <data>
       <url>http://www.example.com/search.xq?q=</url>
       <q></q>
    </data>
</xf:instance>

XSLTForms does however support the xf:load function, and when you use xf:load with the xf:resource subelement you can achieve the same result.

For example within a trigger action you can use the following:

<xf:trigger>
   <xf:label>Search</xf:label>
   <xf:action ev:event="DOMActivate">
      <xf:load show="replace">
         <xf:resource value="concat( 'search.xq?q=', instance('search-params')/q )"/>
      </xf:load>
   </xf:action>
</xf:trigger>

Controlling Namespace Prefixes in the Output edit

XSLTForms can change the way that namespace prefixes in your instance data are managed. If you load an instance with a default namespace, all the submitted data will include a namespace prefix.

If your input is:

<task xmlns="http://www.example.com/task">
    <id/>
    <task-name/>
    <task-description/>
</task>

Then the saved output will become:

<task:task xmlns="http://www.example.com/task">
    <task:id/>
    <task:task-name/>
    <task:task-description/>
</task:task>

This can be controlled by the includenamespaceprefixes attribute of the submission element.

Bind Restrictions edit

With XSLTForms you can only have a single bind per element in your instance. So for example you can not put the data type in a separate bind as a required bind. It will generate an error, but will not indicate which element is bound twice.

Set Focus on Inserts edit

There is no way to set the focus on a new insert.

See Repeat Test 1

Upload file with XSLTForms edit

Versions of XSLTForms earlier than rev. 537 (April 2012) do not support the XForms Control upload.

For versions 537 and later, the XForms "upload" control is supported and behaves essentially as described in the XForms 1.1 specification.

For version earlier than 537, there is a work-around.

In case of submission method "xml-urlencoded-post" XSLTForms dynamically build a form whose ID is "xsltforms_form"

If this form still exists, XSLTForms replaces the value of the first child of the form by the serialization of the submitted instance content.

Example

Model

  <xf:model> 
   <xf:instance> 
    <data> 
     <comment>Upload one or two files</comment> 
    <data> 
   </xf:instance> 
   <xf:submission id="sub" method="xml-urlencoded-post" replace="all" action="load.xql"> 
    <xf:message level="modeless" ev:event="xforms-submit-error"> Submit error. </xf:message> 
   </xf:submission> 
  </xf:model>

Form

  <form name="upload" id="xsltforms_form" action="load.xql" enctype="multipart/form-data" method="post"> 
   <input type="hidden" name="postdata"/> 
   <input type="file" name="file1" style="width: 360px" />   
   <input type="file" name="file2" style="width: 360px" />     
  </form>

Submission

  <xf:submit submission="sub"> 
   <xf:label class="label">Send</xf:label> 
  </xf:submit>

XQuery load.xql

...
let $collection := "/db/my/docs"
let $f1 := request:get-uploaded-file-name("file1")
let $f2 := request:get-uploaded-file-name("file2")
let $data := request:get-parameter("postdata",())
let $o1 := if ($f1 ne "") then xdb:store($collection, xdb:encode-uri($f1), request:get-uploaded-file-data("file1")) else ()
let $o2 := if ($f2 ne "") then xdb:store($collection, xdb:encode-uri($f2), request:get-uploaded-file-data("file2")) else ()
...

This is a trick that enables uploading files within an XSLTForms application. File content is not loaded in instance.

References edit

Acknowledgements edit

Most of the above materials were generated with the kind assistance of the XSLTForms author Alain Couthures.


Back: Patching your Browser to Support XForms Next: Configuration File Editor

Section 2: Common Patterns edit

Application Modularity edit

Motivation edit

You want to be able to package and reuse XRX applications.

Method edit

We will use the collection structure of native XML databases to package XRX applications.

Our XRX Application framework uses the following conventions:

  • All applications are placed in a common collection called apps. This is similar to the "/Applications" folder on a Mac(TM) or the "Program Files" folder in Microsoft Windows(TM).
  • Each application has a collection of its own within the apps collection. We use REST-friendly application names that have only lowercase letters and dashes. For example a tool that manages organization business terms might be located in a collection called "/db/apps/terms". Databases that host multiple organizations might store each application collection in a separate apps collection for each organization. For example "/db/org/my-company/apps"
  • All user-manageable data that is contained within the application is contained with a "data" collection within the application collection.
  • XQuery functions that are common to multiple applications should be stored in a sibling folder to the "apps" collection. This allows each application to use relative module imports so that applications can be quickly re-installed on other sites.

Data Encapsulation edit

Motivation edit

There are several motivations for data encapsulation.

  • You want to make it clear to others what data an application manages.
  • You want to use the same encapsulation patterns of object-oriented systems.
  • You want to be able to prevent other applications from changing your data.
  • You want to use consistent role-based access control for your applications data.
  • You want to make it easy for all applications to share data management code for tasks such as ID assignment, versioning and linking.

Method edit

Each XRX application stores all its user-managed data in a sub-collection of the application called data. For example if your application manages business terms the collection path to the terms might be:

  /db/apps/terms/data

Each XRX application stores its own data. When this application's main collection is removed, all the data that it manages is also automatically removed by the simple nature of the collection structure.

Standard Views edit

Motivation edit

XRX applications need standards for viewing lists of items as well as HTML view of specific items.

Method edit

One of the first functions of most web applications is to provide some way to browse and view data. With XRX application we call these functions list-items and view-item. These are generic XQuery scripts that are located in a views sub-collection of the main application collection. View are by their name, read-only views of the XML data.

Listing Items edit

In general, the list-items function must show a brief summary of each record. It is usually displayed in a table view with each column helping to identify a record and distinguish this record from other records. The following are typical requirements of a list-items XQuery

  • Display a single line representation of a record
  • Display the records in a logical order
  • Display columns that help identify the record
  • Allow for pagination for large record sets
  • Provide links to key per-record functions such as viewing the full record, edit or delete

View Item edit

  • The view-item query provides a read-only view to a specific record. It required a parameter of the record identifier.

Searchability edit

Motivation edit

You want to make it easy for users to find data items with your XRX application.

Method edit

Each XRX application that keeps search-able data will have a sub-collection with search features in it. At a minimum this is a search form and a search query. In some cases the search query script is also used to render the search form.

Code Table Management edit

Motivation edit

You want to be able to manage all code-tables and selection lists in a consistent way across multiple applications.

Specifically:

  • XML files that are exported to external sources should not use numeric codes that are not descriptive of the semantics of a category.
  • Any codes that are passed as RESTful parameters should use URL-friendly codes (lowercase and dashes)
  • All user views of codes should use "labels" that may be contextual to a specific language
  • Role-based access should allow separate forms that only code-table administrators should have rights to modify

Method edit

One of the most tedious and time consuming parts of developing many XRX applications is effective management of all of the code-tables within an XML applications. Without a strategic framework to manage codes many projects bet bogged down with the details of inconsistent code-table management.

In general our approach is to treat code-tables and their own data sets. A separate collection which is a child of the main application collection called "code-tables" is used to manage code tables for that application.

Small code-tables are usually stored in XML files with one file per entity. These code table files store a list of all items for that code table including the labels, the value and the definition of that value. According to ISO/IEC 11179 guidelines all code values, like data elements, should have definitions that are approved by a data governance process.

Server Field Validation edit

Motivation edit

You have data in a form field that you want to validate on the server. For example, you want to validate that a zip code or postal code is valid. You want to perform the validation on the server, because loading the full data set into the web browser for validation would slow down the form loading time.

Method edit

Sending data to a remote server and getting the results back without reloading an entire form is a central feature of XForms. The beauty of XRX is that this can be done without writing a single line of JavaScript. We will demonstrate the technique using several components.

  1. We will create a server-side RESTful component that will return a simple "OK" message if the field passes the server side checks or an error message if it fails.
  2. We will add an action to the field that will detect if the value in that field changes. This can be done for each character the user types or when the user tabs out of the field.
  3. This action will trigger a submission to the server and return the result into the model. Both the input parameters to the RESTful service and the results of the service will be stored in separate instance in the model.

Sample Code edit

In this example we will check to see if data entered is a valid ZIP Code (a US postal code).

Here is the sample code from the form:

Input Field edit

In the following code we add an XForms action to the input. The event we trigger on is the DOMFocusOut which is triggered when the users tab out of the field. We perform two actions.

  1. We put the value in the outgoing instance using the xf:setvalue function.
  2. We trigger the RESTful web service.

Sample Code Fragement for Input Field and Action edit

<xf:input ref="zip-code" incremental="true">
    <xf:label>Zip Code:</xf:label>
    <xf:hint>Five digit zip code</xf:hint>
    <xf:action ev:event="DOMFocusOut">
        <!-- copy the value in the form to the outgoing submission instance -->
        <xf:setvalue ref="instance('zip-check')/zip-code" value="instance('save-data')/zip-code"/>
        <xf:send submission="zip-check-submission"/>
    </xf:action>
</xf:input>

Submission Element edit

Here is the additional submission element that is added to the XForms model. The method is HTTP GET which adds all of the parameters in our outgoing "zip-check" instance to the end of the URL. The action is the name of the XQuery script. In this case I use a relative path that is common if the XQuery check is in the same collection as the XQuery script that generates the XForms application.

<xf:submission id="zip-check-submission" method="get" action="zip-check.xq" 
       ref="instance('zip-check-submission')" 
       replace="instance" instance="zip-check-results" 
       separator=";"/>

Instances for Outgoing Value and Incoming Results edit

 <!-- store the outgoing query parameters for the zip check. -->
    <xf:instance id="zip-check">
        <data xmlns="">
            <zip-code/>
        </data>
    </xf:instance>
    
    <!-- a place to store the results of a check -->
    <xf:instance id="zip-check-results">
        <data xmlns=""/>
    </xf:instance>

This will cause the results of the Query to be URL paramters.

zip-check.xq?zip-code=55426

Sample Server-side XQuery edit

Sample Server-Side XQuery edit

xquery version "1.0";

let $file-path := '/db/org/syntactica/wiki-books/xrx/field-server-check/zip-codes.xml'
let $zip-codes := doc($file-path)/code-table/items/item/value

let $zip-code := request:get-parameter('zip-code', '')

return
<results>{
   if (some $item in $zip-codes satisfies ($zip-code = $item))
     then
        <message class="ok">OK</message>
     else
        <message class="error">{concat('zip-code: ', $zip-code, ' is not valid.')}</message>
}</results>

Sample Response for Correct Value edit

<results>
   <message class="ok">OK</message>
</results>
<results>
   <message class="error">zip-code: 55999 is not valid.</message>
</results>

Sample Data File edit

<code-table>
    <name>zip-code</name>
    <items>
        <item>
            <label>St. Louis Park, MN</label>
            <value>55426</value>
        </item>
        <item>
            <label>Mendota Heights, MN</label>
            <value>55118</value>
        </item>
        <item>
            <label>Minneapolis, MN</label>
            <value>55401</value>
        </item>
        <item>
            <label>Edina, MN</label>
            <value>55439</value>
        </item>
    </items>
</code-table>

Full XForms Example edit

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xf="http://www.w3.org/2002/xforms">
    <head>
        <title>Form Title</title>
        <style type="text/css"><![CDATA[
            
            @namespace xf url("http://www.w3.org/2002/xforms");
            body {
                font-family:Helvetica, sans-serif;
            }
            ]]>
            </style>
        <xf:model>

            <xf:instance id="save-data">
                <data xmlns="">
                    <zip-code>44526</zip-code>
                </data>
            </xf:instance>
            
            <xf:instance id="zip-check">
                <data xmlns="">
                    <zip-code>44526</zip-code>
                </data>
            </xf:instance>
            
            <!-- place to store the results of a the zip code -->
            <xf:instance id="zip-check-results">
                <data xmlns="">
                    <id/>
                </data>
            </xf:instance>

            
            
            <xf:submission id="zip-check-submission" method="get" action="zip-check.xq"
                ref="instance('zip-check')" replace="instance" instance="zip-check-results"
                separator=";"/>
            
        </xf:model>
    </head>
    <body>

        <xf:input ref="instance('save-data')/zip-code" incremental="true">
            <xf:label>Zip Code:</xf:label>
            <xf:hint>Five digit zip code.</xf:hint>
            <xf:action ev:event="DOMFocusOut">
                <!-- copy the value in the form to the outgoing submission instance -->
                <xf:setvalue ref="instance('zip-check')/zip-code"
                    value="instance('save-data')/zip-code"/>
                <xf:send submission="zip-check-submission"/>
            </xf:action>
        </xf:input>

        <xf:output ref="instance('zip-check-results')/message">
            <xf:label>Response:</xf:label>
        </xf:output>

        <xf:trigger submission="zip-check-submission">
            <xf:label>Check Zip Code</xf:label>
        </xf:trigger>
    </body>
</html>

Discussion edit

This technique can be employed even if the data sets are small or the validation checks simple. The form designer must consider the trade-off between storing a validation rule in the form and the overhead of going to and from a service. There are several variations of this pattern. One variation is using the "suggest" pattern to suggest one of several values as the user enters data. In this cast each character that changes may trigger a new list of possible values.

Breadcrumb Navigation Bar edit

Motivation edit

We want to provide a consistent way for users to navigate around in applications and their functions. To do this we provide a standard site navigation breadcrumb view.

Method edit

Each site style module has a breadcrumbs() function. This function looks at the context of the application in the site and conditionally displays each level of the site as a path of links.

To support this function we will need to create a function that displays the depth we are in the site. We call this the $style:web-depth-in-site variable.

Here is an example of how to calculate it:

declare function style:web-depth-in-site() as xs:integer {
(: if the context adds '/exist' then the offset is six levels.  If the context is '/' then we only need to subtract 5 :)
let $offset := 
   if ($style:web-context)
then 6 else 5
    return count(tokenize(request:get-uri(), '/')) - $offset
};

This function counts the number of "/" characters that appear in the current uri using the function get-uri(). It then does some offset calculations so that the root node of the site has a count of 1.

The $style:site-home is a path to the site home that can be renders in the context of the site. For example you might set it to:

  let $style:site-home := '/rest/db/org/my-org'

The Application ID and Application Name should also be set in the $style:app-id $style:app-name variables.

This can be extracted from URI and the app-info.xml file.

declare function style:breadcrumbs() as node() {
   <div class="breadcrumbs">
   
      { (: Check if Site Home should be displayed :)
      if ($style:web-depth-in-site gt 1) 
        then <a href="{$style:site-home}/index.xq">Site Home</a>
        else ()
      }
      
      { (: Check if Apps Link should be displayed :)
        if ($style:web-depth-in-site gt 2) then
             (' &gt; ' , <a href="{$style:site-home}/apps/index.xq">Apps</a>)
        else ()
      }
      
      { (: Check if App Name should be displayed :)
        if ($style:web-depth-in-site gt 3) then
             (' &gt; ' , 
             <a href="{$style:site-home}/apps/{$style:app-id}/index.xq">
             {$style:app-info/xrx:app-name/text()}
             </a>)
        else ()
      }
   </div>
};

Section 3: Sample Applications edit

Configuration File Editor edit

Motivation edit

You have an XML configuration file that only one person at a time will be editing. You want to make it easy to edit the configuration file so that non-technical users without knowledge of XML or occasional users will not make any mistakes editing the file.

Note that this method may not be appropriate if multiple people might edit the same file or record simultaneously. See the record locking section for more detail on this process.

Method edit

We will load the entire file into an single XForms instance, edit it, and save the entire file. This can be done with a single submission in the XForms application and a single store() operation in the eXist (or similar) database. This can be done even if the configuration file is very complex, and has many sections and many repeating elements. Regardless of the complexity of the file, you only need a single function call on the server to store the file.

Program Outline edit

Let's assume that the file is stored in a single eXist collection such as my-config.xml. To load this file into an XForms application, you simply put the document into an instance within the XForms model:

<html xmlns:xf="http://www.w3.org/2002/xforms">
   <head>
      <xf:model>
         <xf:instance src="my-config.xml" id="config"/>
      </xf:model>
   </head>
   <body>
   ...
   </body>
</html>

You can then save the file by adding a submission element to the model that will save the file back to the database once the data has changed in the client.

<xf:submission id="save" ref="config" method="post"/>
...
<xf:submit id="save">
   <xf:label>Save</xf:label>
</xf:submit>

Note that you will need to use "post" not "put" in this example. The submit element creates a button in your form with the label "Save".

Building the Forms Application for the Configuration File edit

There are many ways to "autogenerate" the XForms application that will edit the configuration file in a browser, even if your configuration file is complex and has many repeating elements.

One way is to use a transformation program that transforms an XML Schema to an XForms file. One example of this is the NIEM transform. Although there are other examples of this type of transform, most of these require you to have an XML Schema for your configuration file. If you do not have an XML Schema there are tools that can generate an XML Schema from one or more instance documents.

If you do not have an XML Schema, you can "draw" your XForms client using commercial XForms tools such as IBM Workplace Forms Designer. This drag-and-drop environment makes it easy for non-programmers to build and maintain complex forms.

If you are building a form on a budget, then another option is to use the Orbeon XForms Builder which is an XForms application that will build the form for you.

Client Side Save edit

If you have a secure Intranet you can use the HTTP PUT operator to save your configuration file directly to the web file system. Sometimes you will need to be able to authenticate a user before you permit the save. This can be done with a traditional login and session management system or you can create a single script that has the correct permissions.


Sample Save XQuery edit

xquery version "1.0";
declare namespace request="http://exist-db.org/xquery/request";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";

(: put the collection we want to save our data to here :)
let $my-collection := '/db/config-files'
let $my-config-file-name := 'my-config.xml'

(: get the data we want to update from the HTTP POST :)
let $my-posted-data := request:get-data()

(: make sure we have write access to this collection :)
let $login := collection($my-collection, 'my-userid', 'my-password')

let $store-return-status := xmldb:store($my-collection, $my-config-file-name, $my-posted-data)

(: this is what we return.  You can also return an XHTML file. :)
return
<return>
   <message>File {$my-config-file-name} saved in collection {$my-collection}</message>
   <result-code>{$store-return-status}</result-code>
</return>

Back: XSLTForms and eXist Next: Dictionary Editor

Dictionary Editor edit

Motivation edit

You want a simple application that saves one XML file per form. You want multiple users to each be editing individual records without conflict.

Design edit

We will put each term in a separate XML file, and the files will be numbered sequentially - 1.xml, 2.xml, and so on. Each file defines a single term, its acronym, and its definition.

Method edit

  1. Create a new eXist collection (aka Folder if you are using a WebDAV tool) called "dictionary"
  2. Create three collections called "data", "edit", and "views" within the "dictionary" collection.

data/1.xml edit

<Term>
   <id>1</id>
   <TermName>Hello</TermName>
   <TermDefinition>An informal greeting.</TermDefinition>
</Term>

data/2.xml edit

<Term>
   <id>2</id>
   <TermName>Howdy</TermName>
   <TermDefinition>An informal greeting used by cowboys.</TermDefinition>
</Term>

Your XForms application loads the data into an instance:

<xf:instance src="1.xml"/>

You pass the ID number to an XQuery "edit.xq" as a parameter in the URI, "id". "edit.xq" uses the ID parameter as a variable, "$id", to build the form.

Contents of edit/edit.xq edit


calling format: edit.xq?id=1


xquery version "1.0";
let $id := request:get-paramter(id, '')
return

<html>
...
<xf:instance src="{$id}.xml"/>
<xf:submission method="post" action="save.xq"/>
...
</html>

Contents of new-instance.xml edit

<Term>
   <id/>
   <TermName/>
   <TermDefinition/>
</Term>

Contents of save.xq edit

xquery version "1.0";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";

(: this is the collection where we store all the terms, one file per term :)
let $collection := '/db/dictionary/data'

(: this is where the form "POSTS" documents to this XQuery using the POST method of a submission :)
let $term := request:get-data()

(: this logs you into the collection :)
let $collection := xmldb:collection('/db/dictionary/data', 'mylogin', 'mypassword')

(: get the next ID :)
let $next-id := doc(concat($collection, 'edit/next-id.xml'))/next-id/text()
let $file := concat($next-id, 'xml')

(: this creates a new file using the next-id and saves the term into the file :)
let $store := store($collection, $file, $term)

(: now that the save has been done we can increment the next-id :)
let $update := update insert doc("/db/dictionary/edit/next-id.xml")/data/next-id/text() ($next-id + 1)

(: now put in the id in the file we just created :)
let $update := update insert doc( concat($collection, '/', $file) )/Term/id/text() $next-id

<results>
    <message>{$term/TermName/text(), $term/id/text()} has been saved.</message>
</results>

Contents of edit/next-id.xml edit

<data>
   <next-id>3</next-id>
</data>

Contents of views/list-terms.xq edit

As you save more and more terms, you will want to create a list of them. You can create an XQuery that list all terms. For each term you can include a link to view and edit each term.

xquery version "1.0";

let $collection := '/db/dictionary/data'
return
<html>
   <head>
      <title>Listing of Dictionary Terms</title>
   </head>
   <body>
   <h1>Dictionary Terms</h1>
   <ol>{for $term in collection($collection)/Term
      let $id := $term/id/text() 
      return
         <li>{$term/TermName/text()} : {$term/Defintion/text()}
             <a href="view-term.xq?id={$id}">View</a>
             <a href="../edit/edit.xq?id={$id}">Edit</a>
         </li>
   }</ol>
   </body>
</html>

Contents of edit/update.xq edit

xquery version "1.0";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";

(: update.xq :)

let $collection := '/db/dictionary/data'

(: this is where the form "POSTS" documents to this XQuery using the POST method of a submission :)
let $term := request:get-data()

(: this logs you into the collection :)
let $collection := xmldb:collection('/db/dictionary/data', 'mylogin', 'mypassword')

(: get the id out of the posted document :)
let $id := $term/id/text()
let $file := concat($id, '.xml')

(: this saves the new file and overwrites the old one :)
let $store := store($collection, $file, $term)

<results>
    <message>{$term/TermName/text(), $term/id/text()} has been updated.</message>
</results>

edit.xq Header edit

The edit.xq takes parameters from the URI (i.e. in the form "edit.xq?id=2") and either puts a new element in the instance or it puts an existing element in the instance.

xquery version "1.0";
declare option exist:serialize "method=html media-type=text/html indent=yes";

let $new := request:get-parameter('new', '')
let $id := request:get-parameter('id', '')

return
(: check for required parameters :)
if (not($new or $id))
   then 
      <error>
           <message>Parameter "new" and "id" are both missing.  One of these two arguments is required for this web service.</message>
      </error>
   else
      let $server-port := substring-before(request:get-url(), '/exist/rest/db/') 
      let $collection := '/db/dictionary/data'

      (: put in the appropriate file name :)
      let $file := if ($new)
         then ('new-instance.xml')
         else ( concat( $server-port, '/exist/rest', $collection, '/', $id, '.xml'))
      return

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xf="http://www.w3.org/2002/xforms" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:ev="http://www.w3.org/2001/xml-events" >
    <head>
       <title>Edit Term</title>
       <xf:model>
           <!-- this line loads either the new instance or the current data file into the form model -->
           <xf:instance xmlns="" src="{$file}"/>
       </xf:model>
    </head>
    <body>
       <xf:output ref="id">
           <xf:label>ID:</xf:label>
       </xf:output >
       <xf:input ref="TermName" class="TermName">
           <xf:label>Term:</xf:label>
       </xf:input>
       <xf:textarea ref="TermDefinition" class="TermDefinition">
           <xf:label>TermDefinition:</xf:label>
       </xf:textarea >
    </body>
</html>

xforms.css edit

The following file can be linked into the form for formatting. [And which file might that be?]


Back: Configuration File Editor Next: Regular Expression Builder

Regular Expression Builder edit

Motivation edit

You want to build a form that will allow you to quickly test replacement regular expressions using XQuery's replace function.

Method edit

We will use an XForms client that has several controls:

  1. a test input string
  2. a regular expression to evaluate
  3. a new string to be used in the replace
  4. a "submit" trigger (button)

When the submit trigger is pressed, the three strings are sent to the server and the XQuery replace() function will be executed. The result is an output text area that is returned from the server to the web browser.

This tool will allow you to do things such as test your URL-rewrite rules.

Screen Image edit

 
Replace Test Tool Before Submit
 
Replace Test Tool After Submit

Link to Working Application edit

Replace Test

Sample Program edit

<html 
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:ev="http://www.w3.org/2001/xml-events" >
    <head>
        <style type="text/css">
        @namespace xf url("http://www.w3.org/2002/xforms");
        body {font-family: Ariel, Helvetica, san-serif}

/* Input controls appear on their own lines. */
xf|input, xf|select, xf|select1, xf|textarea 
{display:block; margin:5px 0;}

/* Makes the labels right aligned in a 150px wide column that floats to the left of the input controls. */
xf|input > xf|label, xf|select > xf|label, xf|select1 > xf|label, xf|textarea > xf|label, xf|output > xf|label 
{font-weight: bold;text-align:right; padding-right:10px; width:150px; float:left; text-align:right;}

/* make the input boxes a little wider */
.xf-value {width: 200px}

       </style>
        <xf:model>
            <xf:instance id="request" xmlns="">
                <data>
                     <input>abcdefghi</input>
                     <pattern>def</pattern>
                     <replacement>123</replacement>
                </data>
            </xf:instance>
             <xf:instance id="response" xmlns="">
                <data/>
            </xf:instance>
            <xf:submission id="submit" method="get" 
                 action="http://localhost:8080/exist/rest/db/test/replace/replace.xq"
                 replace="instance" instance="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:model>
    </head>
    <body>
    <h1>XForms Replace Tester</h1>
        <xf:input ref="input">
            <xf:label>Input:</xf:label>
        </xf:input>
        <xf:input ref="pattern">
            <xf:label>Pattern:</xf:label>
        </xf:input>
        <xf:input ref="replacement">
            <xf:label>Replacement:</xf:label>
        </xf:input>
        <xf:switch>
           <xf:case id="ready">
           <xf:submit submission="submit">
              <xf:label>Submit</xf:label>
           </xf:submit>
            <xf:submit submission="echo-test">
              <xf:label>Echo Test</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">
              <xf:output ref="instance('response')/replace-result/text()">
                 <xf:label>Result:</xf:label>
              </xf:output>
           </xf:case>
        </xf:switch>
    </body>
</html>

XQuery Replace Tester edit

If you are using eXist, just place this file on the server in the same folder as your XForms test driver.

In the example above I used a test folder on the localhost:

http://localhost:8080/exist/rest/db/test/replace/replace.xq

xquery version "1.0";
declare namespace exist = "http://exist.sourceforge.net/NS/exist"; 
declare namespace system="http://exist-db.org/xquery/system";
declare namespace request="http://exist-db.org/xquery/request";
declare option exist:serialize "method=xml media-type=text/xml indent=yes";
(: replace demo :)
let $input := request:get-parameter('input', '')
let $pattern  := request:get-parameter('pattern', '')
let $replacement := request:get-parameter('replacement', '')

return
<results>
   <input>{$input }</input>
   <pattern>{$pattern}</pattern>
   <replacement>{$replacement}</replacement>
   <replace-result>{replace($input , $pattern, $replacement)}</replace-result>
</results>

Discussion edit

This shows that you can quickly build tools to teach yourself complex functions like regular expression handling. You can also use the XQuery match function, which returns true or false if the regular expression matches an input string.

There are two variations of this example that are interesting. The first is where you replace the input form with a large text area for doing global replacements of large blocks of text. The second is where you replace the input box with a selection list with common replacement patterns.

References edit

Examples of replacement functions can be found here:

w3c replace examples


Back: Dictionary Editor Next: Autoincrement File ID

Autoincrement File ID edit

Motivation edit

You want to create a new file and automatically create a new identifier for the file.

Method edit

Create an XML file with a single counter in it. Use this counter as part of the file name. After you confirm that the file has been saved, increment the counter using the update function.

Sample XML File to Hold Next ID edit

<data>
   <next-id>47</next-id>
</data>

Sample Code for save-new.xq edit

The following example works with an incoming HTTP POST request.

xquery version "1.0";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";
declare namespace xmldb = "http://exist-db.org/xquery/xmldb";
declare namespace request="http://exist-db.org/xquery/request";

declare option exist:serialize "method=html media-type=text/html indent=yes";

(: save a new task - filename: save-new.xq - change base path if this changes. :)

(: this only works with POST data :)
let $my-doc := request:get-data()

(: the base directory URL for this XQuery.  Use this to put links in the result page. :)
let $base-path := substring-before(request:get-url(), '/save-new.xq')

(: these paths must start with '/db' and are used for passing to doc() :)
let $data-collection := '/db/app/data'
let $next-id-file-path := '/db/app/edit/next-id.xml'

let $next-id := doc($next-id-file-path)/data/next-id/text()

(: use this as an arugment for for store command :)
let $new-term-base-file-name := concat($next-id, '.xml')

(: use this for doc :)
let $new-term-file-path := concat($data-collection, '/', $new-term-base-file-name)

(: add this line for testing in your local system or if you don't have group or RBAC set up :)
let $login := xmldb:collection($data-collection, 'mylogin', 'mypassword')

(: store the new document in the given collction :)
let $store-return-status := xmldb:store($data-collection, $new-term-base-file-name, $my-doc)

(: increment the next-id by one for the next document.  You can also use "update value" :)
let $retCode1 := update replace doc($next-id-file-path)/data/next-id/text()
                        with ($next-id + 1)
(: note that next-id is now the next item so the current is next-id -1 :)

(: update the ID in the new document.  Note that the saved document must have
the ID in the path /app/id. :)
let $retCode2 :=  update replace doc($new-term-file-path)/app/id with <id>{$next-id - 1}</id>

return
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
       <title>Save Confirmation</title>
    </head>
    <body>
       <h1>New file has been saved. id={$next-id - 1}.</h1>
   </body>
</html>

Taking into account concurrent access edit

Same as above but with locking the node holding the identifier.

The following example works with an incoming HTTP POST request.

xquery version "1.0";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";
declare namespace xmldb = "http://exist-db.org/xquery/xmldb";
declare namespace request="http://exist-db.org/xquery/request";

declare option exist:serialize "method=html media-type=text/html indent=yes";

(: save a new task - filename: save-new.xq - change base path if this changes. :)

(: this only works with POST data :)
let $my-doc := request:get-data()

(: the base directory URL for this XQuery.  Use this to put links in the result page. :)
let $base-path := substring-before(request:get-url(), '/save-new.xq')

(: these paths must start with '/db' and are used for passing to doc() :)
let $data-collection := '/db/app/data'
let $next-id-file-path := '/db/app/edit/next-id.xml'

(: Get next-id current value and increment the node value with an exclusive lock of the node :)
let $next-id := util:exclusive-lock(
    doc($next-id-file-path)/data/next-id,
    let $current-id := doc($next-id-file-path)/data/next-id/text()
    let $retCode := update replace doc($next-id-file-path)/data/next-id/text() with ($current-id + 1)
    return ($current-id - 1))                      
(: Warning - Pitfall : $current-id evaluation changes after the update :)

(: use this as an arugment for for store command :)
let $new-term-base-file-name := concat($next-id, '.xml')

(: use this for doc :)
let $new-term-file-path := concat($data-collection, '/', $new-term-base-file-name)

(: add this line for testing in your local system or if you don't have group or RBAC set up :)
let $login := xmldb:collection($data-collection, 'mylogin', 'mypassword')

(: store the new document in the given collction :)
let $store-return-status := xmldb:store($data-collection, $new-term-base-file-name, $my-doc)

(: update the ID in the new document.  Note that the saved document must have
the ID in the path /app/id. :)
let $retCode2 :=  update replace doc($new-term-file-path)/app/id with <id>{$next-id}</id>

return
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
       <title>Save Confirmation</title>
    </head>
    <body>
       <h1>New file has been saved. id={$next-id}.</h1>
   </body>
</html>

Adding and Incrementing an Attribute edit

Note that the syntax for inserting an attribute is the following:

  update insert attribute {$attrName} {$attrValue} into expression

The keyword attribute must be used and be followed with the attribute name and value.

If you want to store a new id in an attribute the syntax is:

  update insert attribute {'id'} {$next-id} into $doc/root

Note: if the attribute already exists in the target node then insert works as replace

And if you are updating the id you can use the replace function with the @ in the path expression:

  update replace doc/root/@id with ($next-id + 1)

Back: Regular Expression Builder Next: Move a Resource

Move a Resource edit

Motivation edit

You want to create an XForms front end to an XQuery script that will move a resource from one collection to another collection or you want to move a collection from one location to another location.

Method edit

We will build a simple XForms front end to the xmldb:move() operator. The syntax of the move operators is the following:

xmldb:move($from-collection, $to-collection, $resource)

Where:

  $from-collection is the path name to the collection you are moving the file from
  $to-collection is the path name to the collection you are moving the file to
  $resource in the name of the resource

If you are going to move a complete collection the format is:

xmldb:move($old-collection, $new-collection)

On this case the operation:

  xmldb:move('/db/bar', '/db/foo')

would result in the bar collection being located inside the foo collection:

  /db/foo/bar

To test this application create two test collections in your eXist database such as:

  /db/from-collection
  /db/to-collection

Sample XForms Client edit

Create the following xhtml file:

<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms" 
xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:ev="http://www.w3.org/2001/xml-events">
    <head>
        <title>Move File</title>
        <style language="text/css">
       <![CDATA[
       @namespace xf url("http://www.w3.org/2002/xforms");
         .from .xf-value  {width: 40ex;}
         .to .xf-value  {width: 40ex;}
       ]]>
       </style>
       <xf:model>
           <xf:instance xmlns="">     
               <data>
                   <from>/db/from-collection</from>
                   <to>/db/to-collection</to>
                   <resource>foo.xml</resource>
               </data>
           </xf:instance>
            <xf:submission id="save" method="get" 
               action="move.xq"
               separator="&amp;"
               replace="all"/>
        </xf:model>
    </head>
    <body>
        <h3>Move Resource</h3>
         <xf:input ref="from" class="from">
            <xf:label>From Collection:</xf:label>
        </xf:input>
        <xf:input ref="to" class="to">
            <xf:label>To Collection:</xf:label>
        </xf:input>
       <xf:input ref="resource" class="resource">
            <xf:label>Resource:</xf:label>
        </xf:input>
        <xf:submit submission="save">
            <xf:label>Move File</xf:label>
        </xf:submit>
    </body>
</html>

Sample XQuery on Server edit

The following XQuery named "move.xq" should be placed in the same collection as the move.xhtml file.

xquery version "1.0";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";

(: this logs you in and makes sure you have write access to the desitination folder :)
let $login := xmldb:collection('/db/to-collection', 'username', 'password')

let $from := request:get-parameter('from', '')
let $to := request:get-parameter('to', '')
let $resource := request:get-parameter('resource', '')

let $code := xmldb:move($from, $to, $resource)

return
<html>
   <head>
      <title>Move Result</title>
   </head>
   <body>
   <b>from:</b>{$from}<br/>
   <b>to:</b>{$to}<br/>
     <b>resource:</b>{$resource}<br/>
     <b>result code:</b>Result code is
     </body>
</html>

Move a Collection edit

xquery version "1.0";
declare option exist:serialize "method=xhtml media-type=text/html omit-xml-declaration=yes indent=yes";

(: this logs you in and makes sure you have write access to the destination folder :)
let $login := xmldb:login('/db', 'user-id', 'your-password')
 
let $from := request:get-parameter('from', '')
let $to := request:get-parameter('to', '')

(: You can put in checks to make sure that both from and to are not null here and that they both exist. :)
 
let $move-result-code := xmldb:move($from, $to)
 
return
<html>
   <head>
      <title>Move Collection Result</title>
   </head>
   <body>
      <h1>Move Collection Result</h1>
      <b>from:</b>{$from}<br/>
      <b>to:</b>{$to}<br/>
     <b>result code:</b>Result code is:{$move-result-code}
     </body>
</html>

Discussion edit

Now that you have the core move code done, you can enhance the program to browse to a specific collection for the source and browse to a specific collection for the destination. You can also create a listing of the resources in the source and destination folders.


Back: Autoincrement File ID Next: Save File Dialog

Save File Dialog edit

Motivation edit

You want to allow the user to save form data into a named file in an eXist collection that will not conflict with existing files.

Method edit

When the user selects a Save button we will use switch/case to open up a save dialogue. In this dialogue we will list all of the current files in a collection and allow the user to create a new file with a name that does not conflict with any existing file names. If the user selects a file from the list, we will replace the current file name with that file name.

Using the get-child-resources Function edit

eXist provides a function that lists the files in a collection. You can get a sorted list of all the files in a collection with the following XQuery

xquery version "1.0";
declare namespace xdb="http://exist-db.org/xquery/xmldb";
declare option exist:serialize "method=xml media-type=text/xml indent=yes";

let $collection := request:get-parameter('collection', '/db/test')

return
<files>{
         for $child in xdb:get-child-resources($collection)
         order by lower-case($child)
         return
        <file>{$child}</file>
}</files>

When executed, this XQuery will return the files in a collection. For example here is the URL of an XQuery execution with the collection parameter passed as an argument:

list-files-in-collection.xq?collection=/db/rss

<files>
   <file>news.rss</file>
   <file>test.rss</file>
</files>

We can then use this XML listing to display a list of existing files and allow the user to select a name that is not on this list.

Sample Program edit

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xf="http://www.w3.org/2002/xforms">
    <head>
        <title>Save File Dialog</title>
        <link type="text/css" rel="stylesheet" href="save-panel.css" />

        <xf:model>
            
            <!-- This is the data that we want to save to a file on the server. -->
            <xf:instance id="my-data" xmlns="">
                <data>
                    <element1>Element 1</element1>
                    <element2>Element 2</element2>
                    <element3>Element 3</element3>
                    <!-- This is the file name that we are going to save the data into. -->
                    <filename>my-filename.xml</filename>
                </data>
            </xf:instance>
            
            <!-- Placeholder for the list of files in the target collection. -->
            <xf:instance xmlns="" id="list-files-results">
                <data/>
            </xf:instance>
            
            <!-- This does the save. The server-side script must look for the filename element to do the save
                to the correct file.  -->
            <xf:submission id="save" method="post" action="save.xq" ref="my-data" replace="all" />
            
            <!-- Gets the list of current files in the server-side collection and puts them in the model. -->
            <xf:submission id="list-files" method="post" action="list-files.xq" replace="instance" instance="list-files-results" />
            
         </xf:model>
    </head>
    <body>
        <h1>Example of Save File Panel</h1>
        <xf:input ref="element1">
            <xf:label>Element 1:</xf:label>
        </xf:input>
        <br/>
        <xf:input ref="element2">
            <xf:label>Element 2:</xf:label>
        </xf:input>
        <br/>
        <xf:input ref="element3">
            <xf:label>Element 3:</xf:label>
        </xf:input>
        <br/>
        <xf:switch>
            <xf:case id="default">
                <xf:submit submission="save">
                    <xf:label>Save As...</xf:label>
                    <xf:toggle case="save-dialog" ev:event="DOMActivate" />
                </xf:submit>
            </xf:case>
            <xf:case id="save-dialog">
                <xf:action ev:event="xforms-select">
                    <xf:send submission="list-files" />
                </xf:action>
                
                <!-- This turns all the files into buttons that can be selected to select a specific file. -->
                <xf:repeat nodeset="instance('list-files-results')/file" class="list-files" id="list-files-repeat">
                    <xf:trigger appearance="minimal">
                        <xf:label>
                            <xf:output ref="." />
                        </xf:label>
                        <xf:action ev:event="DOMActivate">
                            <!-- Set the filename that we will save to to the value under the selected item. -->
                            <xf:setvalue
                                ref="instance('my-data')/filename"                        
                                value="instance('list-files-results')/file[index('list-files-repeat')]" />
                        </xf:action>
                    </xf:trigger>
                </xf:repeat>
                
                
                <xf:input ref="instance('my-data')/filename">
                    <xf:label>File Name:</xf:label>
                </xf:input>
                <br/>
                <xf:submit submission="list-files">
                    <xf:label>Refresh</xf:label>
                </xf:submit>
                <xf:submit submission="save">
                    <xf:label>Save</xf:label>
                </xf:submit>
            </xf:case>
        </xf:switch> 
    </body>
</html>

CSS File edit

@namespace xf url("http://www.w3.org/2002/xforms");

body {font-family: Helvetica, sans-serif;}

.list-files {
   font-size: .8em;
   color: blue;
   background-color: white;
   border: 1px solid black;
   padding: 5px;
   }

Discussion edit


Back: Move a Resource Next: Login and Session Management

Login and Session Management edit

Motivation edit

You want to use an XForms application to get login information and set server session variables.

Method edit

The XForms standard has a "secret" attribute for collecting a password. After the user fills out the login form, it is POSTed to the server.

Note that you should not use HTTP GET for passwords since the passwords will appear in the web log files as a URL parameter.

The eXist system has several functions for setting session variables. After a user logs in these session variables should be set and all subsequent XQueries can use these session variables when accessing secure resources.

Note that setting session variables is out-of-scope of the W3C XQuery 1.0 standard and each server may use slight variations of these functions. But the concepts should be very similar.

Most commonly, a session variable is used to associate the user to one or more roles. This is known as role-based access control (RBAC). This allows your XQueries to set conditional behavior based on the user's role, and avoids having to hard-code XQueries based on usernames that may change frequently. A typical role is the "admin" role or the "document-approver" role. eXist uses a UNIX style group that can be associated with a collection or a file. You can use these groups for security if you note that a collection or file can be associated with one-and-only-one group at any time. Users are frequently associated with multiple roles during a session, just as in UNIX a user can be in many groups.

Sample XForms Login Screen edit

A sample login XForms application is provided here:

XForms Login Screen

This form may be customized to put in any legal disclaimers about the use of unauthorized systems. It can then be wrapped in an XQuery function such as local:display-login() and invoked if the user is accessing a page that they do not have authorization to access.

Sample XQuery to Check for Login edit

(: This is juat a rough outline based on the admin.xql program.  Needs more work... :)

let $user := xdb:get-current-user()
let $pass := local:get-pass()
let $logout := local:get-login()

let $isLoggedIn :=  if($user eq "guest")
   then
    (
    (: is this a login attempt? :)
        if($user and not(empty($pass)))
    then
        (
         if($user eq "guest")
          then
            (
                (: prevent the guest user from accessing the admin webapp :)
                false()
            )
            else
            (
                (: try and log the user in :)
                xdb:login("/db", $user, $pass)
            )
        )
        else
        (
            (: prevent the guest user from accessing the admin webapp :)
            false()
        )
    )
    else
    (
        (: if we are already logged in, are we logging out - i.e. set permissions back to guest :)
        if  ($logout)
           then
        (
        	let $null := xdb:login("/db", "guest", "guest") return
        	    false()
        )
        else
        (
             (: we are already logged in and we are not the guest user :)
            true()
        )
    )
return
<html>
...
</html>

XQuery Function to display session information edit

Once the user is logged in, the following function can be used to display session information in the upper-right corder of the screen.

(: 
    Display the version, SVN revision and user info in the top right corner 
:)
declare function admin:info-header() as element()
{
    <div class="info">
        <ul>
            <li>Version: { util:system-property( "product-version" ) }</li>
            <li>SVN Revision: { util:system-property( "svn-revision" ) }</li>
            <li>Build: {util:system-property("product-build")}</li>
            <li>User: { xdb:get-current-user() }</li>
        </ul>
    </div>
};

Back: Save File Dialog Next: File Locking

File Locking edit

Motivation edit

You want to put systems in place to avoid the problem of concurrent users writing over each other changes.

Consider the following scenario. Bob loads the data to a configuration file into his client XForms application, makes half of his changes and then goes out to lunch before he does a save back to the server. During lunch Alice opens the same data in her XForms client, makes some changes, and does a save. When Bob gets back from lunch he does a save on the same data and wipes out all of Alice's updates.

This is known as the "Lost Update Problem" and is common when you have many users that may be concurrently editing the same data.

Two Strategies for Managing Client Locks edit

There are many strategies to solve this problem, and each of them have some design limitations.

In general you will find two separate locking strategies which can be used together or independently:

The first strategy is to do checksums on the data when you move the data to the client. When you save, you recalculate the checksum on the data and make sure it has not changed. You can also put the last modified timestamp in the client and warn the user if the file has changed since they started editing it. The etag element within the HTML header is frequently used to hold an md5 checksum of the data.

The second strategy is to set some state on the server when a resource is edited and provide reasonable timeouts if the user does not do a save. This method is always more complex because you must deal with unlocking resources and this often involves tools and reports to view locked files.

The strategy presented here is to place a lock on the file when an Edit form is used and unlock the file when a save is performed.

eXist provides some simple functions to lock a file. This lock can be used to prevent the second user from opening up an edit session. The drawback to this approach is that if a file is locked and the user does not do a save, then a tool must be created to permit records to be manually unlocked.

eXist Locking Functions edit

Here are some of the locking functions you can use on eXist:

util:exclusive-lock($a as node()*, $b as item()*) item()*
Puts an exclusive lock on the owner documents of all nodes in the first argument $a. Then evaluates the expressions
in the second argument $b and releases the acquired locks aftertheir completion.
util:shared-lock($a as node()*, $b as item()*) item()*
Puts a shared lock on the owner documents of all nodes in the first argument $a. Then evaluates the expressions
in the second argument $b and releases the acquired locks aftertheir completion.
xmldb:document-has-lock($a as xs:string, $b as xs:string) xs:string?
Returns the name of the user that holds a write lock on the resource specified in $b in the collection $a.
If no lock is in place, the empty sequence is returned. The collection can be passed as a simple collection path or an XMLDB URI.

References edit


Back: Login and Session Management Next: Selection List Generator

Selection List Generator edit

Motivation edit

You want to store all your code tables in a central location and have each form dynamically generate selection lists from these code tables.

Method edit

We will create a very simple XML file format to store all our codes. We will store one code table per file and then write a simple XQuery that goes through all these files to build a sample selection list. This selection list will create an instance in the model to hold each selection list value. It will also populate the instance with the first value in the list.

In our sample, we will assume an XRX file naming standard such as /db/apps/app-name/code-tables/my-code-table.xml where each application contains its own code tables for maximum application portability between systems. Applications that share code tables can store these in a location such as /db/shared/code-tables/my-code-table.xml

Sample Screen Image edit

 
Output from Selection List Generator

XML File Format edit

<code-table>
    <code-table-name>PublishStatusCode</code-table-name>
    <definition>A way to classify the publishing workflow of an item.</definition>
    <items>
        <item>
            <label>Draft</label>
            <value>draft</value>
        </item>
        <item>
            <label>Under Review</label>
            <value>under-review</value>
        </item>
        <item>
            <label>Published</label>
            <value>published</value>
        </item>
    </items>
</code-table>

Sample XQuery edit

This query goes through all the files in the XRX application code-tables collection and looks for XML files that have code-table as the root element. It then creates a report that contains all the selection lists inside a working XForms application.

xquery version "1.0";
import module namespace style ='http://code.google.com/p/xrx/style' at '/db/xrx/modules/style.xqm';
declare namespace xhtml="http://www.w3.org/1999/xhtml";

(: XQuery to construct an XForm for either a new item or update item :)
declare option exist:serialize "method=xhtml media-type=application/xhtml+xml indent=yes"; 

let $app-collection := style:app-base-uri()
let $code-table-collection := concat($app-collection, '/code-tables')
let $code-tables := collection($code-table-collection)/code-table

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>XForms Selection List Tester</title>
      {style:import-css()}
      <link type="text/css" rel="stylesheet" href="block-form.css"/>
      <xf:model>
         <!-- This instance holds the value of each code -->
         <xf:instance xmlns="" id="save-data" src="">
            <data>
               {for $code-table in $code-tables
                 return
                    element {$code-table/code-table-name/text()} {$code-table/items/item[1]/value/text()}}
            </data>
         </xf:instance>
      </xf:model>
   </head>
   <body>
      {style:header()}
      {style:breadcrumb()}
      <h1>Sample with a Model in the XForm</h1>
      {for $code-table in $code-tables
         let $code-table-name := $code-table/*:code-table-name/text()
      return
         <xf:select1 ref="{$code-table-name}">
            <xf:label>{$code-table-name}: </xf:label>
            {for $item in $code-table/*:items/*:item
              return
                 <xf:item>
                    <xf:label>{$item/*:label/text()}</xf:label>
                    <xf:value>{$item/*:value/text()}</xf:value>
                 </xf:item>}
            <xf:hint>{$code-table/*:definition/text()}</xf:hint>
         </xf:select1>
      }
      {style:footer()}
   </body>
</html>

Note that this XQuery uses element constructors to create element names in the instance. It also uses the *:name notation to put data from the null namespace directly into the XForms namespace.

This file also puts the definition of the element directly into the hits of the XForms application. Under some XForms applications such as Firefox the hint appears in a floating ephemeral mode display in the left margin of the form.

Sample CSS File edit

Here is the block-form.css file that can be used to make each selection appear on a separate line:

@namespace xf url("http://www.w3.org/2002/xforms");
body {font-family: Arial, Helvetica; sans-serif;}

/* This line ensures all the separate input controls appear on their own lines */
xf|output, xf|input, xf|select, xf|select1, xf|textarea {display:block; margin:5px 0;}

/* Makes the labels right aligned in a column that floats to the left of the input controls. */
xf|select > xf|label,
xf|select1 > xf|label
{text-align:right; padding-right:10px; width:250px; float:left;}

Back: File Locking Next: Glossary Term Editor

Glossary Term Editor edit

Motivation edit

You want a tool to manage your organization-specific business terms including definitions, acronyms, synonyms, broader/narrower terms, see-also terms, definition sources, approval history, versions, and traceability and mappings to ISO/IEC 11179 data elements.

Method edit

Create a tool that manages individual business terms using a vocabulary similar to the W3C Simple Knowledge Organization SKOS draft standard. Use one XML file in an eXist collection for each term and have the server assign an ID number as each term is created. Allow each term to belong to one synonym set. We will associate each term with broader terms and use XQuery reports to infer narrower terms.

Design Steps edit

To build the glossary we can create an XML Schema for each term and generate an XForms application to manage the Term data. We can then hand-edit the generated form to add specific functionality.

New Instance File edit

<Term>
   <id/>
   <TermName/>
   <Acronym/>
   <SynonymID/>
   <Definition/>
   <SeeAlso/>
   <Broader/>
   <PrimarySourceCode/>
   <PrimarySourceOtherText/>
   <Approvals>
      <ApprovalStatusCode/>
      <AssignedToCode/>
      <ApprovedByCode/>
      <ApprovalDate/>
   </Approvals>
   <GroupStarredItemIndicator/>
   <ProjectCode/>
   <ClassifierCode/>
   <Tag/>
   <TermNoteText/>
   <DataElement>
      <DataElementIndicator/>
      <DataElementDerivedIndicator/>
      <DataElementDerivationRuleText/>
      <DataElementIdentifierIndicator/>
      <DataElementEnumerationsText/>
      <DataElementValidationRulesText/>
      <DataElementName/>
   </DataElement>
</Term>

Term XML Schema File edit

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
   <xs:element name="Term">
      <xs:annotation>
         <xs:documentation>A single business term in organization specific business glossary. v0.06</xs:documentation>
      </xs:annotation>
      <xs:complexType>
         <xs:sequence>
            <xs:element name="id">
               <xs:annotation>
                  <xs:documentation>The term identifier. Usually a small integer assigned upon creation.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="TermName" type="xs:string">
               <xs:annotation>
                  <xs:documentation>The name of a term in the glossary.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="Acronym" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>Optional acronym or abbreviation if used for the term.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="SynonymID" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>A pointer to a synonym set.  Each synonym set contains a list of approximately equivalent terms and one of those terms is a preferred term.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="Definition">
               <xs:annotation>
                  <xs:documentation>A short, precise, non-circular (you cannot say a widget is a widget) definition for this term that clearly differentiates this term from other terms.</xs:documentation>
               </xs:annotation>
               <xs:simpleType>
                  <xs:restriction base="xs:string">
                     <xs:maxLength value="1000"/>
                     <xs:minLength value="5"/>
                  </xs:restriction>
               </xs:simpleType>
            </xs:element>
            <xs:element name="SeeAlso" type="xs:string" minOccurs="0" maxOccurs="unbounded">
               <xs:annotation>
                  <xs:documentation>Other terms related to this term other that synonyms.  May be hyperlinked.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="Broader" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>A concept that is more general in meaning.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="PrimarySourceCode" type="PrimarySourceCodeType" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>The primary source of the definition.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="PrimarySourceOtherText" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>A text description of the primary source if it is not in one of the PrimarySourceCode.  You must have a other text if PrimarySourceCode is 'other'</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="Approvals" minOccurs="0">
               <xs:complexType>
                  <xs:sequence>
                     <xs:element name="ApprovalStatusCode" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>An approval status such as draft, assigned to review team or approved.</xs:documentation>
                        </xs:annotation>
                        <xs:simpleType>
                           <xs:restriction base="xs:string">
                              <xs:enumeration value="initial-draft"/>
                              <xs:enumeration value="assigned-to-review-team"/>
                              <xs:enumeration value="project-approved"/>
                              <xs:enumeration value="obsolete"/>
                           </xs:restriction>
                        </xs:simpleType>
                     </xs:element>
                     <xs:element name="AssignedToCode" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>The person (BA) that this term is assigned to faciliatate approval.  A code table is used to select the person's name.</xs:documentation>
                        </xs:annotation>
                        <xs:simpleType>
                           <xs:restriction base="xs:string">
                              <xs:enumeration value="Alice"/>
                              <xs:enumeration value="Ann"/>
                              <xs:enumeration value="Bob"/>
                              <xs:enumeration value="John"/>
                              <xs:enumeration value="Fred"/>
                              <xs:enumeration value="Peg"/>
                              <xs:enumeration value="Sue"/>
                           </xs:restriction>
                        </xs:simpleType>
                     </xs:element>
                     <xs:element name="ApprovedByCode" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>Name of the person in the business unit that approved this data element.</xs:documentation>
                        </xs:annotation>
                        <xs:simpleType>
                           <xs:restriction base="xs:string">
                              <xs:maxLength value="250"/>
                           </xs:restriction>
                        </xs:simpleType>
                     </xs:element>
                     <xs:element name="ApprovalDate" type="xs:date" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>The date the data element was approved.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                  </xs:sequence>
               </xs:complexType>
            </xs:element>
            <xs:element name="GroupStarredItemIndicator" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>An indication that this item is of interest to a specific user.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="ProjectCode" minOccurs="0" maxOccurs="unbounded">
               <xs:annotation>
                  <xs:documentation>The projects that this term is associated with.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="ClassifierCode" minOccurs="0" maxOccurs="unbounded">
               <xs:annotation>
                  <xs:documentation>A code used to filter data elements.  The user interface is a selection list that reads an external code table of filters.</xs:documentation>
               </xs:annotation>
               <xs:simpleType>
                  <xs:restriction base="xs:string">
                     <xs:enumeration value="Adjust"/>
                     <xs:enumeration value="Entity"/>
                     <xs:enumeration value="IRS"/>
                     <xs:enumeration value="Legal"/>
                     <xs:enumeration value="MM"/>
                     <xs:enumeration value="Process"/>
                     <xs:enumeration value="Product"/>
                     <xs:enumeration value="Program-Project"/>
                     <xs:enumeration value="System"/>
                     <xs:enumeration value="Technical"/>
                  </xs:restriction>
               </xs:simpleType>
            </xs:element>
            <xs:element name="Tag" minOccurs="0" maxOccurs="unbounded">
               <xs:annotation>
                  <xs:documentation>A keyword associated with this term.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="TermNoteText" type="xs:string" minOccurs="0">
               <xs:annotation>
                  <xs:documentation>Any additional notes about this term.  Keep your definitions short by using this text area.</xs:documentation>
               </xs:annotation>
            </xs:element>
            <xs:element name="DataElement">
               <xs:complexType>
                  <xs:sequence>
                     <xs:element name="DataElementIndicator">
                        <xs:annotation>
                           <xs:documentation>Set to true if this is a formal data elment that should be registered by the metadata registry.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                     <xs:element name="DataElementDerivedIndicator" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>An indication that the element is derived from other data elements.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                     <xs:element name="DataElementDerivationRuleText" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>A textual description of the rule to derive this data element.  Use names of other data elements if possible.  This field is required if the DataElementDerivedIndicator is true.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                     <xs:element name="DataElementIdentifierIndicator" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>An indication that this data element identifies an instance within an indetification scheme.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                     <xs:element name="DataElementEnumerationsText" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>A textual description of all the data element enumerations including codes, ranges and definitions with distinction definitions for each code and range of codes.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                     <xs:element name="DataElementValidationRulesText" minOccurs="0">
                        <xs:annotation>
                           <xs:documentation>A textual description of any validation rules used to check this data element.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                     <xs:element name="DataElementName" minOccurs="0" maxOccurs="unbounded">
                        <xs:annotation>
                           <xs:documentation>The ISO name (namespace prefix, ObjectPropertyTerm) of a data element in a metadata registry.</xs:documentation>
                        </xs:annotation>
                     </xs:element>
                  </xs:sequence>
               </xs:complexType>
            </xs:element>
         </xs:sequence>
      </xs:complexType>
   </xs:element>
   <xs:complexType name="DataElementType">
      <xs:sequence/>
   </xs:complexType>
   <xs:simpleType name="PrimarySourceCodeType">
      <xs:restriction base="xs:string">
         <xs:enumeration value="enterprise-glossary"/>
         <xs:enumeration value="sales-glossary"/>
         <xs:enumeration value="hr-glossary"/>
         <xs:enumeration value="other"/>
      </xs:restriction>
   </xs:simpleType>
</xs:schema>

XForms Application edit

This is an XQuery that when run with a parameters of new=true or an id=123 will generate an XForms application. It will need to be in the same folder as a new-instance.xml and it will need a save.xq and an update.xq to do the work of saving new instances (with the ids assigned) and updating existing instances, respectively. The update.xq will be responsible for versioning and archiving old values. The update script can also be responsible for creating log files of who changed what files and when.

Note: I have not fixed this yet to use the new pathnames for the Approval and DataElement containers.

xquery version "1.0";
declare namespace exist = "http://exist.sourceforge.net/NS/exist"; 
declare namespace system="http://exist-db.org/xquery/system";
declare namespace request="http://exist-db.org/xquery/request";
import module namespace cms = "http://cms.metadata.dmccreary.com" at "/db/mdr/cms/cms-module.xq";
declare option exist:serialize "method=xhtml media-type=text/xml indent=yes";

let $new := request:get-parameter('new', '')
let $id := request:get-parameter('id', '')
let $form := 'Glossary Editor'
let $form-version := '0.08'

return

(: check for required parameters :)
if (not($new or $id))
    then (
    <error>
        <message>Parameter "new" and "id" are both missing.  One of these two arguments is required for this web service.</message>
    </error>)
    else (
        let $server-port := substring-before(request:get-url(), '/exist/rest/db/') 
        let $collection := '/db/mdr/glossaries/data/pace/'
(: put in the appropriate file name :)
let $file := if ($new)
   then ('new-instance.xml')
   else ( concat( $server-port, '/exist/rest/', $collection, $id, '.xml'))
return
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xf="http://www.w3.org/2002/xforms" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:ev="http://www.w3.org/2001/xml-events" >
    <head>
        <title>{$form}</title>
        {cms:import-css()}
        <link rel="stylesheet" type="text/css" href="../../resources/css/xforms-global.css"/>
        <link rel="stylesheet" type="text/css" href="../../resources/css/xrx-xforms.css"/>
        <link rel="stylesheet" type="text/css" href="local-xform.css"/>
        <xf:model>
            <xf:instance xmlns="" id="my-term" src="{$file}"/>
            <xf:bind nodeset="instance('my-term')/TermName" required="true()"/>
            <xf:bind nodeset="instance('my-term')/Definition" required="true()"/>
            <xf:bind nodeset="instance('my-term')/ApprovalDate" type="xs:date"/>
            <!-- these do not work since the form will not submit with a null date -->
            <xf:bind nodeset="instance('my-term')/ApprovalDate" required="false()" nillable="true()"/>
            <xf:bind nodeset="instance('my-term')/GroupStarredItemIndicator" type="xs:boolean"/>
            <xf:bind nodeset="instance('my-term')/DataElement/DataElementIndicator" type="xs:boolean"/>
            <xf:bind nodeset="instance('my-term')/DataElement/DataElementIdentifierIndicator" type="xs:boolean"/>
            <xf:bind nodeset="instance('my-term')/DataElement/DataElementDerivedIndicator" type="xs:boolean"/>
            <xf:bind nodeset="instance('my-term')/GroupStarredItemIndicator" required="false()"/>
            <!-- these are used to bind to conditional views or triggers-->
            <xf:instance xmlns="" id="views">
                <data>
                    <approved/>
                    <synonym-delete-trigger/>                    
                    <see-also-delete-trigger/>
                    <broader-delete-trigger/>
                    <narrower-delete-trigger/>
                    <tag-delete-trigger/>
                    <other-source/>
                    <data-element/>
                    <data-element-derived/>
                </data>
            </xf:instance>
            <!-- only display the delete triggers if there is more than one item on the list -->

            <xf:bind nodeset="instance('my-term')/PreferredTermIndicator" type="xs:boolean"/>
            <xf:bind id="approved" nodeset="instance('views')/approved" relevant="instance('my-term')/ApprovalStatusCode='program-approved'"/>
            <xf:bind id="synonym-delete-trigger" nodeset="instance('views')/synonym-delete-trigger" relevant="count(instance('my-term')/Synonym) &gt; 1"/>            
            <xf:bind id="see-also-delete-trigger" nodeset="instance('views')/see-also-delete-trigger" relevant="count(instance('my-term')/SeeAlso) &gt; 1"/>

            <xf:bind id="broader-delete-trigger" nodeset="instance('views')/broader-delete-trigger" relevant="count(instance('my-term')/Broader) &gt; 1"/>
            <xf:bind id="narrower-delete-trigger" nodeset="instance('views')/narrower-delete-trigger" relevant="count(instance('my-term')/Narrower) &gt; 1"/> 
            <xf:bind id="tag-delete-trigger" nodeset="instance('views')/tag-delete-trigger" relevant="count(instance('my-term')/tag) &gt; 1"/>
            <xf:bind id="other-source" nodeset="instance('views')/other-source" relevant="instance('my-term')/PrimarySourceCode='other' "/>
            <xf:bind id="data-element" nodeset="instance('views')/data-element" relevant="instance('my-term')/DataElementIndicator='true' "/>
            <xf:bind id="data-element-derived" nodeset="instance('views')/data-element-derived" relevant="instance('my-term')/DataElementDerivedIndicator='true' "/>
 
             {if ($new='true')
             then 
                 <xf:bind nodeset="instance('my-term')/ApprovalDate" calculate="substring(now(), 1, 10)"/>
                 else ()}
             
            <!-- code tables -->
            <xf:instance xmlns="" id="code-tables" src="code-tables.xq"/>
           
            <xf:submission id="save" method="post" action="{if ($new='true') then ('save-new.xq') else ('update.xq')}" instance="my-term" replace="all"/>
            <xf:submission id="echo-test" method="post" action="{$server-port}/exist/rest/db/mdr/xqueries/echo-test.xq" instance="my-term" replace="all"/>
        </xf:model>
    </head>
    <body>     
        {cms:header()}
        <a class="breadcrumb" href="../../index.xhtml">Metadata Registry</a> &gt;
        <a class="breadcrumb" href="../index.xhtml">Glossary Manager</a>
        <h1>{if ($new='true') then ('Create New') else ('Update')} Project Glossary Term</h1>
        <p>{$form} - {$form-version} - File: {$file}</p>
        <xf:group ref="instance('my-term')">
            <xf:label class="group-label">Glossary Term</xf:label>
            <xf:input ref="TermName" class="TermName">
                <xf:label>Term Name:</xf:label>
                <xf:hint>The name of a term in the glossary.</xf:hint>
            </xf:input>
            <xf:input ref="Acronym">
                <xf:label>Acronym:</xf:label>
                <xf:hint>The acronym or abbreviation for this term.</xf:hint>
            </xf:input>
            
             <xf:input ref="SynonymID">
                <xf:label>Synonym Set ID:</xf:label>
                <xf:hint>A link to the Synonym Set Editor.</xf:hint>
            </xf:input>
            
            <xf:textarea class="Definition" ref="Definition">
                <xf:label>Definition:</xf:label>
                <xf:hint>A short, precise, non-circular definition for this term that clearly differentiates this term
                    from other terms.</xf:hint>
            </xf:textarea>

            <xf:group>
                 <xf:label class="group-label">See Also</xf:label>
                 <xf:repeat id="see-also-repeat" nodeset="instance('my-term')/SeeAlso">
                     <xf:input ref="." class="SeeAlso inline-delete" id="see-also-input">
                         <xf:label>See Also (non-synonym):</xf:label>
                         <xf:hint>A related term in the glossary that is not a synonym.</xf:hint>
                     </xf:input>
                     <xf:trigger bind="see-also-delete-trigger" class="inline-delete">
                         <xf:label>Delete See Also</xf:label>
                         <xf:delete nodeset="instance('my-term')/SeeAlso[index('see-also-repeat')]" ev:event="DOMActivate"/>
                     </xf:trigger>
                 </xf:repeat>
                 <xf:trigger>
                     <xf:label>Insert New "See Also" Term</xf:label>
                     <xf:action ev:event="DOMActivate">
                         <xf:insert nodeset="instance('my-term')/SeeAlso" at="last()" position="after"/>
                         <xf:setvalue ref="instance('my-term')/SeeAlso[index('see-also-repeat')]" value=""/>
                         <xf:setfocus control="see-also-input"/>
                     </xf:action>
                 </xf:trigger>
            </xf:group>

             <xf:input ref="instance('my-term')/Broader">
                <xf:label>Broader Term:</xf:label>
                <xf:hint>A term that is more general in meaning.</xf:hint>
            </xf:input>     
            
            <xf:group>
                  <xf:label class="group-label">Definition Sources</xf:label>
                 <xf:repeat id="source-repeat" nodeset="instance('my-term')/PrimarySourceCode">
                     <xf:select1 ref="." class="inline-delete" id="source-select">
                         <xf:label>Definition Source:</xf:label>
                         <xf:hint>The source of information about the term and its defintion.</xf:hint>
                         <xf:itemset nodeset="instance('code-tables')/CodeTable[CodeTableName='SourceCode']/item">
                             <xf:label ref="label"/>
                             <xf:value ref="value"/>
                         </xf:itemset>
                     </xf:select1>
                     <xf:trigger bind="tag-delete-trigger" class="inline-delete">
                         <xf:label>Delete Source</xf:label>
                         <xf:delete nodeset="instance('my-term')/PrimarySourceCode[index('source-repeat')]" ev:event="DOMActivate"/>
                     </xf:trigger>
                 </xf:repeat>
                     <xf:trigger>
                         <xf:label>Add New Source</xf:label>
                         <xf:action ev:event="DOMActivate">
                             <xf:insert nodeset="instance('my-term')/PrimarySourceCode" at="last()" position="after"/>
                             <xf:setvalue ref="instance('my-term')/PrimarySourceCode[index('source-repeat')]" value=""/>
                             <xf:setfocus control="source-select"/>
                         </xf:action>
                     </xf:trigger>
             </xf:group>
             
            <!-- bind="other-source" -->
            <xf:group ref="instance('views')/other-source">
               <xf:input ref="instance('my-term')/PrimarySourceOtherText">
                  <xf:label>Other Source: </xf:label>
               </xf:input>
            </xf:group>

            <xf:group>
                  <xf:label class="group-label">Projects</xf:label>
            <xf:repeat id="project-repeat" nodeset="instance('my-term')/ProjectCode">
                <xf:select1 ref="." class="inline-delete" id="project-select">
                    <xf:label>Project:</xf:label>
                    <xf:hint>A code for classifying all the data elements.</xf:hint>
                    <xf:itemset nodeset="instance('code-tables')/CodeTable[CodeTableName='ProjectCode']/item">
                        <xf:label ref="label"/>
                        <xf:value ref="value"/>
                    </xf:itemset>
                </xf:select1>
                <xf:trigger bind="tag-delete-trigger" class="inline-delete">
                    <xf:label>Delete Project</xf:label>
                    <xf:delete nodeset="instance('my-term')/ProjectCode[index('project-repeat')]" ev:event="DOMActivate"/>
                </xf:trigger>
            </xf:repeat>
                <xf:trigger>
                    <xf:label>Add New Project</xf:label>
                    <xf:action ev:event="DOMActivate">
                        <xf:insert nodeset="instance('my-term')/ProjectCode" at="last()" position="after"/>
                        <xf:setvalue ref="instance('my-term')/ProjectCode[index('project-repeat')]" value=""/>
                        <xf:setfocus control="project-select"/>
                    </xf:action>
                </xf:trigger>
            </xf:group>
            
            <xf:select1 ref="instance('my-term')/AssignedToCode">
                <xf:label>Assigned to Facilitate Approval:</xf:label>
                <xf:itemset nodeset="instance('code-tables')/CodeTable[CodeTableName='AssignedToCode']/item">
                        <xf:label ref="label"/>
                        <xf:value ref="value"/>
                    </xf:itemset>
            </xf:select1>
            
            <xf:select1 ref="instance('my-term')/ApprovalStatusCode">
                <xf:label>Approval Status:</xf:label>
                <xf:item>
                    <xf:label>Initial Draft</xf:label>
                    <xf:value>initial-draft</xf:value>
                </xf:item>
                <xf:item>
                    <xf:label>Assigned for Review</xf:label>
                    <xf:value>assigned-to-review-team</xf:value>
                </xf:item>
                <xf:item>
                    <xf:label>Program Approved</xf:label>
                    <xf:value>program-approved</xf:value>
                </xf:item>
            </xf:select1>
            
            <xf:group ref="instance('views')/approved">
                <xf:select1 ref="instance('my-term')/ApprovedByCode">
                    <xf:label>Approved By:</xf:label>
                    <xf:hint>The person or organization that approved this term.</xf:hint>
                     <xf:itemset nodeset="instance('code-tables')/CodeTable[CodeTableName='ApprovedByCode']/item">
                            <xf:label ref="label"/>
                            <xf:value ref="value"/>
                        </xf:itemset>
                </xf:select1>
                
                <xf:input ref="instance('my-term')/ApprovalDate">
                    <xf:label>Approval Date:</xf:label>
                    <xf:hint>The date that this term was approved.</xf:hint>
                </xf:input>
            </xf:group>
            
            <xf:input ref="instance('my-term')/GroupStarredItemIndicator">
                <xf:label>Starred Item:</xf:label>
                <xf:hint>An indication by the group that this item needs attention.</xf:hint>
            </xf:input>
            
            <xf:group>
                  <xf:label class="group-label">Classifiers</xf:label>
                  <xf:repeat id="classifier-repeat" nodeset="instance('my-term')/ClassifierCode">
                     <xf:select1 ref="." class="inline-delete" id="classifier-select">
                         <xf:label>Classifier (Filter) Code:</xf:label>
                         <xf:hint>A code for classifying all the data elements.</xf:hint>
                         <xf:itemset nodeset="instance('code-tables')/CodeTable[CodeTableName='ClassifierCode']/item">
                             <xf:label ref="label"/>
                             <xf:value ref="value"/>
                         </xf:itemset>
                     </xf:select1>
                     <xf:trigger bind="tag-delete-trigger" class="inline-delete">
                         <xf:label>Delete Classifier (Filter)</xf:label>
                         <xf:delete nodeset="instance('my-term')/ClassifierCode[index('classifier-repeat')]" ev:event="DOMActivate"/>
                     </xf:trigger>
                 </xf:repeat>
                     <xf:trigger>
                         <xf:label>Add New Classifier (Filter)</xf:label>
                         <xf:action ev:event="DOMActivate">
                             <xf:insert nodeset="instance('my-term')/ClassifierCode" at="last()" position="after"/>
                             <xf:setvalue ref="instance('my-term')/ClassifierCode[index('classifier-repeat')]" value=""/>
                             <xf:setfocus control="classifier-select"/>
                         </xf:action>
                     </xf:trigger>
            </xf:group>
            
           <xf:group>
                  <xf:label class="group-label">Tags</xf:label>
                 <xf:repeat id="tag-repeat" nodeset="instance('my-term')/Tag">
                     <xf:input ref="." class="Tag inline-delete" id="tag-input">
                         <xf:label>Tag (Keyword):</xf:label>
                     </xf:input>
                     <xf:trigger bind="tag-delete-trigger" class="inline-delete">
                         <xf:label>Delete Tag (Keyword)</xf:label>
                         <xf:delete nodeset="instance('my-term')/Tag[index('tag-repeat')]" ev:event="DOMActivate"/>
                     </xf:trigger>
                 </xf:repeat>
                 <xf:trigger>
                     <xf:label>Append New Tag (Keyword)</xf:label>
                     <xf:action ev:event="DOMActivate">
                         <xf:insert nodeset="instance('my-term')/Tag" at="last()" position="after"/>
                         <xf:setvalue ref="instance('my-term')/Tag[index('tag-repeat')]" value=""/>
                         <xf:setfocus control="tag-input"/>
                     </xf:action>
                 </xf:trigger>
            </xf:group>
            
            <xf:textarea class="Notes" ref="instance('my-term')/TermNoteText">
                <xf:label>General notes:</xf:label>
                <xf:hint>Any related notes about this term.</xf:hint>
            </xf:textarea>
            
             <xf:input ref="instance('my-term')/DataElement/DataElementIndicator">
                <xf:label>Candidate Data Term:</xf:label>
                <xf:hint>This  term has a mapping to a registered data element.</xf:hint>
            </xf:input>
            
            <xf:group ref="instance('views')/data-element">
            
                <xf:input ref="instance('my-term')/DataElement/DataElementDerivedIndicator">
                   <xf:label>Data Element Derived:</xf:label>
                   <xf:hint>This data element is derived from other data elements.</xf:hint>
               </xf:input>

                <xf:group ref="instance('views')/data-element-derived">
                    <xf:textarea class="large-textarea" ref="instance('my-term')/DataElement/DataElementDerivationRuleText">
                        <xf:label>Derivation Rules:</xf:label>
                        <xf:hint>Any rules used to derive this data element from other data elements.</xf:hint>
                    </xf:textarea>
                 </xf:group>
                
                 <xf:input ref="instance('my-term')/DataElement/DataElementIdentifierIndicator">
                   <xf:label>Data Element Identifier:</xf:label>
                   <xf:hint>This data element is use to identify data sets.</xf:hint>
               </xf:input>
               
                <xf:textarea class="large-textarea" ref="instance('my-term')/DataElementEnumerationsText">
                    <xf:label>Enumerations (Valid Codes):</xf:label>
                    <xf:hint>A listing of all valid values and definitions for the possible values of this data element.</xf:hint>
                </xf:textarea>

                <xf:textarea class="large-textarea" ref="instance('my-term')/DataElement/DataElementValidationRulesText">
                    <xf:label>Validation Rules:</xf:label>
                    <xf:hint>Any rules used to validate this data element.</xf:hint>
                </xf:textarea>

                <xf:input ref="instance('my-term')/DataElement/DataElementName" class="DataElementName">
                   <xf:label>Data Element Name:</xf:label>
                   <xf:hint>ISO name for this data element including the ObjectClass, Property and Representation Term using an UpperCamelCase notation.</xf:hint>
               </xf:input>

            </xf:group>
            
            <xf:submit submission="save">
                <xf:label>Save</xf:label>
            </xf:submit>
        </xf:group>
        <a href="{cms:feedback-url()}">Feedback</a>
    </body>
</html>
)

Search Application edit


Back: Selection List Generator Next: FAQ Manager

FAQ Manager edit

Motivation edit

You want a simple tool to manage frequently asked questions for your web site. You want a user-friendly form to allow each user to enter the questions and answers, and to categorize each FAQ into one or more categories.

Source Code edit

Link to FAQ Application


Back: Glossary Term Editor Next: Detecting Duplicates

Detecting Duplicates edit

Motivation edit

You want to detect duplicate terms as the user types within a field of a form.

Method edit

We will create a form that sends a request to the server each time a letter is typed into a field. The form will call a ReST web service and pass the current term as a parameter.

Sample XQuery edit

The following query can be used on the server to detect duplicates.

xquery version "1.0";
declare namespace request="http://exist-db.org/xquery/request";
declare option exist:serialize "method=xml media-type=text/xml indent=yes";
let $term-name := request:get-parameter('term-name', '')
let $collection := '/db/terms'
return
if (not($term-name)) then
   <error>
      <message>Error: term-name argument required</message>
   </error>
else
<result>{
  if (collection($collection)/Term[TermName/text()=$term-name or Abbreviation/text()=$term-name])
    then <true/>
    else <false/>
}</result>

This query takes a single parameter and returns a true or false element.

$BASENAME/xqueries/term-exists.xq?term-name=Product

If the term exists in the database it will return the following:

<result>
   <true/>
</result>

XForms Application edit

<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>Duplicate Detection Example</title>
       <xf:model>
           <xf:instance xmlns="" id="term">
               <data>
                   <term-name>Product</term-name>
                </data>
            </xf:instance>
            <xf:submission id="check-for-duplicate" method="get" 
                    action="term-exists.xq" 
                    instance="term" replace="all"/>
        </xf:model>
     </head>
     <body>
          <h1>Check Term</h1>
          <xf:input ref="term-name">
               <xf:label>Term: </xf:label>
          </xf:input>
          <xf:submit submission="check-for-duplicate">
               <xf:label>Check for Dups</xf:label>
          </xf:submit>
     </body>
</html>

Now we have a simple XForms that calls a web service and returns a true/false record. Our next step is to make this test occur in the background as the user types and display a warning message as a duplicate has been detected.

Adding Character-by-Character Testing edit

Now we have two tasks. We need to fire off the event to the server as the user types and we need to bring the information back without interrupting the user.

First we want to add an event to the input field that sends a message each time a character is typed into the input field:

<xf:input ref="term-name" incremental="true">
   <xf:label >Term: </xf:label>
   <xf:action ev:event="xforms-value-changed">
      <xf:send submission="check-for-duplicate"/>
   </xf:action>
</xf:input>

Back: FAQ Manager Next: Data Element Editor

Data Element Editor edit

Motivation edit

The core of an ISO metadata data registry is a collection of data elements. These data elements represent the semantics or meaning of a data element within a standard or an organization. Data elements do not necessarily map to a representation of data in a specific data store such as an RDBMS, an Excel spreadsheet, an XML file or a CSV file. Data elements can store mappings to these representations via data maps.

There are two types of data elements in an ISO metadata registry:

Conceptual Data Elements - similar to RDBMS tables, concepts in a taxonomy tree, or object classes without the behavior. Conceptual data elements are really collections for grouping similar properties.

Properties - are similar to a column of a table, a leaf element in a taxonomy, or an simple instance variable in an object class.

As with objects, properties can exhibit inheritance properties. So if a superclass has a property, that property is valid for all subclasses.

Implementation edit

Our ISO registry example will have an XForms application for storing both conceptual data elements and properties. Both of these will be called generic data elements and they both share many attributes such as who created them, their definitions, and their mappings. The only difference is that a conceptual data element has no property or data type assignment (representation term).

Each data elements will be stored in a separate XML file in a data element collection. This allows multiple users to independently edit data elements and lock each data elements to avoid lost updates.


Back: Detecting Duplicates Next: Selection List Management

Selection List Management edit

Motivation edit

You want every selection list in your form to be customized to the context of the situation. Selection lists need to be dynamically generated by a series of context aware services on the web server.

Method edit

XForms are powerful because they can load all the codes in a form from a single instance in the model.

This for instance, if you would like to customize what selection lists are displayed to a user based on their role or other property in their session, such as what department or group they are associated with.

Method edit

This can be done by using session variables stored on the server. You can alternatively store the user in a cookie on the client and then use this information to look up the correct information.

Assume you have an XML file that associates a user with their roles:

<Users>
   <User>
      <LoginID>jdoe</LoginID>
      <Roles>
          <Role>project-manager</Role>
          <Role>glossary-code-table-admin</Role>
      </Roles>
   </User>
   <User>
      ...
</Users>

These roles can be in a session variable (see session) or you can have a function that looks up the role from the LoginID.

When the forms load, a script can be executed that loads all the code tables into an instance in the model:

<xf:model>
   <xf:instanace src="all-codes.xq?LoginID=jdoe"/>
</xf:model>

Back: Data Element Editor Next: Customizing Selection Lists

Customizing Selection Lists edit

Motivation edit

Many times a form or application can use the context of a situation to customize the functionality of the form based on the context of the situation. Context can include the user, group, organization structure, department, time of day, or any other parameter.


Back: Selection List Management Next: Table Sorting

Table Sorting edit

Motivation edit

You have a table of data and you want to change how the results are sorted by clicking on the column.

Method edit

We will add a sort parameter to a simple XQuery.

Sample Data edit

We will use a sample table of flowers.

http://extjs.com/deploy/dev/examples/grid/plants.xml


Back: Customizing Selection Lists Next: NIEM Services

NIEM Services edit

Motivation edit

You want to create a set of web services based on the National Information Exchange Model.

Process edit

We will import the core NIEM data as an XML Schema.

To do this first download the NIEM data model from the NIEM.gov web site. Inside the zip file you will find a niem-core directory with a niem-core.xsd file. Add this to your database and run the following XQuery on it:

Sample Code edit

declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare option exist:serialize "method=xml media-type=text/xml indent=yes";

let $doc := '/db/niem/data/niem-core.xsd'

return
<results>{
      for $type in doc($doc)//xs:complexType
      return
          <type>{substring-before(data($type/@name), 'Type')}</type>
}</results>

This will return a list of all the NIEM complex types.

<results>
   <type>ActivityConveyanceAssociation</type>
   <type>ActivityDocumentAssociation</type>
   <type>ActivityInvolvedPersonAssociation</type>
   <type>ActivityItemAssociation</type>
   <type>ActivityOrganizationAssociation</type>
   <type>ActivityPersonAssociation</type>
   <type>ActivityScheduleAssociation</type>
   <type>Activity</type>
   <type>AddressGrid</type>
...

This XQuery can in turn be used as a web service to populate an XForms suggestion list similar to the Suggestion example in the XForms cookbook.


Back: Table SortingNext: Product Ratings

Product Ratings edit

Motivation edit

You want a method for creating a rating system for items, such as a five-star rating for a product or service.

Method edit

Each registered user in a system can rate items. A separate collection associates users with ratings. Ratings can be placed anywhere in a form and can associate a user to an item and that association has a numeric value of 1 to 5. Items can then display their average user rating.

References edit

Sample Drupal Five Star rating system created with JQuery


Back: NIEM Services Next: Business Rules Template

Business Rules Template edit

Motivation edit

You want to create a simple "rules editor" that allows non-programmers to maintain simple business rules for an application.

Method edit

Business rules are expressed as a set of conditional statements. Each conditional statement has two parts: a condition and an action. For example, the following is a simple business rule that can check the validity of a date:

IF (TransactionDate > currentDate())
THEN ERROR, MESSAGE="Transaction date must be in the past"

The if portion of the rule tests to see if the transaction date is larger than the current date. If it is, then the date is not in the past. The submission of the form data is rejected, and a useful error message is returned to the user.

One of the best examples of a simple rule editor is an email rules editor. This is usually set up as a template such as the following:

To be done...


Back: Product Ratings Next: Metadata Shopper

Metadata Shopper edit

Motivation edit

You 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 edit

We 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 edit

Here is a sample screen image of a shopping cart before you begin searching for data elements:

 
Empty Shopping Cart

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.

 
Results of Search "last"

Here is a sample screen image showing the shopping cart after several metadata elements have been added to it.

 
Full Shopping Cart

Data Element Search Results edit

The 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 edit

The 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="&amp;">
                <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="&amp;">
                <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> &gt;
        <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&lt;xf:output
                            value="concat(
                            'http://dlficsb501:8080/exist/rest/db/mdr/data-elements/views/view-data-element.xq?id=',
                            DataElementName,
                            prefix,':', DataElementName
                            )"
                        class="url" /&gt; -->
                    </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 edit

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


Back: Business Rules Template Next: Subset Generator

Subset Generator edit

Motivation edit

You have multiple XML documents and you want the semantics and business rules for all the shared data elements to be driven from a single model.

Method edit

We will use two types of XML Schemas. A semantic schema or subset schema is a sequence of all of the elements of a document exchange including the meaning of the enumerated values of the elements. A constraint schema or exchange schema will import the semantic schema and include the document constraints such the order, grouping and what cardinality each of the elements in the document.

We will leverage many of the concepts of ISO/IEC 11179 metadata registry. We will use a single XQuery that takes in a wantlist and generates a semantic subschema from this registry. This assures that all leaf-level elements in a family of XML Schemas have consistent datatypes and precise semantic definitions that all refer back to the metadata registry. This automatic generation of structures from a central data model is known as model-driven design.

Background on Subset Generation edit

Subset generation techniques were first developed by the Georgia Institute of Technology in support of the GJXDM and NIEMstandards. The tools took the form of a web site that allowed a user to shop for data elements in the registry and keep a list of those data elements in a file called a wantlist. This wantlist was then used to generate a subset of the GJXDM system that is guaranteed to be consistent with the GJXDM. These tools were carried forward for use in the National Information Exchange Model as well as the Minnesota Department of Revenue subschema generators.

Method edit

A subset generator is part two of a three part process.

 
  • Step one: Use a metadata shopping cart to shop for data elements in your registry and to create a wantlist. A shopping cart tool with an integrated search function allows non-programmers to take an active role in schema creation.
  • Step two: is to generate a Subset Schema that conforms to the Liskov Substitution Principle using the wantlist generated in part one as the input.
  • Step three: Import that subset XML Schema into a Constraint Schema. This can be done my referencing the REST parameters to the wantlist directly in the import or it can be done by saving the subset into a discrete file.

Wantlist edit

A Wantlist is a simple enumeration of all the data elements you will place at leaf-elements in your XML Schema. Wantlists do not contain any cardinality (number for repetitions) or complex branch structures. They are much like the grocery list you take shopping in which the quantities of items are not specified.

GTRI Wantlist XML Schema

Sample Wantlist edit

The following is a sample wantlist generated from the US NIEM metadata shopping cart tool. Not only is the three-part property specified (ObjectClass, Property, Representation Term) but the isReference indicators are also available.

<?xml version="1.0" encoding="UTF-8"?>
<w:WantList xmlns:w="http://niem.gov/niem/wantlist/1" w:release="2.0" w:product="NIEM">
    <w:Element w:prefix="nc" w:name="LocationCityName" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="LocationCounty" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="LocationCountyCode" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="LocationPostalCode" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="LocationStateFIPS5-2AlphaCode" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="LocationStateName" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="LocationStateUSPostalServiceCode" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetCategoryText" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetExtensionText" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetFullText" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetName" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetNumberText" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetPostdirectionalText" w:isReference="false"/>
    <w:Element w:prefix="nc" w:name="StreetPredirectionalText" w:isReference="false"/>
</w:WantList>

This wantlist can be saved from the web-shopping-cart tool to your local file system and then reloaded at a future date. In our example, any wantlist can be saved on the eXist server and passed as a parameter to a subset generator. This allows developers to reuse and version wantlists in addition to other XML Schema components.

XQuery Based Subschema Generator edit

This XQuery takes as a single argument a wantlist file name stored in a wantlist collection. It iterates through each element in the wantlist and generates the appropriate XML Schema types.

The format of the URL for the Query is:

[WEBSERVER]/db/wantlists/subset.xq?wantlist=WANTLISTNAME

In this case the .xml file extension can be omitted.


xquery version "1.0";
import module namespace mdr = "http://mdr.example.com" at "/db/mdr/modules/mdr.xq";
declare namespace exist = "http://exist.sourceforge.net/NS/exist"; 
declare namespace xsd = "http://www.w3.org/2001/XMLSchema";
declare namespace system="http://exist-db.org/xquery/system";
declare namespace request="http://exist-db.org/xquery/request";
declare option exist:serialize "method=xhtml media-type=text/xml indent=yes";

let $wantlist := request:get-parameter('wantlist', '')
return
if (string-length($wantlist) < 1)
   then (
        <results>
           <message>Error: "wantlist" is a required parameter.</message>
        </results>
    )
    else (
        let $wantlist-collection := '/db/mdr/wantlists/data'
        let $file-path := concat($wantlist-collection, '/', $wantlist, '.xml')
        let $doc := doc($file-path)
        return
            <xsd:schema 
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
            xmlns:e="http://metadata.example.com" 
            targetNamespace="{$doc//NamespaceURI/text()}">
               <xsd:annotation>
                   <xsd:documentation>
                      <wantlist>{$wantlist}</wantlist>
                      <path>{$file-path}</path>
                   </xsd:documentation>
                     </xsd:annotation>
               {for $element in $doc//Element
                   let $prefix := 'p:'
                   let $simpleType := 
                      if (ends-with($element, 'Code'))
                              then
                                 (<xsd:simpleType name="{concat($element/text(), 'Type')}">
                                     <xsd:annotation>
                                            <xsd:documentation>{mdr:get-definition-for-element($element)}</xsd:documentation>
                                     </xsd:annotation>
                                     {mdr:get-restrictions-for-element($element/text())}
                               </xsd:simpleType>
                               )
                      else ()                    
                    let $elementDef := <xsd:element name="{$element/text()}" 
                    type="{if (ends-with($element, 'Code')) 
                       then (concat($prefix, $element/text(), 'Type'))
                       else (mdr:get-xml-schema-datatype-for-element($element))}" nillable="true">
                         <xsd:annotation>
                                <xsd:documentation>{mdr:get-definition-for-element($element)}</xsd:documentation>
                         </xsd:annotation>
                    </xsd:element>

                    return ($simpleType, $elementDef)
               }
            </xsd:schema>
)

Using an XML Schema to validate your XForms edit

Some XForms systems allow you to use an XML Schema to validate your form. For example, with Orbeon forms you can do the following:

<xf:model schema="MyTypeLibrary.xsd">

Back: Metadata Shopper Next: XForms Generator

XForms Generator edit

Motivation edit

You have an XML Schema and you want to automatically generate an XForms application directly from the XML Schema.

Method edit

We will write an XQuery that will take the URL of an XML Schema as an input and will generate the XForms edit form as an output.

Note: Some of the functions in this example depend on use of ISO/IEC element naming conventions to "guess" the correct control to place on the XForms application. As an alternative you can add tags to the annotations of the XML Schema to always generate the correct XForms controls from an XML Schema. The downside to this option is that you will be forced to use a text editor rather then a graphical editor for your XML Schemas.

Steps edit

Here are the steps used to generate the XForms application

  1. Parse the XML Schema file looking for a document root
  2. Start to generate xf:groups for each complex element in the XML Schema
  3. For each simpleType, guess at the appropriate XForms control to use. Use hints from the XML data type or the element name to guess the correct control type. Leverage ISO/IEC 11179 metadata registry naming conventions and Representation terms whenever possible. Use xf:input for simple text fields, but use xf:select1 for enumerated values. For example, use the textarea control if the suffix of the element name is text, description or note.
  4. Generate any business rules in the bindings section of the XForms model. Although future XForms clients may be able to automatically infer rules from the XML Schema, today many XForms clients do not fully support XML Schemas. So, to generate a list of all the required fields, a set of binding elements must be generated. Luckily this is only a few lines of XQuery code.
  5. Generate an instance document for the initial values of the form

We will leverage a large collection of XQuery functions to keep our main XQuery simple. A separate module will be used to store all these functions.

The resulting XForms application can then be customized with specific behavior. Once this is done you must regenerate the XForms application if the XML Schema changes. Merging the customizations and the new XML Schema can be done using an XML merge tool.

Sample Functions edit

Looking for textarea edit

The following function uses the some construct of XQuery. It is very similar to a for loop, but instead of returning output for each item in a sequence, it only returns a Boolean true or false if one item in a sequence satisfies some criteria. In this case, the criteria is if the suffix of the element name ends in text, note or description, we will use a textarea.

declare function schema-to-xforms:is-textarea($element-name as xs:string) as xs:boolean {
    (: if the element name has any of the following suffix we map it to the input element type :)
    let $textarea-suffixes :=
    <items>
         <item>text</item>
         <item>note</item>
         <item>description</item>
    </items>
    let $lower-case-element-name := lower-case($element-name)
    return
        some $type in $textarea-suffixes/item
        satisfies ends-with($lower-case-element-name, $type)
};

Adding Date Bindings edit

To get our XForms application to automatically put calendar pickers in the user interface we need to bind each of the elements with an XML Schema date type.

Here is a sample bindings for date type:

<xf:model>
   ...
   <xf:bind nodeset="//TaskStartDate" type="xs:date"/>
   <xf:bind nodeset="//TaskEndDate" type="xs:date"/>
   ...
</xf:model>

The XQuery to generate these from all the elements in an XML Schema is very simple if you use the appropriate naming conventions and end each element with the suffix "Date". The query to generate all the date bindings is just to use the XQuery function ends-with() such as the following:

for $element in $schema//xs:element[ends-with(lower-case(@name), 'date')]
return
   <xf:bind nodeset="//{string($element/@name)}" type="xs:date"/>

This will also need to be done for attributes if you store dates in attributes.

Generating Boolean Bindings edit

In the same way, you can also look for all elements that end with the suffix "indicator" to turn input controls with the words "true" and "false" into checkboxes.

for $element in $schema//xs:element[ends-with(lower-case(@name), 'indicator')]
return
   <xf:bind nodeset="//{string($element/@name)}" type="xs:boolean"/>

Looking for required fields edit

You can get a list of all the non-optional elements in an XML Schema by just looking for all elements that are not complex and do not permit zero occurrences. If an element is complex, it will have a child element of complexType. Our XPath expression has to remove these by adding a predicate with a not() function: xs:element[not(xs:complexType). We must then use a boolean and statement to exclude all elements that do not have a minOccurs='0' attribute.

for $element in $schema//xs:element[not(xs:complexType) and not(@minOccurs='0')]
return
   <xf:bind nodeset="//{string($element/@name)}" required="true()"/>

Sample Nesting with xs:group Element edit

You can use the group element to keep track of the context of the data in your form.

For example if the form instance had the following structure:

<root>
   <sub-node>
       <sub-sub-node>
         <fname>John</fname>
         <lname>Doe</fname>
      </sub-sub-node>
   </sub-node>      
</root>

then you would generate the following xf:group element with nodeset attribute set to the correct context:

<xf:group nodeset="/root/sub-node/sub-sub-node">
   <xf:label class="group-label">contact</xf:label>
   <xf:input ref="fname">
      <xf:label>Name: </xf:label>
   <xf:input>
  <xf:input ref="lname">
      <xf:label>Name: </xf:label>
   <xf:input>
</xf:group>

You can also style the group using a bounding box similar to the way that the HTML fieldset is styled.

Resources edit

References edit


Back: Subset GeneratorNext: User Manager

Large XForms Generator edit

Motivation edit

You want to be able to generate large XForms using a REST service for doing performance tests for large forms. The primary item being tested is how long does the form take to become "ready" after the form is loaded.

Method edit

We will use an XQuery to create XForms files. This XQuery will take a single URL parameter that has a count of the number of elements it will generate. The XForms will have one input field and two select1 fields for each count. Each input control will have its own instance in the model.

Sample of Instance in the model. edit

<root>
   <input1>input 1</input1>
   <input2>input 2</input2>
   <country-code1>country-code 1</country-code1>
   <country-code2>country-code 2</country-code2>
   <state-code1>state-code 1</state-code1>
   <state-code2>state-code 2</state-code2>
</root>

Instance Generator edit

xquery version "1.0";

let $count := xs:positiveInteger(request:get-parameter('count', '20'))

return
<root>
    {for $counter in (1 to $count)
    return
       element {concat('input', $counter)} {concat('input ', $counter)}
    }

    {for $counter in (1 to $count)
    return
       element {concat('country-code', $counter)} {concat('country-code ', $counter)}
    }

   {for $counter in (1 to $count)
    return
       element {concat('state-code', $counter)} {concat('state-code ', $counter)}
    }

</root>

Autogeneration of Input Fields edit

for $counter in (1 to $input-count)
   return
      (<xf:input ref="input{$counter}">
          <xf:label>Input {$counter}:</xf:label>
          </xf:input>,
      <br/>)

Autogeneration of Select1 Fields edit

for $counter in (1 to $select1-count)
  return
     (<xf:select1 ref="country-code{$counter}">
         <xf:label>Country {$counter}:</xf:label>
         <xf:itemset nodeset="instance('code-tables')/code-table[name='country-code']/items/item">
            <xf:label ref="label"/>
            <xf:value ref="value"/>
         </xf:itemset>
         </xf:select1>,
         <br/>
     )

The code tables are stored in a static XML file called "code-tables.xml". The format is as follows:

<code-tables>
    <code-table>
        <name>country-code</name>
        <items>
            <item>
                <label>Select a Country...</label>
                <value/>
            </item>
            <item>
                <label>Afghanistan</label>
                <value>af</value>
            </item>
            <item>
                <label>Albania</label>
                <value>al</value>
            </item>
...etc....
        </items>
    </code-table>
</code-tables>

Source Code edit

xquery version "1.0";
(: Default function and element declarations :)
declare default function namespace "http://www.w3.org/2005/xpath-functions";
declare default element namespace "http://www.w3.org/1999/xhtml";

declare namespace xf="http://www.w3.org/2002/xforms";
declare namespace ev="http://www.w3.org/2001/xml-events";
declare option exist:serialize "method=xhtml media-type=text/xml indent=no process-xsl-pi=no";

let $count := xs:positiveInteger(request:get-parameter('count', '20'))
let $input-count := $count
let $select1-count := $count

let $style :=
<style language="text/css">
    <![CDATA[
        @namespace xf url("http://www.w3.org/2002/xforms");
        
        body {
            font-family: Helvetica, Arial, sans-serif;
        }
        .block-form xf|label {
            width: 10ex;
            font-weight: bold;
            width: 10ex;
            display: inline-block;
            text-align: right;
        }
        
    ]]>
 </style>

let $model :=
<xf:model>
    <xf:instance id="save-data" src="instance.xq?count={$input-count}"/>
    <xf:instance id="code-tables" src="code-tables.xml" xmlns=""/>
</xf:model>
        
let $content :=
<div class="content">
    {
       for $counter in (1 to $input-count)
       return
            (<xf:input ref="input{$counter}">
                <xf:label>Input {$counter}:</xf:label>
            </xf:input>,
            <br/>)
     }
     
     {
       for $counter in (1 to $select1-count)
       return
          (<xf:select1 ref="country-code{$counter}">
              <xf:label>Country {$counter}:</xf:label>
              <xf:itemset nodeset="instance('code-tables')/code-table[name='country-code']/items/item">
                 <xf:label ref="label"/>
                 <xf:value ref="value"/>
              </xf:itemset>
             </xf:select1>,
             <br/>
           )
     }
     
          {
       for $counter in (1 to $select1-count)
       return
          (<xf:select1 ref="state-code{$counter}">
              <xf:label>State {$counter}:</xf:label>
              <xf:itemset nodeset="instance('code-tables')/code-table[name='us-state-code']/items/item">
                 <xf:label ref="label"/>
                 <xf:value ref="value"/>
              </xf:itemset>
             </xf:select1>,
             <br/>
           )
     }
</div>
 
let $form :=
<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>Test Form</title>
        { $style }
        { $model }
    </head>
    <body>
        <div class="container">
            <div class="inner">
                <h2>Test Form</h2>
                { $content }
            </div>
        </div>
    </body>
</html>

let $xslt-pi := processing-instruction xml-stylesheet {'type="text/xsl" href="/rest/db/xforms/xsltforms-new/xsltforms.xsl"'}
let $debug := processing-instruction xsltforms-options {'debug="yes"'}

return ($xslt-pi, $debug, $form)

Sample Code Tables edit

Many large forms use large code tables. This example uses two large code tables. One is a list of all the countries in the world and another is a list of all US State Codes.

Sample Usage edit

Sample Output edit

User Manager edit

Motivation edit

You want to manage a group of users of your web site, validate their roles on the site, and track user-dependent information. This application will manage a list of users, and associate with the users the number of attempted logins, their session timeout interval, and the roles they are assigned.

Method edit

Our application will use Role-Based Access Control (RBAC). We will use several functions to perform user management:

 xmldb:login($collection, $user, $pass) will log the user into the system and verify they have write access to a collection.

Note that to log the user out, we will change the login to the user "guest."

 xmldb:get-current-user() will return the user-id of the current user.

We will also need to add a new function that will check to see if a given user has a role.

 auth:has-role(xmldb:get-current-user(), $role) as xs:boolean check to see if a user has a given role.

This will return a true() if the current user has a given role.

User Data edit

The following is an example of a user's information. Note that we do not store the password in this file. The eXist system is used to store the user's password.

<user>
    <id>47</id>
    <user-id>jdoe</user-id>
    <person-given-name>John</person-given-name>
    <person-family-name>Doe</person-family-name>
    <account-active-indicator>true</account-active-indicator>
    <max-login-retrys>5</max-login-retrys>
    <session-timeout-minutes>60</session-timeout-minutes>
    <roles>
        <role>ba</role>
        <role>ba-admin</role>
    </roles>
</user>

In the above example, the user jdoe has the role of ba and ba-admin which means the user is classified as a business analyst and also has the ability to administer the business analyst tools in the system.

So the following function:

  auth:has-role('jdoe', 'ba')

will return true()

Login Form edit

The following is a simplified login form that will allow the user to enter a login and a password and perform an HTTP post operation. Note that this is different from the eXist standard login in that it does not place the password on the URL using an HTTP get. This information is placed in a log file can compromise the security of the system.

xquery version "1.0";
import module namespace style ='http://code.google.com/p/xrx/style' at '/db/xrx/modules/style.xqm';
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

let $user := xmldb:get-current-user()

(: this gets the entire input URL :)
let $get-query-string := request:get-query-string()

(: were are only interested in the portion after the return= :)
let $return-uri := substring-after($get-query-string, 'return=')

return
<html>
   <head>
      <title>Login</title>
      {style:import-css()}
   </head>
   <body>
      {style:header()}
      <h1>Login</h1>
      <p>Current User = {$user}</p>
      <form action="login.xq" method="post">
            <table class="login" cellpadding="5">
                <tr>
                    <th colspan="2" align="left">Please Login</th>
                </tr>
                <tr>
                    <td align="left">Username:</td>
                    <td><input name="user" type="text" size="20"/></td>
                </tr>
                <tr>
                    <td align="left">Password:</td>
                    <td><input name="pass" type="password" size="20"/></td>
                </tr>
            </table>
            <input name="return" type="hidden" value="{$return-uri}" size="20"/>
            <input type="submit" value="Login"/>
        </form>
        <p>We will return to the following URI on success: {$return-uri}</p>
      {style:footer()}
   </body>
</html>

Login Verification Script edit

The following script takes incoming login data from an HTTP POST operation and performs a login. HTTP POST data arrives from the request:get-data() format in the following key-value-pair format:

user=jdoe&pass=mypass&return=/exist/rest/db/xrx/apps/test/view/view-item.xq&id=47

Note that we assume that the user-id is in the first key-value pair and the pass is in the second. If you want a more general interface you can scan for the correct key in all the incoming form key-value pairs.

xquery version "1.0";
import module namespace style ='http://code.google.com/p/xrx/style' at '/db/xrx/modules/style.xqm';
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

let $data := request:get-data()
let $tokens := tokenize($data, '&amp;')
let $user := substring-after($tokens[1], 'user=')
let $pass := substring-after($tokens[2], 'pass=')
let $return := substring-after($tokens[3], 'return=')

(: this is required to fix the incomming URI encodings :)
let $unescape-uri := request:unescape-uri($return, 'UTF-8')

(: this is required to put the first parameter deliminator ? back in place of the ampersand :)
let $fix-first-param := replace($unescape-uri, 'xq&amp;', 'xq?')
let $login-success-indicator := xmldb:login("/db", $user, $pass)

return
<html>
   <head>
      <title>Login Result</title>
      {style:import-css()}
   </head>
   <body>
      {style:header()}
      {style:breadcrumb()}
      <h1>Login Status</h1>
      {if ($login-success-indicator)
         then <p>Your Login Was Successful <a href="{$fix-first-param}">Return</a></p>
         else <p>Login Failed <a href="login-form.xq">Try Again</a></p>
      }
      You are logged in to the root database collection as {xmldb:get-current-user()}
      <br/>
      <a href="logout.xq">Logout</a>
      {style:footer()}
   </body>
</html>

Logout Script edit

The following script logs you out of the system. Technically, it just logs you in as the user that has read-only access.


xquery version "1.0";
import module namespace style='http://mdr.crossflow.com/style' at '/db/crossflo/modules/style.xq';
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";

let $null := xmldb:login("/db", "guest", "guest") 

let $user := xmldb:get-current-user()

return
<html>
   <head>
      <title>Logout</title>
      {style:import-css()}
      {style:breadcrumb()}
   </head>
   <body>
      {style:header()}
      <h1>You have been logged out.</h1>
      <p>User = {$user}</p>
      <a href="login-form.xq">Login</a>
      {style:footer()}
   </body>
</html

Adding an Authorization Function edit

We will now create an XQuery module that performs an authentication check for a given user.

Role-Based Conditional Display of Edit Functions edit

Many screens have links to Edit forms that allow users to change the data. You can conditionally display these Edit links by adding the following code to each page that would normally have an Edit link:

{if (auth:has-role(xmldb:get-current-user(), 'ba'))
   then
      <div>
         <a href="../edit/edit.xq?id={$id}">Edit Item</a>
         <a href="../edit/delete-confirm.xq?id={$id}">Delete Item</a>
      </div>
   else 
      <span>User {xmldb:get-current-user()} has no roles with edit privileges 
         <a href="{auth:login-uri()}">Login</a>
     </span>
}

Checking for Authenticated User edit

You can also add a function in any page that has sensitive information or functions by checking that the user is logged in. This can be done with a single function called auth:validate-user()

  request:get-session-attribute("user") - this function will return the user name associated with the login session
  response:redirect-to($uri as xs:anyURI) - this function will redirect the user to another URI such as a login panel.

Note that typically you want to make it easy to allow the user to bounce back to the screen that they were on. To do this you frequently want to add a parameter to the login.xq XQuery that will redirect the user back to where they came from.

  response:redirect-to('/exist/rest/db/xrx/apps/user-manager/login.xq?return=/exist/rest/db/myapp/edit/edit.xq?id=47')

The following is a sample function that can be placed at the top of all pages that require user authentication:

declare function auth:validate-user() as xs:string {
   let $url:= request:get-url()
   let $param-names := request:parameter-names()
   let $param-string :=for $count in 1 to count($param-names)
   let $param := $param-names[$count]
   let $value := request:get-parameter($param, '')
   let $return := if ($count=1) then (concat('?',$param,'=',$value)) else (concat('&amp;',$param,'=',$value))
        return
            $return
   let $return-param :=fn:string-join($param-string,'')
   let $get-session-name :=request:get-session-attribute("user")
   let $login-uri := '/exist/rest/db/xrx/apps/user-manager/login.xq?url='
return
   if ($get-session-name) then
      ($get-session-name)
   else(
       response:redirect-to(xs:anyURI(concat($string,$url,fn:string-join($param-string,'')))))
};

Back: XForms Generator Next: Content Routing

Map Navigation edit

Motivation edit

You would like to add map navigation to your XRX application.

Although map controls were not defined within the original XForms specification, they can be added by using a bit of JavaScript.

Method edit

We will use three frameworks.

  1. XSLTForms - that enables the model, controls and bindings
  2. JQuery - JQuery UI, JQuery layout - for the user interface. This includes the controls to do pans north, south, east, west
  3. OpenLayers - for the map navigation, pan and zoom functions

The XForms model will contain the following:

  1. A default instance that contains a search query with the min and max longitude and latitude (default).
  2. A place to store the response to the query (response).
  3. A locate instance (locate)
  4. A few binding statements
  5. A submission to get the new map data from the openmap database

Sample User Interface edit

This example has a simple map user interface control that allows for panning to the North/South/East/West and zoom and zoom out.

 

This user interface uses a JQuery object that is associated with XForms input user interface controls.

Sample Model Source edit

The following is stored in the XForms model:

Note that the following namespaces are used in this example:

xmlns:geo="http://schematronic.ru/geo"
xmlns:param="http://schematronic.ru/param"
xmlns:ev="http://www.w3.org/2001/xml-events"
<xf:model>
   <!-- this holds the parameters for the outbound search query. -->
   <xf:instance id="default" xmlns="http://schematronic.ru/geo">
       <geo:search>
           <param:query/>
           <!-- variables for the min and max longitude and latitude -->
           <param:min-lon/>
           <param:min-lat/>
           <param:max-lon/>
           <param:max-lat/>
        </geo:search>
    </xf:instance>

    <!-- this holds the search results -->
    <xf:instance xmlns="" id="response">
       <response/>
    </xf:instance>

    <xf:instance id="locate">
       <eval>
           javascript:g.locate(<lon></lon>, <lat></lat>, "<icon></icon>")
       </eval>
    </xf:instance>

    <!-- These binds associate an id with a variable in the search form and a calculation -->
    <xf:bind id="query" nodeset="instance('default')/param:query"/>
    <xf:bind id="min-lon" nodeset="instance('default')/param:min-lon" calculate="min-lon()"/>
    <xf:bind id="min-lat" nodeset="instance('default')/param:min-lat" calculate="min-lat()"/>
    <xf:bind id="max-lon" nodeset="instance('default')/param:max-lon" calculate="max-lon()"/>
    <xf:bind id="max-lat" nodeset="instance('default')/param:max-lat" calculate="max-lat()"/>
    <!-- These binds associate an ID with 
    <xf:bind id="lon" nodeset="instance('locate')/lon"/>
    <xf:bind id="lat" nodeset="instance('locate')/lat"/>
    <xf:bind id="icon" nodeset="instance('locate')/icon"/>

    <!-- When the user selects any of the navigation controls, the following does a POST to the server. -->
    <!-- Note that the response to the search is places in the "response instance -->
     <xf:submission id="do-search" method="post" ref="instance('default')" replace="instance" instance="response" resource="/gate">
         <xf:load 
            ev:event="xforms-submit-done" 
            resource="javascript:showResult()"/>
     </xf:submission>
</xf:model>

JavaScript Imports edit

The Javascript libraries will come from the openstreemap.org site. We will also use some JQuery user interface controls.

Here are the static JavaScript files you will need to add.

Importing Static JavaScript Libraries edit

   <script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script>
   <script type="text/javascript" src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
   <script type="text/javascript" src="/share/geo.js"></script>
   <script type="text/javascript" src="/share/jquery.js"></script>
   <script type="text/javascript" src="/share/jquery-ui.js"></script>
   <script type="text/javascript" src="/share/jquery-layout.js"></script>

Inline JavaScript Data edit

In addition to the above, the following JavaScript data must be used.

<script type="text/javascript">
    var g;
    function map(){
       var lat  = 51.30505;
       var lon  = 37.85331;
       var zoom = 12;
       g = geo("map", lon, lat, zoom);            
    }
    var isDebug = false;                   
    var layout;
  
    function showResult (){
                    layout.open("west");
    }      
    jQuery(function (){      
        jQuery("#search input").addClass("ui-state-default ui-corner-left");
        jQuery("#search button").addClass("ui-button ui-state-default ui-corner-right");
        layout = jQuery("body").layout({
              north : {
                            resizable          : false,
                            slidable           : false,
                            closable           : false,
                            spacing_open       : 0,
                            spacing_closed     : 0,
                            size               : 40
                        },
              south : {
                            size               : 100,
                            resizable          : true,
                            slidable           : true,
                            closable           : true,
                            initHidden         : !isDebug
                        },
              west : {
                            size               : 300,
                            minSize            : 200,
                            maxSize            : 400,
                            resizable          : true,
                            slidable           : true,
                            closable           : true,
                            initHidden         : true
                        }
                        
                    }); 
             map();             
     if (isDebug) jQuery("#console").show();    
})

</script>

Form Body edit

<body>
   <div id="header" class="ui-layout-north ui-widget-header">
      <span id="logo">
         <a href="/">
            <img src="/site/stkurier/images/logo/logo.png" alt="logo" />
         </a>
      </span>
      <span id="search">
          <xf:input xmlns:xf="http://www.w3.org/2002/xforms" bind="query">
               <xf:send submission="do-search" ev:event="xforms-value-changed"/>
          </xf:input>
          <xf:submit submission="do-search">
              <xf:label>
                  <img alt="search" src="/share/icons/fugue-icons/icons/magnifier-medium-left.png" />
               </xf:label>
           </xf:submit>
            </span>
        </div>
        <div class="ui-layout-west">
            <div id="result">
                <ol>
                    <xf:repeat xmlns:xf="http://www.w3.org/2002/xforms" id="place_list" nodeset="instance('response')//*:place">
                        <li>
                            <xf:trigger appearance="minimal">
                                <xf:action xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="DOMActivate">
                                    <xf:setvalue bind="lon" value="instance('response')//*:place[index('place_list')]/@lon"></xf:setvalue>
                                    <xf:setvalue bind="lat" value="instance('response')//*:place[index('place_list')]/@lat"></xf:setvalue>
                                    <xf:setvalue bind="icon" value="instance('response')//*:place[index('place_list')]/@icon"></xf:setvalue>
                                    <xf:load>
                                        <xf:resource value="instance('locate')"></xf:resource>
                                    </xf:load>

                                </xf:action>
                                <xf:label>
                                    <xf:output ref="@icon" mediatype="image/*" if="@icon"></xf:output>
                                    <xf:output ref="@display_name"></xf:output>
                                </xf:label>
                            </xf:trigger>
                        </li>
                    </xf:repeat>
                </ol>
            </div>
        </div>
        <div class="ui-layout-center">
            <div id="map"></div>
        </div>
        <div div="#debug" class="ui-layout-south">
            <div id="console"></div>
        </div>
    </body>

XQuery edit

The following is a sample of the server-side XQuery code.

   geo:search($query, $min-lon, $min-lat, $max-lon, $max-lat)

will be evaluated at server side for instance:

<geo:search>
     <param:query/>
     <param:min-lon/>
     <param:min-lat/>
     <param:max-lon/>
     <param:max-lat/>
</geo:search>

Geo XQuery Module edit

module namespace geo = "http://schematronic.ru/geo";

import module namespace http    = "http://exist-db.org/xquery/httpclient";
import module namespace request = "http://exist-db.org/xquery/request";

declare variable $geo:search-service-uri := "http://nominatim.openstreetmap.org/search";

declare function geo:search($query  as xs:string,
                           $min-lon as xs:float, $min-lat as xs:float,
                           $max-lon as xs:float, $max-lat as xs:float) {

   let $view-box := string-join(($min-lon, $min-lat, $max-lon, $max-lat), ",")
   let $lon      := ($min-lon + $max-lon) div 2
   let $lat      := ($min-lat + $max-lat) div 2
   let $uri      := escape-uri(xs:anyURI(concat(
                       $geo:search-service-uri,
                       "?format=xml",
                       "&amp;viewbox=", $view-box,
                       "&amp;addressdetails=1&amp;limit=100",
                       "&amp;polygon=0",
                       "&amp;q=", $query
                    )), false())
   let $response := http:get($uri, false(), ())

   let $results  := $response//searchresults

   return
       element {name($results)} {
           $response/@*,
           for $i in $results/place
           order by ($lon - $i/@lon) * ($lon - $i/@lon) + ($lat - $i/@lat) * ($lat - $i/@lat)
           return $i
       }

};

declare function geo:search($query as xs:string, $view-box as xs:float*) {
   geo:search($query, $view-box[1], $view-box[2], $view-box[3], $view-box[4])
};

declare function geo:search($query as xs:string) {
   geo:search($query, -180, -90, 180, 90)
};

Credits edit

All the work was done by Evgeny Gazdovsky. The writeup was done by Dan McCreary.

Section 4: XRX Patterns edit

Content Routing edit

Motivation edit

You want all your XForms application to us a single save.xq submission. This makes it easier to autogenerate XForms applications and makes it easier to maintain insert and update operations in a central location.

Method edit

Create a single save XQuery that inspects the content of a POST operation. Use the name of the root element or other content patterns within the submitted XML file to process the incoming saved documents. Use a table-driven rule engine to determine if the incoming XML document is a new record (and requires a new ID assigned), an update (and requires versioning), or whether other business rules apply.


Back : User Manager Next: URL Rewriting

URL Rewriting edit

Motivation edit

You want the URLs/URIs in your system to reflect the logical structure of a service, not the collection structure of your database.

Method 1: Use the Jetty Rewrite Handler edit

Use the Jetty Rewrite Handler that can be installed with the Jetty Server. The RewriteHandler uses an XML configuration file to store the rewrite patterns of a URI. This file can contain regular expressions that allow a single rule to be used rewrite a large number of URIs.

Method 2: Use an Atom interface edit

The eXist native XML database has an Atom Publishing Protocol interface. You can use this to create new URL interfaces to existing XQueries.

Example edit

Suppose you use XQuery to execute a report of all terms that have changed in the last N days for project P. Using an eXist XQuery the URL might be

http://example.com/exist/rest/db/apps/glossary/views/terms-changed.xq?days=N&project=P/


The URL rewrite might be:

http://example.com/terms/new/days=N/project=P/

This makes the URL become a more stable application program interface and allows you to move the XQuery to another location in the eXist database and change parameter names without impacting your consumers, their bookmarks, or other systems that access this data through a REST interface.

Example Using Apache Mod Rewrite edit

The most popular way to implement URL rewriting is to use the Apache web server as a front end to your web application. This allows you to convert "dynamic" URLs into ones that appear to be "static", where there are no question marks or ampersands in the URL string and parameters are passed as though they were folders rather than key-value pairs.

Here is the code that would be added to the Apache configuration file:

Options +FollowSymLinks
RewriteEngine on
RewriteRule terms/new/days=(.*)/project=(.*)/ db/apps/glossary/views/terms-changed.xq?days=$1&project=$2

See also edit

An XForms application to manage Jetty rewrite URLS.

References edit


Back: Content Routing