Posts filed under ‘XForms’

Ann: eXist-db 2.0 released


Why this news on the betterFORM blog?

The answer is simple: eXist Solutions (the company behind eXist-db) and betterFORM have joined forces to integrate their two long-standing projects and offer commercial support for this new platform. As true Open Sourcers we’ll continue to offer this new product as free, Open Source software. Not much change here. But additionally there now will be subscription-based support for those that need long-term reliability for their commercial applications.

Beyond having integrated our products to work seamlessly with each other we have also joined our skill set and experience in the XML domain. We offer this joint expertise to our customers as consulting, development and training services to enable them to build better applications in less time.

See the new eXist-db homepage for details.

Advertisements

February 11, 2013 at 12:29 pm Leave a comment

Taking MIPs to the next level


Since: betterFORM 5.0rc1 – buildNumber:11612

Model Item Properties (MIPs) are one of the central features of XForms. In short they allow to add constraints to XML nodes. The available MIPs are:

  • readonly – boolean XPath expression
  • required – boolean XPath expression
  • calculate – XPath expression
  • relevant – boolean XPath expression
  • constraint – boolean XPath expression
  • type – XML Schema simple datatype or extension thereof

However with XForms 1.1 each of these MIPs were allowed to be attached to a node only once. If a bind element happened to attach one MIP to same node a second time the processor would throw a XFormsBindingException.

This was rather limiting expecially with respect to the ‘constraint’ MIP as in practice it often happens that you’d like to test for more than one condition. Of course you can combine conditions with a simple ‘and’ like so:

<xf:bind nodeset="a" constraint=". &gt; 5 and .  &lt; 10" />

But this leads to long and hard to read expressions. Wouldn’t it be much nicer to be able to write:

<xf:bind nodeset="a" constraint=". &gt; 5" />
<xf:bind nodeset="a" constraint=". &lt; 10" />

MIPs in XForms 2.0…

This is exactly what is addressed in the upcoming XForms 2.0. It now allows that one and the same MIP is attached to the same node several time and defines some combination rules. This table was taken from the current XForms 2.0 Draft.

property combination
type* all
constraint all
relevant all
required one
readonly one
calculate not allowed
p3ptype not allowed

*To be honest we still couldn’t make much sense out of the fact that the ‘type’ MIP is also allowed to be combined as the use cases seem to be rather exotic. But probably it just lacks a good example. For the time being we haven’t considered that in our implementation.

‘all’ means that all conditions are chained with a boolean ‘and’ so all conditions must become true for the constraint validation to become true. ‘one’ on the other hand combines the statement with boolean ‘or’.

…and beyond

The combination rules for ‘constraint’, ‘relevant’, ‘required’ and ‘readonly’ are now implemented in betterFORM. But we went some steps further and also picked up some of the ideas from a  post published under the name ‘MIP Names, Type, Etc.’ on the W3C XForms group wiki.

Besides using the syntax shown above you are now able to write the following statements:

<xf:bind ref="a">
    <bf:constraint  value=". &gt; 5"/>
    <bf:constraint value=". &lt; 10" />
</xf:bind>

The custom element ‘constraint’ in the betterFORM namespace is an alternative notation that allows some interesting options beyond making the markup even more readable. See this:

<xf:bind ref="a">
    <bf:constraint  value=". &gt; 5">
        <xf:alert>The value must be bigger than 5</xf:alert>
    </bf:constraint>
    <bf:constraint value=". &lt; 10" >
        <xf:alert>The value must be smaller than 10</xf:alert>
    </bf:constraint>
</xf:bind>

Here we find our good old friend the ‘alert’ element which XForms authors know as one of the child elements of XForms controls to signal an error. The alerts are now attached to a single constraint MIP and allow to tell the user exactly which of multiple constraints has failed in a given situation.

But there’s even more. In some situations even a single condition might be complicated and therefore hard to read. This is especially true when it’s crammed into an attribute. Now you can also write:

<xf:bind ref="a">
    <bf:constraint>
        <bf:value>
            my really long and complicated 
            possibly multiline XPath
        </bf:value>
        <xf:alert>The value must be bigger than 5</xf:alert>
    </bf:constraint>
</xf:bind>

Besides for constraint also

<bf:relevant/>
<bf:required/>
<bf:readonly/>

are  supported and useable in the same way.

