diff --git a/src/ui/step/StepUiBuilder.js b/src/ui/step/StepUiBuilder.js
new file mode 100644
index 0000000..22c0333
--- /dev/null
+++ b/src/ui/step/StepUiBuilder.js
@@ -0,0 +1,269 @@
+/**
+ * Builds UI from template
+ *
+ * Copyright (C) 2015 LoVullo Associates, Inc.
+ *
+ * This file is part of liza.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
A problem was encountered ' + + 'while attempting to view this step.
'; + } + + // enclose it in a div so that we have a single element we can query, + // making our lives much easier + ui.setContent( + $( '' ) + .attr( 'id', '__step' + ui.getStep().getId() ) + .html( data.content.html ) + ); + + // free the content from memory, as it's no longer needed (we don't need + // both the DOM representation and the string representation in memory + // for the life of the script - it's a waste) + delete data.content; + + // create the group objects + this._createGroups( ui ); + + // track changes so we know when to validate and post + ui.setDirtyTrigger(); + + // let others do any final processing before we consider ourselves + // ready + ui.emit( ui.__self.$( 'EVENT_POST_PROCESS' ) ); + }, + + + /** + * Instantiates Group objects for each group in the step content, then + * styles them + * + * TODO: refactor into own builder + * + * @param {StepUi} ui new ui instance + * + * @return {undefined} + */ + 'private _createGroups': function( ui ) + { + // reference to self for use in closure + var _self = this, + groups = {}, + group = null, + group_id = 0, + + step = ui.getStep(); + + // instantiate a group object for each of the groups within this step + var $groups = ( ui.getContent().find( '.stepGroup' ) ).each( function() + { + group = _self._groupBuilder( $( this ), _self._elementStyler ); + group_id = group.getGroupId(); + + groups[ group_id ] = group; + + // let the step know what fields it contains + step.addExclusiveFieldNames( + group.getGroup().getExclusiveFieldNames() + ); + + _self._hookGroup( group, ui ); + } ); + + // XXX: remove public property assignment + ui.groups = groups; + ui.initGroupFieldData(); + + // we can style all the groups, since the elements that cannot be styled + // (e.g. table groups) have been removed already + _self._elementStyler.apply( $groups, false ); + }, + + + /** + * Hook various group events for processing + * + * @param {GroupUi} group group to hook + * @param {StepUi} ui new ui instance + * + * @return {undefined} + */ + 'private _hookGroup': function( group, ui ) + { + group + .invalidate( function() + { + ui.invalidate(); + } ) + .on( 'indexAdd', function( index ) + { + ui.emit( ui.__self.$( 'EVENT_INDEX_ADD' ), index, this ); + } ) + .on( 'indexRemove', function( index ) + { + ui.emit( ui.__self.$( 'EVENT_INDEX_REMOVE' ), index, this ); + } ).on( 'indexReset', function( index ) + { + ui.emit( ui.__self.$( 'EVENT_INDEX_RESET' ), index, this ); + } ) + .on( 'action', function( type, ref, index ) + { + // simply forward + ui.emit( ui.__self.$( 'EVENT_ACTION' ), type, ref, index ); + } ) + .on( 'postAddRow', function( index ) + { + ui.emit( 'postAddRow', index ); + } ); + } +} );