Search

A suite of templates used to rapidly build HTML forms that play nicely with Symphony events.

I found myself repeatedly typing a lot of similar code for creating form elements. Stuff like checked whether a checkbox is selected and writing in the checked attribute, or conditionally writing in value attributes either from a Data Source or from the post-values nodeset of an evens. Tiresome and rather boring.

So I’ve put together form-controls.xsl as a utility to building form elements with the core aim of:

  • optionally pre-populating them with values from a Data Source (or static values)
  • persisting postback values when an event fails (even if they were previously pre-populated)
  • ability to associate labels with controls, optionally wrap labels around controls, add validation class to labels
  • adding feedback to the HTML when a field is invalid (class attribute)
  • providing better validation messages and error response

It’s still an early version (0.2) but I’ve used previous versions on many commercial sites already so it has been fairly well tested. v0.2 is entirely refactored.

Download

http://github.com/nickdunn/form-controls/tree/master

Usage

The documentation for something like this is rather large, so I’ve commented each template as best I can, describing the parameters for each. The utility provides templates for the following:

  • validation-summary — given an event, it will output a message for each invalid field. Custom messages can be provided for specific fields, even specific states (missing or invalid). Defaults to Symphony’s own messages.
  • control-is-valid — used internally, but returns boolean if a field is invalid after postback
  • control-name, control-id — works out the HTML-friendly @name and @id attributes for a form element (i.e. converts “title” to “fields[title]”)
  • label
  • checkbox
  • radio
  • input
  • textarea
  • select (with some clever presets)

I should probably add that to make this utility as useful as possible to everyone, your feedback about Symphony Events and the HTML forms in general would be incredibly useful. Any additional nuisances, niggles, suggestions or comments I'd love to build into this utility.

This is very interesting. Form creation is part of my job that really drives me nuts.

So, you create an .xsl doc for each form you create? I'm curious, why not store the 'instance' data in e.g. form-test.xsl in a flavor of xml that would trigger the templates you call? In other words, store the form instance in xml, and have it transformed to the final xhtml.

Yes, since the form is a normal form element in a page XSL file. Rather than type each field, they are replaced with a call-template.

For example:

<xsl:call-template name="form:input">
  <xsl:with-param name="event" select="/data/events/create-article"/>
  <xsl:with-param name="handle" select="'title'"/>
</xsl:call-template>

Will create the following HTML:

<input type="text" id="fields-title" name="fields[title]" />

If the event create-article fires, the input will be given a value attribute with the posted value.

The "instance" could well be XML. The entire form structure could be stored as XML and the form built at runtime by triggering the templates in this utility. I plan to do this using the data from the Section Schemas extension (which outputs a Symphony Section as XML for this purpose). If I modify if to output the Main/Side position, and field order, we can fairly accurately rebuild a Section form dynamically in front end XSLT :-)

However forms are never that simple. Login forms, registration forms, contact forms... they are never quite as simple as a default Symphony form. They often require additional fieldsets/legends, labels, meta elements stored in spans, div containers in order to implement complex designs etc. Much of the time I am implementing a form designed by a visual designer, and in these instances (and in my experience, about 80% of forms I create) an auto-generated form from an XML structure wouldn't suffice.

I haven't tried it out yet, but from what you describe, this sounds absolutely beautiful! Now I can understand why you went to the trouble of fixing the front end checkbox bug.

You've been very busy, and I really appreciate all the work you've been putting into Symphony extensions, Nick. I finally get a chance to work on a redesign for our own site, so you're helping to make the exploration phase a lot of fun. And your timing has been perfect.

Yes, ditto bauhouse's appreciation.

 "we can fairly accurately rebuild a Section form dynamically in front end XSLT :-)"

I think this is The Way To Go. I know it is a ways off, but I would love to see all of Symphony produced by Symphony, like django.

Cheers all. With respect to the front end checkbox bug, the utility will work without the patch anyway. It writes a hidden form field with a no value, and the checkbox after it in the source will overwrite the value to yes if checked. So this template should work back to Symphony 2 rev5.

I'll put together a proof of concept tying these two concepts together (Form Controls utility and Section Schemas) to see what I can come up with. In fact I have an XForms book on the way so I can think about this some more. I'm hoping the W3C have done a lot of the thinking already particularly with respect to a sensible XML schema for storing form structure.