Of course the new custom syntax fully harmonizes with the standard syntax so the following will be perfectly valid:

<xf:bind ref="a">
    <bf:constraint  value=". &gt; 5">
        <xf:alert>The value must be bigger than 5</xf:alert>
    </bf:constraint>
</xf:bind>
...
<xf:bind nodeset="a" constraint=". &lt; 10"/>

One Question left

Great, now we have the ability to signal to the user what exactly went wrong, combine several occurrences of a MIP and we can trigger each UI control bound to that node. But what if we do not want that? It might be even confusing if alert messages pop up in the UI at several places.

So we need a mechanism to selectively and explicitly say that we want an error to be displayed for a given control. We currently solved this like so:

<xf:input ref="a">
    <xf:label>a text</xf:label>
    <xf:alert srcBind="aBind"/>
</xf:input>

‘srcBind’ is an idref and points to an existing bind with id=’aBind’. This is taken as the root element for all alerts that might be displayed (if their respective constraint fails) or not displayed (if their respective constraint passes). The xf:alert in the UI becomes a container for all messages whose constraint fails and are displayed as a stack.

alerts

We hope these extensions are useful and welcome your feedback.

December 19, 2012 at 1:32 am 1 comment

bf:include – an alternative to XInclude


Since: betterFORM 5.0rc1 – buildNumber:11523
Author: Lars Wndauer

When building applications with many forms and especially when using a lot of subforms you’re often facing a lot of markup redundancy. This article shows a new utility in betterFORM to avoid such redundant parts thereby making your applications more maintainable.

In the XML world there is the well-known XInclude specification that handles includes in the XML markup language. However this specification only handles very simple file inclusions. If you have more fine-grained snippets to assemble you have to go for XPointer in addition which has unfortunately some shortcomings – it’s simply not or only partly implemented in many environments and has a somewhat crude syntax.

This article shows a more pragmatic approach which has been developed by the betterFORM team during its work on a large customer project.

Example 1 – simple inclusion 

<xf:model>
    <xf:instance id="i-contacts" src="data/contacts.xml"/>
    <bf:include src="templates/binds-contacts.xml"/>
</xf:model>

This isn’t differnt to what you would get from XInclude. It simply includes a complete file and replaces the <bf:include /> tag with that.

If authors don’t want to create a bind file for any group of binds but to manage them in a single file this is possible as well. Imagine a bind.xml as shown in example 2.

Example 2 – inclusion of a specific element

This example goes one step further allowing you to address a specific id in the referenced file. Consider the XML snippet below:

<binds>
  <bind id="b-contacts" nodeset="contacts">
      <bind nodeset="user/@shortname" constraint="string-length(.) &gt; 5" />
  </bind>
  <bind id="b-events" nodeset="contacts">
     …     
  </bind
</bind>

To reference the “b-contact” bind in your form simply use the fragment identifier on the URI: <bf:include src=”templates/binds.xml#b-contacts />.

This technique already allows to group similar contents together in one file but to use them in different contexts.

Form authors will quickly see how useful this small extension is, but they will also notice that in the real world things are often not as easy as this. Consider example 3.

Example 3 – add some markup to the inclusion

Often when building large form applications with XForms you’re facing markup that is largely the same in different contexts. This is especially true with many submissions that load the same data in different parts of the application. But you have to repeat the whole block just to add a different a different error-handler as the message in the example below:

<xf:submission id="s-load-data" resource="context:payload" method="get" replace="instance" instance="i-default">
      <xf:message id="load-data-error-handler" ev:event="xforms-submit-error" ref="instance('i-lang')/msg[@key='submissionLoadError']"/>
 </xf:submission>

To handle this use case the bf:include mechanism provides an additional bf:action attribute that can be used to add some markup.

This can be achieved with the following markup:

<bf:include src="templates/submission.xml#s-load-data">
    <xf:message bf:action="append" id="load-data-error-handler" ev:event="xforms-submit-error" (...)
</bf:include/>

Please note the bf:action attribute here. This tells the engine to append the xf:message element to the included submission with id=”s-load-data”.

Extend Included Markup

Markup included via bf:include can easily be extended using the bf:action attribute. Example 4 shows how the bf:action attribute value “append” is used to chain a submission transforming the instance data to the “s-load-data” submission.

Example 4 – Append 

<bf:include src="templates/submission.xml#s-load-data">
   <xf:send bf:action="append" ev:event="xforms-submit-done"  submission="s-transform-data"/>
</bf:include/>

Overwrite Included Markup

Another scenario that comes up quite often is that a single piece of the included markup must be overwritten with some other markup. Example 5 shows how this can be achieved. Here the error message from example 3 is overwritten with another error submission handler:

<bf:include src="templates/submission.xml#s-load-data">
   <xf:send id="load-data-error-handler" bf:action="overwrite" ev:event="xforms-submit-done"  submission="s-error-handler"/>
</bf:include/>

It should be noted that this inclusion mechanism can be applied at deploy- or runtime. If you want to apply inclusions at runtime you have to configure betterFORM to do so by editing the betterform-config.xml file and setting the property “webprocessor.doIncludes” to “true”.

Conclusion

No question bf:include especially in combination with the XForms subforms mechanism is a powerful technique for form authors when writing maintainable enterprise applications.  The bf:action attributes will be available in the upcoming betterFORM 5 release. If you can’t wait simply check out the betterFORM development branch at https://github.com/betterFORM/betterFORM/tree/development and get started.

Note: the inclusion is currently still not yet integrated within the build process of betterFORM. This will follow at a later stage.

November 10, 2012 at 5:14 pm Leave a comment

XForms model to model communication


XForms has a MVC architecture and this it what makes it so powerful when it comes to build complex applications that reach far beyond a few fields with validation. But as applications grow there naturally comes the requirement for modularization. Models are the building blocks of an XForms application.

XForms already supports multiple models on one page, each with its own, encapsulated instances and constraints. But models must be able to exchange some data in order to act as a whole in a larger architecture. Sometimes this just means to pass a single or only a few values and sometimes this means to exchange whole instances. At the same time a model should enforce some kind of encapsulation as known from object-oriented languages to ensure consistency and robustness.

Before discussing possible ways to enable model-to-model communication it should be mentioned that there is already a pattern to exchange instances between models. As two models existing in a page can perfectly use the same instance URI to load and submit data it’s already possible to ‘share’ those. But first this comes to the price of serializing the data as XML and re-parsing them when loading into the other model. Second – there will be two (detached) copies of the same instance – one in each model that need to be synchronized with each other.

Alternatives for data-exchange between models

Though XForms is not a new technology there are no established patterns for data-exchange between models. The following paragraphs are based upon our experiences with this topic. Though the need has existed before the upcoming of subforms has made the need for some kind of data-exchange more urgent as for maximum re-use of subforms (which can be full-fledged standalone forms in other contexts) you’ll likely want your models to be as independent and loosely coupled as possible.

The following paragraphs present some approaches we found during our project work over time. It should be noted that these techniques are custom betterFORM extensions to the standard but equivalents might be found in other XForms implementations.

Cross-model submissions

If complete instances or larger parts of them need to be shared between models a cross-model submission is the right answer. An example might an address model that shall be re-used within a larger context that also needs the address instance data structure for incorporation into one of its instances.

An example:

<xf:submission ref="instance()" resource="context:aKey" method="put" replace="none" ...

A bit of background will help to understand what the ContextSubmissionHandler is doing. In betterFORM each instance of a XForms processor maintains a hashmap called ‘context’ with params. E.g. this map holds URL params and http headers that were used to call the XForms page and serves as a way to pass in some values for use within the form. The above submission stores the default instance into this map under the key ‘aKey’. To fetch the instance from that map the following submission can be  used:

<xf:submission ref="instance('another')" resource="context:aKey" method="get" replace="instance" ...

Here the ‘get’ method is used to read the previously stored instance from the map and replace instance ‘another’ with it. For safety the submissionhandler is consuming which means that the instance will be deleted automatically from the ‘context’ hashmap once it has been read.

This kind of submission is especially useful in conjunction with subforms – if you are using multiple subforms e.g. in a wizard and you want to pass instance data from one subform to another the context hashmap is the right way to do it. Form A might already have  been destroyed when loading form B but the instance data can survive within the context hashmap and be used in the next step of the wizard.

Another advantage of cross-model submissions is that one model can re-use the validation constraints and logic of another without breaking the encapsulation between the models as data exit and enter a model through its interface – the submission module. As the data are exchanged directly between models within a single instance of an XForms processor its reasonably safe to rely on validations done by a foreign model without duplicating binding constraints in both models.