I can see this being useful for generating your own entry create/edit forms within a CMS, but I wouldn't think it would ever be useful for creating public-facing registration/login type forms. In these examples you would need to use the more elemental templates for each individual form control (as per the example).

This utility does rather excite me. I've not found anything else like this yet using XSLT. The server controls in ASP.NET are close (I come from an ASP.NET background, so this was the inspiration), and XSLTForms is similar but not quite the same.

Ok, here goes. This routine assumes the following:

  • you have a section Articles
  • you have installed the Section Schemas extension and have created a DS for this section and attached to the page
  • you have created an Event named Publish Article and attached to the page

The template with the control mode should be re-usable. It checks the type attribute for each field returned in the XML and executes the corresponding template. I've included the common field types but obviously fields provided by extensions won't be matched.

http://gist.github.com/95047

Adding prefilled values to these form controls would be a case of creating a $value variable inside the third template which takes the name() and queries another DS XML to find that field's value. Each call-template would then need an additional with-parameter to pass this value variable.

Really great stuff! Thanks.

Just tried this out to dynamically generate the front end form. Posted a new article. Works great, Nick! But only if I remove the references to the location attribute in the template you linked on Github. Instead of this:

<xsl:if test="*[@location='main']">
    <div id="main">         
        <xsl:apply-templates select="*[@location='main']" mode="control">
            <xsl:sort select="@sortorder" data-type="number" order="ascending"/>
        </xsl:apply-templates>
    </div>
</xsl:if>

<xsl:if test="*[@location='sidebar']">
    <div id="sidebar">          
        <xsl:apply-templates select="*[@location='sidebar']" mode="control">
            <xsl:sort select="@sortorder" data-type="number" order="ascending"/>
        </xsl:apply-templates>
    </div>
</xsl:if>

I'm using this:

<div id="main">         
    <xsl:apply-templates select="*" mode="control">
        <xsl:sort select="@sortorder" data-type="number" order="ascending"/>
    </xsl:apply-templates>
</div>

You mentioned:

If I modify it to output the Main/Side position, and field order, we can fairly accurately rebuild a Section form dynamically in front end XSLT :-)

So, have you successfully added the location attribute to your development copy of the Section Schemas extension and not pushed it to Github yet?

Now, I've pulled some values from one of the section entries into the form by adding a global values variable at the top of the example stylesheet that filters by the $title URL parameter:

<xsl:variable name="event" select="/data/events/publish-article"/>
<xsl:variable name="values" select="/data/articles/entry[title/@handle = $title]"/>

Then, with an Articles page configured with a single URL parameter, title, I can dynamically pull the values of each entry into the corresponding form fields. If the title is not supplied, the form fields remain blank. That way, if the entry title is supplied, a conditional instruction can include the entry id to enable editing of the entry:

<xsl:template match="section-schema" mode="form">

    <form action="" method="post">

        <xsl:if test="$title"><input name="id" type="hidden" value="{$values/@id}" /></xsl:if>

        <xsl:call-template name="form:validation-summary">
            <xsl:with-param name="event" select="$event"/>
        </xsl:call-template>

        <div id="main">         
            <xsl:apply-templates select="*" mode="control">
                <xsl:sort select="@sortorder" data-type="number" order="ascending"/>
            </xsl:apply-templates>
        </div>

        <input type="submit" name="action[publish-article]" value="Submit" class="submit-button" />

    </form>

</xsl:template>