The instanceOfModel() function

If there are only a few values to be passed the instanceOfModel(modelId, instanceId) function is a way to do this. It takes two params – the id of a model and the id of an instance within that model.

Example:

<xf:setvalue ref="instanceOfModel('modelA', 'default')/baz" value="instanceOfModel('modelB','default')/foo/bar"/>

In this case the value of the XPath ‘foo/bar’ in the ‘default’ instance of  ‘modelB’ will be set to the node ‘baz’ in the instance ‘default’ of ‘modelA’. Easy enough but this function also is somewhat dangerous as users might expect this function to work in any context. E.g. they might expect that instanceOfModel can also be used on bind elements. But this would create inter-model dependencies which is not a healthy thing to do. The XForms dependency engine was designed to efficiently handle dependencies within one model but not to create dependencies between models. Ignoring this could cause serious consequences for the consistency of data.

For this reason this function should be used with caution. As long as its used in conjunction with the setvalue action everything should be fine but you shouldn’t use it within a model. Therefore if in doubt the method proposed in the next section should be preferred.

Dispatching custom events

Dispatching a custom event with contextinfo is a less intrusive way of passing values between models than the instanceOfModel() function (and avoids its dangers). It uses the ability of XML Events to carry context information along with the actual event. A small addition to the standard XForms dispatch action allows you to specify custom parameters for a event and pass a set of values to a target in another model. As always an example illustrates this best.

Somewhere within the scope of model ‘A’ a dispatch action is fired. It passes two params as contextinfo to the event. The first one is a static string while the second (someXPath) pulls a value from an instance in model ‘A’.

<xf:group model="A">
---
  <xf:dispatch name="customEvent" targetid="foo">
    <xf:contextinfo name="param1" value="'hello'"/>
    <xf:contextinfo name="param2" value="someXPath"/>
  </xf:dispatch>
...
</xf:group>

Somewhere  in the scope of Model ‘B’ a handler for ‘customEvent’ is defined and can access the passed params with the standard XForms event() function. – That easy.

<group model="B" id="foo">
    <xf:action ev:event="customEvent">
        <xf:setvalue ref="staticResult" value="event('param1')"/>
        <xf:setvalue ref="xpathResult" value="event('param2')"/>
    </xf:action>
...
</xf:group>

Though this method uses a bit more markup than the instanceOfModel approach it avoids its dangers. As it passes data by value and not by reference it honors the encapsulation of models.

This article was intended to show some alternatives for the model-to-model communication in XForms. However it is assumed that this is not the end of the discussion and other ways will be invented and hopefully be standardized in future version of XForms.

As always comments and suggestions are more than welcome.

March 26, 2012 at 11:36 pm 1 comment

Aeronautical Information Services (AIS) and XForms


One area where we give ourselves in the hand of technology is air traffic. As soon as we enter an airplane we have to trust a complex machinery and hope that machines, pilots and ground personal are doing their job right and bring us safely back to the ground. But flying does not only involve airplanes and well-trained pilots but mountains of data about airports, airspaces, run ways, weather conditions etc. etc.

A important acronym in this domain is ATM which stands for ‘Air Traffic Management’. Two of the most important organizations in air traffic are the Federal Aviation Association (FAA) and Eurocontrol. Together both organizations developed a conceptual model of the whole aeronautical domain and also offer an implementation of that model in XML Schema. This is the technological foundation for Aeronautical Information Services (AIS) that allow the digital exchange of data between all relevant parties.

However it takes more than a schema to build an application. The schema describes all the entities, objects and their relationships and it’s up to the software vendors to build complete solutions from that. All the data that are needed to ensure a high level of safety in the operation of air traffic must be entered by humans and those need good user interfaces to do their job. The gathered information is then used on-flight to give pilots up-to-date information about outages, special conditions at certain airports, restricted airspaces and so forth.

This is where XForms comes into play. The aforementioned schema consists of about 4 megabytes of schema definitions and about 150 ‘features’ and same amount of ‘objects’ as they are called in this model. These ‘features’ and ‘objects’ can then be combined with each other, nested within each other and linked to other ‘features’ and ‘objects’. All this happens at runtime when ground operators build complex messages from the available set of entities.

Though clean from a conceptual view this highly dynamic model puts heavy burdens on the engineers and eliminates any possibility to build static user interfaces to support all types of messages. Fortunately the authors of XForms decided to build it upon XML and as such it is ideally suited to be generated from a formal description such as a XML Schema.

The application described here was build by a leading vendor of Air Traffic Control (ATC) solutions.Their engineers have choosen a SOA approach to provide maximum flexibility and connectivity with other applications. In the backend a large Oracle database is used to store all the collected data. The whole user interface for collecting and maintaining the data is generated from the XML Schema resulting in about 300 separate XForms documents using several dozens of custom datatypes. Each form comes with its own template instance which represents one entity from the schema. Those template instances are then aggregated at runtime to build a certain message to be stored in the database.

The necessary aggregation of instances and associated user interfaces was a problem that couldn’t be solved elegantly with XForms 1.1. The key to success was a tiny extension to XForms allowing forms to be embedded in already loaded ones. With an additional value ’embed’ for the attribute ‘show’ of the XForms load action and an attribute ‘targetid’ this extension is only a very small addition to the language set of XForms. Here’s an example:

<xf:load show="embed' targetid="foo">
    <xf:resource value="'myResource'"/>
 </xf:load>

This extension has already been proposed for XForms 2.0 which is currently under development.

Additional challenges had to be met: we needed a way to exchange instance data of a subform with their respective ‘master’ and to allow to ‘mount’ some piece of data into the master instance to build a complete messages. We developed several solutions to this that do not break the encapsulation of the target model but discussion of the details is left to another blog post. Further we had to find a user interface presentation that allows an operator to keep the overview over a complex document. To solve this we implemented a tabbed control behaving like a XForms repeat – for each loaded subform a tab is dynamically added and shows the respective subform within it. An additional breadcrumb navigation helps to keep track of the hierarchy in the document. Last but not least the instance data of a stored message are not allowed to contain empty nodes. Thus when loading a stored message for editing the data had to be merged with their template instances. This was solved with a XSLT transform to be executed whenever a message was read from the web-service layer of the database.

After heavy and serious testing the customer was able to deliver the first implementation of the standard worldwide which is now in production use in several countries. Soon after first release the generative approach proved itself valuable as the application needed to be migrated to a new XML Schema version. With a single excecution of the XForms generator the new forms were available and could be used right away.

We think this project is an impressive example of the power of XForms. To summarize the facts:

  • 300 generated XForms documents
  • about 50 custom datatypes
  • a highly dynamic user interface embedded inside of a portal
  • about 4 megabyte of raw XML Schema data using about a dozen of different namespaces
  • massive use of optional elements
  • all forms connected to SOAP web-services
  • running in a standard web browser without any plugins
  • not a single line of script code was necessary to implement the forms

October 12, 2011 at 2:19 pm 4 comments

XML Summer School is using betterFORM


The XML Summer School is now using the eXist XML database and betterFORM for their trainings. They have setup a demo application which allows the students to experience the power of XML. Here’s a screenshot of this application:

The teaching application itself is Open Source and can be downloaded from sourceforge.

September 19, 2011 at 2:49 pm Leave a comment

Getting started with betterFORM limeGreen


This post guides you through the first steps of installing, running and using betterFORM limeGreen. Though betterFORM can be run in a variety of deployments and configurations this document focusses on betterFORM XRX. This is a bundling of the eXistDB XML database with betterFORM in a ready-to-run configuration along with a dashboard to help you manage your applications.

Requirements

  • Java 1.5 installed on your system

Installation

Before you start you need an installation of betterFORM XRX.

  1. Goto the sourceforge download page and get the latest version. This will download the betterFORM installer.
  2. After the download is finished double-click the betterform-install.jar to open it. Please note that Java must be installed on your system and jar files must be configured as executable (which is the case by default).
  3. After you have accepted the license terms and specified the installation directory you will presented with some readme information. Skip that screen to get into the package selection.

select the packages to install

The installer will offer several different options for betterFORM:

  • installation of a WAR for direct deployment into your webcontainer
  • betterFORM standalone bundles the Jetty webcontainer and comes with a graphical application starter
  • betterFORM XRX which integrates betterFORM with eXistDB as a standalone application platform

For the scope of this article we deal with betterFORM XRX which is already selected by default in the installer. Just hit the ‘Next’ button to go to installation screen.