And values can be inserted into the form fields by adding a name parameter and the with-param name="value" instruction for the field values (I'm only showing the input and textarea fields for brevity):

<xsl:template match="*" mode="control">

    <xsl:param name="name" select="name()"/>

    <xsl:choose>

        <xsl:when test="@type='input' or @type='order_entries' or @type='number'">
            <xsl:call-template name="form:label">
                <xsl:with-param name="event" select="$event"/>
                <xsl:with-param name="for" select="name()"/>
                <xsl:with-param name="text" select="@label"/>
                <xsl:with-param name="child">
                    <xsl:call-template name="form:input">
                        <xsl:with-param name="event" select="$event"/>
                        <xsl:with-param name="handle" select="name()"/>
                        <xsl:with-param name="value" select="$values/*[name() = $name]"/>
                    </xsl:call-template>
                </xsl:with-param>
            </xsl:call-template>    
        </xsl:when>

        <xsl:when test="@type='textarea'">
            <xsl:call-template name="form:label">
                <xsl:with-param name="event" select="$event"/>
                <xsl:with-param name="for" select="name()"/>
                <xsl:with-param name="text" select="@label"/>
                <xsl:with-param name="child">
                    <xsl:call-template name="form:textarea">
                        <xsl:with-param name="event" select="$event"/>
                        <xsl:with-param name="handle" select="name()"/>
                        <xsl:with-param name="rows" select="size"/>
                        <xsl:with-param name="value" select="$values/*[name() = $name]"/>
                    </xsl:call-template>
                </xsl:with-param>
            </xsl:call-template>
        </xsl:when>

    </xsl:choose>

</xsl:template>

I think this would make generating my ensembles so much easier. I think I would need the "Show column" attribute included in the XML to generate forms in table format for editing multiple entries, though.

Awesome work, Nick!

I quite like the way the Section Schema data source is automatically updated when fields are added to the section. Nice touch.

Nick, am I right that the select box field does not currently pull in static options into the Section Schemas data source?

Hmm. Including an upload field caused some strange behaviour for a select box field. The select box options would not display when using the following code:

<xsl:when test="@type='select' or @type='selectbox_link' or @type='pages' or @type='author'">
    <xsl:call-template name="form:label">
        <xsl:with-param name="event" select="$event"/>
        <xsl:with-param name="for" select="name()"/>
        <xsl:with-param name="text" select="@label"/>
        <xsl:with-param name="child">
            <xsl:call-template name="form:select">
                <xsl:with-param name="event" select="$event"/>
                <xsl:with-param name="handle" select="name()"/>
                <xsl:with-param name="multiple" select="allow_multiple_selection"/>
                <xsl:with-param name="options" select="options/*"/>
                <xsl:with-param name="value" select="$values/*[name() = $name]/item"/>
            </xsl:call-template>
        </xsl:with-param>
    </xsl:call-template>
</xsl:when>

Plus, there didn't seem to be a way to specify a value for an upload field. Is this possible, or has it not been added yet?

I've updated Section Schemas to fix the Select Box bug.

I'm not sure about the connection between an Upload field breaking a Select Box field. Could you explain further?

I'm not sure it's even possible to specify a default value for an upload field. It's a browser limitation (security feature I presume). Symphony gets around this by writing a hidden form element of the same name, with the path to the file. Only if you choose to change the file does it write in the input with a type of file.

You could add this functionality to the choose statement above. I may add this to the Form Controls utility itself, but I'm reluctant to since its aim isn't to reproduce Symphony's backend — the aim is to make building front-end forms easier.

All I can say about the upload field behaviour is that it seemed weirdly intermittent and could very well have been Safari 4 acting strangely with some weird sort of caching issue. I'll investigate a little further.

About the default value for the upload field, that makes sense. I should have looked under the hood a little more to see how this works in Symphony. Since I am trying to reproduce Symphony's backend, it would be nice to have the option to reproduce the same functionality, including location.

In which case I'd suggest:

  • check if the uploaded value exists, if so write a hidden input field with its value (Form Controls doesn't offer this since it's a trivial task and values don't change on postback)
  • use the form:input to build an input with the same name, but immediately clone its HTML with JavaScript and remove from the DOM
  • provide a "Change file" link and apply JavaScript to remove the hidden field and replace it with the upload field HTML

Form Controls has the sole aim of delivering the elements of building generic form controls themselves, specifically for front end events. Integration with Section Schemas to replicate Symphony's backend functionality will certainly require a level of work within the intermediary template that converts Section Schema XML into call-templates to the Form Controls utility.

Perhaps this middle-man template deserves its own utility. "Section Schema to HTML"? I'd like to keep the two separate to avoid confusion.

@bauhaus, I forgot to push that final change to Github, apologies. It's in there now.

http://github.com/nickdunn/section_schemas/tree/master

@nickdunn, thanks!

So, you've added the location attribute, and that's working well. However, I'm no longer seeing any options in the XML for tags or select boxes.

A typo in the PHP, I was using a PHP or || when it needed an XPath or |. Resolved and updated on Github.

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details