During this step the betterFORM will be installed on your disk.

The following screen lets you choose a password for the ‘admin’ user. Please memorize this password as otherwise you’ll be locked out of the database. If you are just test-driving you may leave the password empty.

After this is done hit ‘Next’ a last time to finish the installer.

Running betterFORM

In standalone (Jetty) and XRX mode betterFORM can be run via a Java executable jar. To run it double-click it in your favorite file browser or execute the following command line:

>java -jar betterform.jar

A small application with betterFORM logo will appear.

Clicking on the icon will run your default web browser with the entry page.

Dashboard

betterFORM limeGreen now comes with a new facility for managing your XForms application resources like XForms documents, CSS and JavaScript files, XQueries and transformations, images etc.

The ‘Dashboard’ will be the default entry page when you browse a new betterFORM installation. Besides browsing and running the XForms, you can add files and collections to your application and view the source.

Feature Explorer

One of the items found on the Dashboard root level is the Feature Explorer. It is the central location for running the reference forms of betterFORM. They serve as a live documentation of XForms and betterFORM features and show you working markup examples and the rendered output.

Some of the pages will have links to the respective sections in the XForms 1.1 Recommendation and the XForms 1.1 Quick Reference documents for quick lookup of syntax details.

On the right hand there is a  CSS reference that highlights the matched elements when hovered.

The Feature Explorer will be extended with further examples as new features become available.

Under the hood Feature Explorer makes heavy use of form embedding. All embedded forms can also be run standalone by browsing the ‘reference’ collection with the Dashboard and clicking on the respective link.

Running the XRX demo application

Start your browser and goto:http://localhost:8080/betterform/rest/db/betterform/apps/timetracker/index.xql or execute index.xql from Dashboard. This should show you a screen like this:

Timetracker XRX demo application

Please note that this is only the first version (0.1) of the application and we plan to continuously extend it to show more features and solution patterns. Therefore not all menu buttons are already working.

Please use the ‘filter’ to select a start date before 01. June 2010 to see all the sample data.

What Timetracker does

TimeTracker is a simple application to track the time spend on a project and we use it internally log our working hours. As such TimeTracker gathers small XML files in a database collection and allows to create, edit, delete and filter those entries. The whole application consists of a bunch of files organized in a collection hierarchy stored in the database.

TimeTracker uses XForms, REST and XQuery to model a complete application. The XForms are generated by XQuery code and XForms embedding is used to assemble the different documents into one single-page application. The data can be filtered by several criteria and are returned as a plain HTML table. The table is then embedded into the main page of the application (index.xql).

filtering tasks in TimeTracker

A single entry in our time tracking is represented by a ‘task’. Tasks are always shown for the selected date range which is displayed in the upper right. By choosing the ‘Filter’ button from the toolbar you can change the displayed date range. Please play with the filter fields to see more or less entries. The form selects a default date range that starts 30 days before today.

A first glimpse behind the scenes

To see the gears TimeTracker was made of it’s best to use the eXistDB Java admin client and take a look into the XQueries and XForms that make up the application. To access the admin client please first goto http://localhost:8080/betterform. In the right upper corner of the Dashboard you find a blue button with an eXist icon. Hit that button and the admin client will download and start automatically.

After you have confirmed the execution of the admin client the following screen will be presented:

eXistDB admin client login

Please use the password you have choosen during install to login and close the dialog. Otherwise the application will not show up.

Go to collection ‘betterform/apps/timetracker’. This is the root collection of the application. You can directly access any file or collection by double-clicking on it. You should start your investigation with ‘index.xql’ with is the main page of the application. All other documents or generated contents will be embedded dynamically into this page. index.xql is simply an XForms document wrapped as an XQuery. XQuery does not do anything here. It’s just to see it’s possible 😉 Instead we could have just stored a XHTML document here.

Further files of interest are ‘edit/edit-item.xql‘ which is the creation and editing form for the application and ‘list-items.xql’ which is responsible for generating the results table. You will find some more files in the database and not all of them are already active.

This is only a first glimpse on the application and betterFORM XRX. A follow-up post will go more into the technical details of how the pieces fit together and show how easy and powerful the combination of XForms REST and XQuery is.

February 24, 2011 at 3:18 pm 1 comment

Older Posts


Recent Posts

betterFORM tweets


%d bloggers like this: