diff --git a/dist/bootstrap-decorator.js b/dist/bootstrap-decorator.js
deleted file mode 100644
index 09aabb8af..000000000
--- a/dist/bootstrap-decorator.js
+++ /dev/null
@@ -1,78 +0,0 @@
-angular.module("schemaForm").run(["$templateCache", function($templateCache) {$templateCache.put("directives/decorators/bootstrap/actions-trcl.html","<div class=\"btn-group schema-form-actions {{form.htmlClass}}\" ng-transclude=\"\"></div>");
-$templateCache.put("directives/decorators/bootstrap/actions.html","<div class=\"btn-group schema-form-actions {{form.htmlClass}}\"><input ng-repeat-start=\"item in form.items\" type=\"submit\" class=\"btn {{ item.style || \'btn-default\' }} {{form.fieldHtmlClass}}\" value=\"{{item.title}}\" ng-if=\"item.type === \'submit\'\"> <button ng-repeat-end=\"\" class=\"btn {{ item.style || \'btn-default\' }} {{form.fieldHtmlClass}}\" type=\"button\" ng-disabled=\"form.readonly\" ng-if=\"item.type !== \'submit\'\" ng-click=\"buttonClick($event,item)\"><span ng-if=\"item.icon\" class=\"{{item.icon}}\"></span>{{item.title}}</button></div>");
-$templateCache.put("directives/decorators/bootstrap/array.html","<div sf-array=\"form\" class=\"schema-form-array {{form.htmlClass}}\" ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\"><h3 ng-show=\"form.title && form.notitle !== true\">{{ form.title }}</h3><ol class=\"list-group\" ng-model=\"modelArray\" ui-sortable=\"\"><li class=\"list-group-item {{form.fieldHtmlClass}}\" ng-repeat=\"item in modelArray track by $index\"><button ng-hide=\"form.readonly || form.remove === null\" ng-click=\"deleteFromArray($index)\" style=\"position: relative; z-index: 20;\" type=\"button\" class=\"close pull-right\"><span aria-hidden=\"true\">&times;</span><span class=\"sr-only\">Close</span></button><sf-decorator ng-init=\"arrayIndex = $index\" form=\"copyWithIndex($index)\"></sf-decorator></li></ol><div class=\"clearfix\" style=\"padding: 15px;\"><button ng-hide=\"form.readonly || form.add === null\" ng-click=\"appendToArray()\" type=\"button\" class=\"btn {{ form.style.add || \'btn-default\' }} pull-right\"><i class=\"glyphicon glyphicon-plus\"></i> {{ form.add || \'Add\'}}</button></div><div class=\"help-block\" ng-show=\"(hasError() && errorMessage(schemaError())) || form.description\" ng-bind-html=\"(hasError() && errorMessage(schemaError())) || form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/checkbox.html","<div class=\"checkbox schema-form-checkbox {{form.htmlClass}}\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}\"><label class=\"{{form.labelHtmlClass}}\"><input type=\"checkbox\" sf-changed=\"form\" ng-disabled=\"form.readonly\" ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\" schema-validate=\"form\" class=\"{{form.fieldHtmlClass}}\" name=\"{{form.key.slice(-1)[0]}}\"> <span ng-bind-html=\"form.title\"></span></label><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/checkboxes.html","<div sf-array=\"form\" ng-model=\"$$value$$\" class=\"form-group schema-form-checkboxes {{form.htmlClass}}\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}\"><label class=\"control-label {{form.labelHtmlClass}}\" ng-show=\"showTitle()\">{{form.title}}</label><div class=\"checkbox\" ng-repeat=\"val in titleMapValues track by $index\"><label><input type=\"checkbox\" ng-disabled=\"form.readonly\" sf-changed=\"form\" class=\"{{form.fieldHtmlClass}}\" ng-model=\"titleMapValues[$index]\" name=\"{{form.key.slice(-1)[0]}}\"> <span ng-bind-html=\"form.titleMap[$index].name\"></span></label></div><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/default.html","<div class=\"form-group schema-form-{{form.type}} {{form.htmlClass}}\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess(), \'has-feedback\': form.feedback !== false }\"><label class=\"control-label {{form.labelHtmlClass}}\" ng-class=\"{\'sr-only\': !showTitle()}\" for=\"{{form.key.slice(-1)[0]}}\">{{form.title}}</label> <input ng-if=\"!form.fieldAddonLeft && !form.fieldAddonRight\" ng-show=\"form.key\" type=\"{{form.type}}\" step=\"any\" sf-changed=\"form\" placeholder=\"{{form.placeholder}}\" class=\"form-control {{form.fieldHtmlClass}}\" id=\"{{form.key.slice(-1)[0]}}\" ng-model-options=\"form.ngModelOptions\" ng-model=\"$$value$$\" ng-disabled=\"form.readonly\" schema-validate=\"form\" name=\"{{form.key.slice(-1)[0]}}\" aria-describedby=\"{{form.key.slice(-1)[0] + \'Status\'}}\"><div ng-if=\"form.fieldAddonLeft || form.fieldAddonRight\" ng-class=\"{\'input-group\': (form.fieldAddonLeft || form.fieldAddonRight)}\"><span ng-if=\"form.fieldAddonLeft\" class=\"input-group-addon\" ng-bind-html=\"form.fieldAddonLeft\"></span> <input ng-show=\"form.key\" type=\"{{form.type}}\" step=\"any\" sf-changed=\"form\" placeholder=\"{{form.placeholder}}\" class=\"form-control {{form.fieldHtmlClass}}\" id=\"{{form.key.slice(-1)[0]}}\" ng-model-options=\"form.ngModelOptions\" ng-model=\"$$value$$\" ng-disabled=\"form.readonly\" schema-validate=\"form\" name=\"{{form.key.slice(-1)[0]}}\" aria-describedby=\"{{form.key.slice(-1)[0] + \'Status\'}}\"> <span ng-if=\"form.fieldAddonRight\" class=\"input-group-addon\" ng-bind-html=\"form.fieldAddonRight\"></span></div><span ng-if=\"form.feedback !== false\" class=\"form-control-feedback\" ng-class=\"evalInScope(form.feedback) || {\'glyphicon\': true, \'glyphicon-ok\': hasSuccess(), \'glyphicon-remove\': hasError() }\" aria-hidden=\"true\"></span> <span ng-if=\"hasError() || hasSuccess()\" id=\"{{form.key.slice(-1)[0] + \'Status\'}}\" class=\"sr-only\">{{ hasSuccess() ? \'(success)\' : \'(error)\' }}</span><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/fieldset-trcl.html","<fieldset ng-disabled=\"form.readonly\" class=\"schema-form-fieldset {{form.htmlClass}}\"><legend ng-show=\"form.title\">{{ form.title }}</legend><div class=\"help-block\" ng-show=\"form.description\" ng-bind-html=\"form.description\"></div><div ng-transclude=\"\"></div></fieldset>");
-$templateCache.put("directives/decorators/bootstrap/fieldset.html","<fieldset ng-disabled=\"form.readonly\" class=\"schema-form-fieldset {{form.htmlClass}}\"><legend ng-show=\"form.title\">{{ form.title }}</legend><div class=\"help-block\" ng-show=\"form.description\" ng-bind-html=\"form.description\"></div><sf-decorator ng-repeat=\"item in form.items\" form=\"item\"></sf-decorator></fieldset>");
-$templateCache.put("directives/decorators/bootstrap/help.html","<div class=\"helpvalue schema-form-helpvalue {{form.htmlClass}}\" ng-bind-html=\"form.helpvalue\"></div>");
-$templateCache.put("directives/decorators/bootstrap/radio-buttons.html","<div class=\"form-group schema-form-radiobuttons {{form.htmlClass}}\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}\"><div><label class=\"control-label {{form.labelHtmlClass}}\" ng-show=\"showTitle()\">{{form.title}}</label></div><div class=\"btn-group\"><label class=\"btn {{ (item.value === $$value$$) ? form.style.selected || \'btn-default\' : form.style.unselected || \'btn-default\'; }}\" ng-class=\"{ active: item.value === $$value$$ }\" ng-repeat=\"item in form.titleMap\"><input type=\"radio\" class=\"{{form.fieldHtmlClass}}\" sf-changed=\"form\" style=\"display: none;\" ng-disabled=\"form.readonly\" ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\" schema-validate=\"form\" ng-value=\"item.value\" name=\"{{form.key.join(\'.\')}}\"> <span ng-bind-html=\"item.name\"></span></label></div><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/radios-inline.html","<div class=\"form-group schema-form-radios-inline {{form.htmlClass}}\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}\"><label class=\"control-label {{form.labelHtmlClass}}\" ng-show=\"showTitle()\">{{form.title}}</label><div><label class=\"radio-inline\" ng-repeat=\"item in form.titleMap\"><input type=\"radio\" class=\"{{form.fieldHtmlClass}}\" sf-changed=\"form\" ng-disabled=\"form.readonly\" ng-model=\"$$value$$\" schema-validate=\"form\" ng-value=\"item.value\" name=\"{{form.key.join(\'.\')}}\"> <span ng-bind-html=\"item.name\"></span></label></div><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/radios.html","<div class=\"form-group schema-form-radios {{form.htmlClass}}\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}\"><label class=\"control-label {{form.labelHtmlClass}}\" ng-show=\"showTitle()\">{{form.title}}</label><div class=\"radio\" ng-repeat=\"item in form.titleMap\"><label><input type=\"radio\" class=\"{{form.fieldHtmlClass}}\" sf-changed=\"form\" ng-disabled=\"form.readonly\" ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\" schema-validate=\"form\" ng-value=\"item.value\" name=\"{{form.key.join(\'.\')}}\"> <span ng-bind-html=\"item.name\"></span></label></div><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/section.html","<div class=\"schema-form-section {{form.htmlClass}}\"><sf-decorator ng-repeat=\"item in form.items\" form=\"item\"></sf-decorator></div>");
-$templateCache.put("directives/decorators/bootstrap/select.html","<div class=\"form-group {{form.htmlClass}} schema-form-select\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess(), \'has-feedback\': form.feedback !== false}\"><label class=\"control-label {{form.labelHtmlClass}}\" ng-show=\"showTitle()\">{{form.title}}</label><select ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\" ng-disabled=\"form.readonly\" sf-changed=\"form\" class=\"form-control {{form.fieldHtmlClass}}\" schema-validate=\"form\" ng-options=\"item.value as item.name group by item.group for item in form.titleMap\" name=\"{{form.key.slice(-1)[0]}}\"></select><div class=\"help-block\" sf-message=\"form.description\"></div></div>");
-$templateCache.put("directives/decorators/bootstrap/submit.html","<div class=\"form-group schema-form-submit {{form.htmlClass}}\"><input type=\"submit\" class=\"btn {{ form.style || \'btn-primary\' }} {{form.fieldHtmlClass}}\" value=\"{{form.title}}\" ng-disabled=\"form.readonly\" ng-if=\"form.type === \'submit\'\"> <button class=\"btn {{ form.style || \'btn-default\' }}\" type=\"button\" ng-click=\"buttonClick($event,form)\" ng-disabled=\"form.readonly\" ng-if=\"form.type !== \'submit\'\"><span ng-if=\"form.icon\" class=\"{{form.icon}}\"></span> {{form.title}}</button></div>");
-$templateCache.put("directives/decorators/bootstrap/tabarray.html","<div sf-array=\"form\" ng-init=\"selected = { tab: 0 }\" class=\"clearfix schema-form-tabarray schema-form-tabarray-{{form.tabType || \'left\'}} {{form.htmlClass}}\"><div ng-if=\"!form.tabType || form.tabType !== \'right\'\" ng-class=\"{\'col-xs-3\': !form.tabType || form.tabType === \'left\'}\"><ul class=\"nav nav-tabs\" ng-class=\"{ \'tabs-left\': !form.tabType || form.tabType === \'left\'}\"><li ng-repeat=\"item in modelArray track by $index\" ng-click=\"$event.preventDefault() || (selected.tab = $index)\" ng-class=\"{active: selected.tab === $index}\"><a href=\"#\">{{interp(form.title,{\'$index\':$index, value: item}) || $index}}</a></li><li ng-hide=\"form.readonly\" ng-click=\"$event.preventDefault() || (selected.tab = appendToArray().length - 1)\"><a href=\"#\"><i class=\"glyphicon glyphicon-plus\"></i> {{ form.add || \'Add\'}}</a></li></ul></div><div ng-class=\"{\'col-xs-9\': !form.tabType || form.tabType === \'left\' || form.tabType === \'right\'}\"><div class=\"tab-content {{form.fieldHtmlClass}}\"><div class=\"tab-pane clearfix\" ng-repeat=\"item in modelArray track by $index\" ng-show=\"selected.tab === $index\" ng-class=\"{active: selected.tab === $index}\"><sf-decorator ng-init=\"arrayIndex = $index\" form=\"copyWithIndex($index)\"></sf-decorator><button ng-hide=\"form.readonly\" ng-click=\"selected.tab = deleteFromArray($index).length - 1\" type=\"button\" class=\"btn {{ form.style.remove || \'btn-default\' }} pull-right\"><i class=\"glyphicon glyphicon-trash\"></i> {{ form.remove || \'Remove\'}}</button></div></div></div><div ng-if=\"form.tabType === \'right\'\" class=\"col-xs-3\"><ul class=\"nav nav-tabs tabs-right\"><li ng-repeat=\"item in modelArray track by $index\" ng-click=\"$event.preventDefault() || (selected.tab = $index)\" ng-class=\"{active: selected.tab === $index}\"><a href=\"#\">{{interp(form.title,{\'$index\':$index, value: item}) || $index}}</a></li><li ng-hide=\"form.readonly\" ng-click=\"$event.preventDefault() || appendToArray()\"><a href=\"#\"><i class=\"glyphicon glyphicon-plus\"></i> {{ form.add || \'Add\'}}</a></li></ul></div></div>");
-$templateCache.put("directives/decorators/bootstrap/tabs.html","<div ng-init=\"selected = { tab: 0 }\" class=\"schema-form-tabs {{form.htmlClass}}\"><ul class=\"nav nav-tabs\"><li ng-repeat=\"tab in form.tabs\" ng-disabled=\"form.readonly\" ng-click=\"$event.preventDefault() || (selected.tab = $index)\" ng-class=\"{active: selected.tab === $index}\"><a href=\"#\">{{ tab.title }}</a></li></ul><div class=\"tab-content {{form.fieldHtmlClass}}\"><div class=\"tab-pane\" ng-disabled=\"form.readonly\" ng-repeat=\"tab in form.tabs\" ng-show=\"selected.tab === $index\" ng-class=\"{active: selected.tab === $index}\"><bootstrap-decorator ng-repeat=\"item in tab.items\" form=\"item\"></bootstrap-decorator></div></div></div>");
-$templateCache.put("directives/decorators/bootstrap/textarea.html","<div class=\"form-group has-feedback {{form.htmlClass}} schema-form-textarea\" ng-class=\"{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}\"><label class=\"{{form.labelHtmlClass}}\" ng-class=\"{\'sr-only\': !showTitle()}\" for=\"{{form.key.slice(-1)[0]}}\">{{form.title}}</label> <textarea ng-if=\"!form.fieldAddonLeft && !form.fieldAddonRight\" class=\"form-control {{form.fieldHtmlClass}}\" id=\"{{form.key.slice(-1)[0]}}\" sf-changed=\"form\" placeholder=\"{{form.placeholder}}\" ng-disabled=\"form.readonly\" ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\" schema-validate=\"form\" name=\"{{form.key.slice(-1)[0]}}\"></textarea><div ng-if=\"form.fieldAddonLeft || form.fieldAddonRight\" ng-class=\"{\'input-group\': (form.fieldAddonLeft || form.fieldAddonRight)}\"><span ng-if=\"form.fieldAddonLeft\" class=\"input-group-addon\" ng-bind-html=\"form.fieldAddonLeft\"></span> <textarea class=\"form-control {{form.fieldHtmlClass}}\" id=\"{{form.key.slice(-1)[0]}}\" sf-changed=\"form\" placeholder=\"{{form.placeholder}}\" ng-disabled=\"form.readonly\" ng-model=\"$$value$$\" ng-model-options=\"form.ngModelOptions\" schema-validate=\"form\" name=\"{{form.key.slice(-1)[0]}}\"></textarea> <span ng-if=\"form.fieldAddonRight\" class=\"input-group-addon\" ng-bind-html=\"form.fieldAddonRight\"></span></div><span class=\"help-block\" sf-message=\"form.description\"></span></div>");}]);
-angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) {
-  var base = 'directives/decorators/bootstrap/';
-
-  decoratorsProvider.defineDecorator('bootstrapDecorator', {
-    textarea: {template: base + 'textarea.html', replace: false},
-    fieldset: {template: base + 'fieldset.html', replace: false},
-    /*fieldset: {template: base + 'fieldset.html', replace: true, builder: function(args) {
-      var children = args.build(args.form.items, args.path + '.items');
-      console.log('fieldset children frag', children.childNodes)
-      args.fieldFrag.childNode.appendChild(children);
-    }},*/
-    array: {template: base + 'array.html', replace: false},
-    tabarray: {template: base + 'tabarray.html', replace: false},
-    tabs: {template: base + 'tabs.html', replace: false},
-    section: {template: base + 'section.html', replace: false},
-    conditional: {template: base + 'section.html', replace: false},
-    actions: {template: base + 'actions.html', replace: false},
-    select: {template: base + 'select.html', replace: false},
-    checkbox: {template: base + 'checkbox.html', replace: false},
-    checkboxes: {template: base + 'checkboxes.html', replace: false},
-    number: {template: base + 'default.html', replace: false},
-    password: {template: base + 'default.html', replace: false},
-    submit: {template: base + 'submit.html', replace: false},
-    button: {template: base + 'submit.html', replace: false},
-    radios: {template: base + 'radios.html', replace: false},
-    'radios-inline': {template: base + 'radios-inline.html', replace: false},
-    radiobuttons: {template: base + 'radio-buttons.html', replace: false},
-    help: {template: base + 'help.html', replace: false},
-    'default': {template: base + 'default.html', replace: false}
-  }, []);
-
-  //manual use directives
-  decoratorsProvider.createDirectives({
-    textarea: base + 'textarea.html',
-    select: base + 'select.html',
-    checkbox: base + 'checkbox.html',
-    checkboxes: base + 'checkboxes.html',
-    number: base + 'default.html',
-    submit: base + 'submit.html',
-    button: base + 'submit.html',
-    text: base + 'default.html',
-    date: base + 'default.html',
-    password: base + 'default.html',
-    datepicker: base + 'datepicker.html',
-    input: base + 'default.html',
-    radios: base + 'radios.html',
-    'radios-inline': base + 'radios-inline.html',
-    radiobuttons: base + 'radio-buttons.html',
-  });
-
-}]).directive('sfFieldset', function() {
-  return {
-    transclude: true,
-    scope: true,
-    templateUrl: 'directives/decorators/bootstrap/fieldset-trcl.html',
-    link: function(scope, element, attrs) {
-      scope.title = scope.$eval(attrs.title);
-    }
-  };
-});
diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js
deleted file mode 100644
index d0af0c0ec..000000000
--- a/dist/bootstrap-decorator.min.js
+++ /dev/null
@@ -1 +0,0 @@
-angular.module("schemaForm").run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions-trcl.html",'<div class="btn-group schema-form-actions {{form.htmlClass}}" ng-transclude=""></div>'),e.put("directives/decorators/bootstrap/actions.html",'<div class="btn-group schema-form-actions {{form.htmlClass}}"><input ng-repeat-start="item in form.items" type="submit" class="btn {{ item.style || \'btn-default\' }} {{form.fieldHtmlClass}}" value="{{item.title}}" ng-if="item.type === \'submit\'"> <button ng-repeat-end="" class="btn {{ item.style || \'btn-default\' }} {{form.fieldHtmlClass}}" type="button" ng-disabled="form.readonly" ng-if="item.type !== \'submit\'" ng-click="buttonClick($event,item)"><span ng-if="item.icon" class="{{item.icon}}"></span>{{item.title}}</button></div>'),e.put("directives/decorators/bootstrap/array.html",'<div sf-array="form" class="schema-form-array {{form.htmlClass}}" ng-model="$$value$$" ng-model-options="form.ngModelOptions"><h3 ng-show="form.title && form.notitle !== true">{{ form.title }}</h3><ol class="list-group" ng-model="modelArray" ui-sortable=""><li class="list-group-item {{form.fieldHtmlClass}}" ng-repeat="item in modelArray track by $index"><button ng-hide="form.readonly || form.remove === null" ng-click="deleteFromArray($index)" style="position: relative; z-index: 20;" type="button" class="close pull-right"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><sf-decorator ng-init="arrayIndex = $index" form="copyWithIndex($index)"></sf-decorator></li></ol><div class="clearfix" style="padding: 15px;"><button ng-hide="form.readonly || form.add === null" ng-click="appendToArray()" type="button" class="btn {{ form.style.add || \'btn-default\' }} pull-right"><i class="glyphicon glyphicon-plus"></i> {{ form.add || \'Add\'}}</button></div><div class="help-block" ng-show="(hasError() && errorMessage(schemaError())) || form.description" ng-bind-html="(hasError() && errorMessage(schemaError())) || form.description"></div></div>'),e.put("directives/decorators/bootstrap/checkbox.html",'<div class="checkbox schema-form-checkbox {{form.htmlClass}}" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}"><label class="{{form.labelHtmlClass}}"><input type="checkbox" sf-changed="form" ng-disabled="form.readonly" ng-model="$$value$$" ng-model-options="form.ngModelOptions" schema-validate="form" class="{{form.fieldHtmlClass}}" name="{{form.key.slice(-1)[0]}}"> <span ng-bind-html="form.title"></span></label><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/checkboxes.html",'<div sf-array="form" ng-model="$$value$$" class="form-group schema-form-checkboxes {{form.htmlClass}}" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}"><label class="control-label {{form.labelHtmlClass}}" ng-show="showTitle()">{{form.title}}</label><div class="checkbox" ng-repeat="val in titleMapValues track by $index"><label><input type="checkbox" ng-disabled="form.readonly" sf-changed="form" class="{{form.fieldHtmlClass}}" ng-model="titleMapValues[$index]" name="{{form.key.slice(-1)[0]}}"> <span ng-bind-html="form.titleMap[$index].name"></span></label></div><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/default.html",'<div class="form-group schema-form-{{form.type}} {{form.htmlClass}}" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess(), \'has-feedback\': form.feedback !== false }"><label class="control-label {{form.labelHtmlClass}}" ng-class="{\'sr-only\': !showTitle()}" for="{{form.key.slice(-1)[0]}}">{{form.title}}</label> <input ng-if="!form.fieldAddonLeft && !form.fieldAddonRight" ng-show="form.key" type="{{form.type}}" step="any" sf-changed="form" placeholder="{{form.placeholder}}" class="form-control {{form.fieldHtmlClass}}" id="{{form.key.slice(-1)[0]}}" ng-model-options="form.ngModelOptions" ng-model="$$value$$" ng-disabled="form.readonly" schema-validate="form" name="{{form.key.slice(-1)[0]}}" aria-describedby="{{form.key.slice(-1)[0] + \'Status\'}}"><div ng-if="form.fieldAddonLeft || form.fieldAddonRight" ng-class="{\'input-group\': (form.fieldAddonLeft || form.fieldAddonRight)}"><span ng-if="form.fieldAddonLeft" class="input-group-addon" ng-bind-html="form.fieldAddonLeft"></span> <input ng-show="form.key" type="{{form.type}}" step="any" sf-changed="form" placeholder="{{form.placeholder}}" class="form-control {{form.fieldHtmlClass}}" id="{{form.key.slice(-1)[0]}}" ng-model-options="form.ngModelOptions" ng-model="$$value$$" ng-disabled="form.readonly" schema-validate="form" name="{{form.key.slice(-1)[0]}}" aria-describedby="{{form.key.slice(-1)[0] + \'Status\'}}"> <span ng-if="form.fieldAddonRight" class="input-group-addon" ng-bind-html="form.fieldAddonRight"></span></div><span ng-if="form.feedback !== false" class="form-control-feedback" ng-class="evalInScope(form.feedback) || {\'glyphicon\': true, \'glyphicon-ok\': hasSuccess(), \'glyphicon-remove\': hasError() }" aria-hidden="true"></span> <span ng-if="hasError() || hasSuccess()" id="{{form.key.slice(-1)[0] + \'Status\'}}" class="sr-only">{{ hasSuccess() ? \'(success)\' : \'(error)\' }}</span><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/fieldset-trcl.html",'<fieldset ng-disabled="form.readonly" class="schema-form-fieldset {{form.htmlClass}}"><legend ng-show="form.title">{{ form.title }}</legend><div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div><div ng-transclude=""></div></fieldset>'),e.put("directives/decorators/bootstrap/fieldset.html",'<fieldset ng-disabled="form.readonly" class="schema-form-fieldset {{form.htmlClass}}"><legend ng-show="form.title">{{ form.title }}</legend><div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div><sf-decorator ng-repeat="item in form.items" form="item"></sf-decorator></fieldset>'),e.put("directives/decorators/bootstrap/help.html",'<div class="helpvalue schema-form-helpvalue {{form.htmlClass}}" ng-bind-html="form.helpvalue"></div>'),e.put("directives/decorators/bootstrap/radio-buttons.html",'<div class="form-group schema-form-radiobuttons {{form.htmlClass}}" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}"><div><label class="control-label {{form.labelHtmlClass}}" ng-show="showTitle()">{{form.title}}</label></div><div class="btn-group"><label class="btn {{ (item.value === $$value$$) ? form.style.selected || \'btn-default\' : form.style.unselected || \'btn-default\'; }}" ng-class="{ active: item.value === $$value$$ }" ng-repeat="item in form.titleMap"><input type="radio" class="{{form.fieldHtmlClass}}" sf-changed="form" style="display: none;" ng-disabled="form.readonly" ng-model="$$value$$" ng-model-options="form.ngModelOptions" schema-validate="form" ng-value="item.value" name="{{form.key.join(\'.\')}}"> <span ng-bind-html="item.name"></span></label></div><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/radios-inline.html",'<div class="form-group schema-form-radios-inline {{form.htmlClass}}" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}"><label class="control-label {{form.labelHtmlClass}}" ng-show="showTitle()">{{form.title}}</label><div><label class="radio-inline" ng-repeat="item in form.titleMap"><input type="radio" class="{{form.fieldHtmlClass}}" sf-changed="form" ng-disabled="form.readonly" ng-model="$$value$$" schema-validate="form" ng-value="item.value" name="{{form.key.join(\'.\')}}"> <span ng-bind-html="item.name"></span></label></div><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/radios.html",'<div class="form-group schema-form-radios {{form.htmlClass}}" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}"><label class="control-label {{form.labelHtmlClass}}" ng-show="showTitle()">{{form.title}}</label><div class="radio" ng-repeat="item in form.titleMap"><label><input type="radio" class="{{form.fieldHtmlClass}}" sf-changed="form" ng-disabled="form.readonly" ng-model="$$value$$" ng-model-options="form.ngModelOptions" schema-validate="form" ng-value="item.value" name="{{form.key.join(\'.\')}}"> <span ng-bind-html="item.name"></span></label></div><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/section.html",'<div class="schema-form-section {{form.htmlClass}}"><sf-decorator ng-repeat="item in form.items" form="item"></sf-decorator></div>'),e.put("directives/decorators/bootstrap/select.html",'<div class="form-group {{form.htmlClass}} schema-form-select" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess(), \'has-feedback\': form.feedback !== false}"><label class="control-label {{form.labelHtmlClass}}" ng-show="showTitle()">{{form.title}}</label><select ng-model="$$value$$" ng-model-options="form.ngModelOptions" ng-disabled="form.readonly" sf-changed="form" class="form-control {{form.fieldHtmlClass}}" schema-validate="form" ng-options="item.value as item.name group by item.group for item in form.titleMap" name="{{form.key.slice(-1)[0]}}"></select><div class="help-block" sf-message="form.description"></div></div>'),e.put("directives/decorators/bootstrap/submit.html",'<div class="form-group schema-form-submit {{form.htmlClass}}"><input type="submit" class="btn {{ form.style || \'btn-primary\' }} {{form.fieldHtmlClass}}" value="{{form.title}}" ng-disabled="form.readonly" ng-if="form.type === \'submit\'"> <button class="btn {{ form.style || \'btn-default\' }}" type="button" ng-click="buttonClick($event,form)" ng-disabled="form.readonly" ng-if="form.type !== \'submit\'"><span ng-if="form.icon" class="{{form.icon}}"></span> {{form.title}}</button></div>'),e.put("directives/decorators/bootstrap/tabarray.html",'<div sf-array="form" ng-init="selected = { tab: 0 }" class="clearfix schema-form-tabarray schema-form-tabarray-{{form.tabType || \'left\'}} {{form.htmlClass}}"><div ng-if="!form.tabType || form.tabType !== \'right\'" ng-class="{\'col-xs-3\': !form.tabType || form.tabType === \'left\'}"><ul class="nav nav-tabs" ng-class="{ \'tabs-left\': !form.tabType || form.tabType === \'left\'}"><li ng-repeat="item in modelArray track by $index" ng-click="$event.preventDefault() || (selected.tab = $index)" ng-class="{active: selected.tab === $index}"><a href="#">{{interp(form.title,{\'$index\':$index, value: item}) || $index}}</a></li><li ng-hide="form.readonly" ng-click="$event.preventDefault() || (selected.tab = appendToArray().length - 1)"><a href="#"><i class="glyphicon glyphicon-plus"></i> {{ form.add || \'Add\'}}</a></li></ul></div><div ng-class="{\'col-xs-9\': !form.tabType || form.tabType === \'left\' || form.tabType === \'right\'}"><div class="tab-content {{form.fieldHtmlClass}}"><div class="tab-pane clearfix" ng-repeat="item in modelArray track by $index" ng-show="selected.tab === $index" ng-class="{active: selected.tab === $index}"><sf-decorator ng-init="arrayIndex = $index" form="copyWithIndex($index)"></sf-decorator><button ng-hide="form.readonly" ng-click="selected.tab = deleteFromArray($index).length - 1" type="button" class="btn {{ form.style.remove || \'btn-default\' }} pull-right"><i class="glyphicon glyphicon-trash"></i> {{ form.remove || \'Remove\'}}</button></div></div></div><div ng-if="form.tabType === \'right\'" class="col-xs-3"><ul class="nav nav-tabs tabs-right"><li ng-repeat="item in modelArray track by $index" ng-click="$event.preventDefault() || (selected.tab = $index)" ng-class="{active: selected.tab === $index}"><a href="#">{{interp(form.title,{\'$index\':$index, value: item}) || $index}}</a></li><li ng-hide="form.readonly" ng-click="$event.preventDefault() || appendToArray()"><a href="#"><i class="glyphicon glyphicon-plus"></i> {{ form.add || \'Add\'}}</a></li></ul></div></div>'),e.put("directives/decorators/bootstrap/tabs.html",'<div ng-init="selected = { tab: 0 }" class="schema-form-tabs {{form.htmlClass}}"><ul class="nav nav-tabs"><li ng-repeat="tab in form.tabs" ng-disabled="form.readonly" ng-click="$event.preventDefault() || (selected.tab = $index)" ng-class="{active: selected.tab === $index}"><a href="#">{{ tab.title }}</a></li></ul><div class="tab-content {{form.fieldHtmlClass}}"><div class="tab-pane" ng-disabled="form.readonly" ng-repeat="tab in form.tabs" ng-show="selected.tab === $index" ng-class="{active: selected.tab === $index}"><bootstrap-decorator ng-repeat="item in tab.items" form="item"></bootstrap-decorator></div></div></div>'),e.put("directives/decorators/bootstrap/textarea.html",'<div class="form-group has-feedback {{form.htmlClass}} schema-form-textarea" ng-class="{\'has-error\': form.disableErrorState !== true && hasError(), \'has-success\': form.disableSuccessState !== true && hasSuccess()}"><label class="{{form.labelHtmlClass}}" ng-class="{\'sr-only\': !showTitle()}" for="{{form.key.slice(-1)[0]}}">{{form.title}}</label> <textarea ng-if="!form.fieldAddonLeft && !form.fieldAddonRight" class="form-control {{form.fieldHtmlClass}}" id="{{form.key.slice(-1)[0]}}" sf-changed="form" placeholder="{{form.placeholder}}" ng-disabled="form.readonly" ng-model="$$value$$" ng-model-options="form.ngModelOptions" schema-validate="form" name="{{form.key.slice(-1)[0]}}"></textarea><div ng-if="form.fieldAddonLeft || form.fieldAddonRight" ng-class="{\'input-group\': (form.fieldAddonLeft || form.fieldAddonRight)}"><span ng-if="form.fieldAddonLeft" class="input-group-addon" ng-bind-html="form.fieldAddonLeft"></span> <textarea class="form-control {{form.fieldHtmlClass}}" id="{{form.key.slice(-1)[0]}}" sf-changed="form" placeholder="{{form.placeholder}}" ng-disabled="form.readonly" ng-model="$$value$$" ng-model-options="form.ngModelOptions" schema-validate="form" name="{{form.key.slice(-1)[0]}}"></textarea> <span ng-if="form.fieldAddonRight" class="input-group-addon" ng-bind-html="form.fieldAddonRight"></span></div><span class="help-block" sf-message="form.description"></span></div>')}]),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){var t="directives/decorators/bootstrap/";e.defineDecorator("bootstrapDecorator",{textarea:{template:t+"textarea.html",replace:!1},fieldset:{template:t+"fieldset.html",replace:!1},array:{template:t+"array.html",replace:!1},tabarray:{template:t+"tabarray.html",replace:!1},tabs:{template:t+"tabs.html",replace:!1},section:{template:t+"section.html",replace:!1},conditional:{template:t+"section.html",replace:!1},actions:{template:t+"actions.html",replace:!1},select:{template:t+"select.html",replace:!1},checkbox:{template:t+"checkbox.html",replace:!1},checkboxes:{template:t+"checkboxes.html",replace:!1},number:{template:t+"default.html",replace:!1},password:{template:t+"default.html",replace:!1},submit:{template:t+"submit.html",replace:!1},button:{template:t+"submit.html",replace:!1},radios:{template:t+"radios.html",replace:!1},"radios-inline":{template:t+"radios-inline.html",replace:!1},radiobuttons:{template:t+"radio-buttons.html",replace:!1},help:{template:t+"help.html",replace:!1},"default":{template:t+"default.html",replace:!1}},[]),e.createDirectives({textarea:t+"textarea.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",submit:t+"submit.html",button:t+"submit.html",text:t+"default.html",date:t+"default.html",password:t+"default.html",datepicker:t+"datepicker.html",input:t+"default.html",radios:t+"radios.html","radios-inline":t+"radios-inline.html",radiobuttons:t+"radio-buttons.html"})}]).directive("sfFieldset",function(){return{transclude:!0,scope:!0,templateUrl:"directives/decorators/bootstrap/fieldset-trcl.html",link:function(e,t,s){e.title=e.$eval(s.title)}}});
\ No newline at end of file
diff --git a/dist/schema-form.js b/dist/schema-form.js
deleted file mode 100644
index d9b93cf24..000000000
--- a/dist/schema-form.js
+++ /dev/null
@@ -1,2335 +0,0 @@
-(function(root, factory) {
-  if (typeof define === 'function' && define.amd) {
-    define(['angular', 'ObjectPath', 'tv4'], factory);
-  } else if (typeof exports === 'object') {
-    module.exports = factory(require('angular'), require('ObjectPath'), require('tv4'));
-  } else {
-    root.schemaForm = factory(root.angular, root.ObjectPath, root.tv4);
-  }
-}(this, function(angular, ObjectPath, tv4) {
-// Deps is sort of a problem for us, maybe in the future we will ask the user to depend
-// on modules for add-ons
-
-var deps = [];
-try {
-  //This throws an expection if module does not exist.
-  angular.module('ngSanitize');
-  deps.push('ngSanitize');
-} catch (e) {}
-
-try {
-  //This throws an expection if module does not exist.
-  angular.module('ui.sortable');
-  deps.push('ui.sortable');
-} catch (e) {}
-
-try {
-  //This throws an expection if module does not exist.
-  angular.module('angularSpectrumColorpicker');
-  deps.push('angularSpectrumColorpicker');
-} catch (e) {}
-
-var schemaForm = angular.module('schemaForm', deps);
-
-angular.module('schemaForm').provider('sfPath',
-[function() {
-  var sfPath = {parse: ObjectPath.parse};
-
-  // if we're on Angular 1.2.x, we need to continue using dot notation
-  if (angular.version.major === 1 && angular.version.minor < 3) {
-    sfPath.stringify = function(arr) {
-      return Array.isArray(arr) ? arr.join('.') : arr.toString();
-    };
-  } else {
-    sfPath.stringify = ObjectPath.stringify;
-  }
-
-  // We want this to use whichever stringify method is defined above,
-  // so we have to copy the code here.
-  sfPath.normalize = function(data, quote) {
-    return sfPath.stringify(Array.isArray(data) ? data : sfPath.parse(data), quote);
-  };
-
-  // expose the methods in sfPathProvider
-  this.parse = sfPath.parse;
-  this.stringify = sfPath.stringify;
-  this.normalize = sfPath.normalize;
-
-  this.$get = function() {
-    return sfPath;
-  };
-}]);
-
-/**
- * @ngdoc service
- * @name sfSelect
- * @kind function
- *
- */
-angular.module('schemaForm').factory('sfSelect', ['sfPath', function(sfPath) {
-  var numRe = /^\d+$/;
-
-  /**
-    * @description
-    * Utility method to access deep properties without
-    * throwing errors when things are not defined.
-    * Can also set a value in a deep structure, creating objects when missing
-    * ex.
-    * var foo = Select('address.contact.name',obj)
-    * Select('address.contact.name',obj,'Leeroy')
-    *
-    * @param {string} projection A dot path to the property you want to get/set
-    * @param {object} obj   (optional) The object to project on, defaults to 'this'
-    * @param {Any}    valueToSet (opional)  The value to set, if parts of the path of
-    *                 the projection is missing empty objects will be created.
-    * @returns {Any|undefined} returns the value at the end of the projection path
-    *                          or undefined if there is none.
-    */
-  return function(projection, obj, valueToSet) {
-    if (!obj) {
-      obj = this;
-    }
-    //Support [] array syntax
-    var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection;
-
-    if (typeof valueToSet !== 'undefined' && parts.length === 1) {
-      //special case, just setting one variable
-      obj[parts[0]] = valueToSet;
-      return obj;
-    }
-
-    if (typeof valueToSet !== 'undefined' &&
-        typeof obj[parts[0]] === 'undefined') {
-       // We need to look ahead to check if array is appropriate
-      obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {};
-    }
-
-    var value = obj[parts[0]];
-    for (var i = 1; i < parts.length; i++) {
-      // Special case: We allow JSON Form syntax for arrays using empty brackets
-      // These will of course not work here so we exit if they are found.
-      if (parts[i] === '') {
-        return undefined;
-      }
-      if (typeof valueToSet !== 'undefined') {
-        if (i === parts.length - 1) {
-          //last step. Let's set the value
-          value[parts[i]] = valueToSet;
-          return valueToSet;
-        } else {
-          // Make sure to create new objects on the way if they are not there.
-          // We need to look ahead to check if array is appropriate
-          var tmp = value[parts[i]];
-          if (typeof tmp === 'undefined' || tmp === null) {
-            tmp = numRe.test(parts[i + 1]) ? [] : {};
-            value[parts[i]] = tmp;
-          }
-          value = tmp;
-        }
-      } else if (value) {
-        //Just get nex value.
-        value = value[parts[i]];
-      }
-    }
-    return value;
-  };
-}]);
-
-
-// FIXME: type template (using custom builder)
-angular.module('schemaForm').factory('sfBuilder',
-['$templateCache', 'schemaFormDecorators', 'sfPath', function($templateCache, schemaFormDecorators, sfPath) {
-
-  var SNAKE_CASE_REGEXP = /[A-Z]/g;
-  var snakeCase = function(name, separator) {
-    separator = separator || '_';
-    return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
-      return (pos ? separator : '') + letter.toLowerCase();
-    });
-  };
-
-
-  var checkForSlot = function(form, slots) {
-    // Finally append this field to the frag.
-    // Check for slots
-    if (form.key) {
-      var slot = slots[sfPath.stringify(form.key)];
-      if (slot) {
-        while (slot.firstChild) {
-          slot.removeChild(slot.firstChild);
-        }
-        return slot;
-      }
-    }
-  };
-
-
-  var build = function(items, decorator, templateFn, slots, path) {
-    path = path || 'schemaForm.form';
-    var container = document.createDocumentFragment();
-    items.reduce(function(frag, f, index) {
-
-      // Sanity check.
-      if (!f.type) {
-        return;
-      }
-
-      var field = decorator[f.type] || decorator['default'];
-      if (!field.replace) {
-        // Backwards compatability build
-        var n = document.createElement(snakeCase(decorator.__name, '-'));
-        n.setAttribute('form', path + '[' + index + ']');
-        (checkForSlot(f, slots) || frag).appendChild(n);
-
-      } else {
-        var tmpl;
-
-        // TODO: Create a couple fo testcases, small and large and
-        //       measure optmization. A good start is probably a cache of DOM nodes for a particular
-        //       template that can be cloned instead of using innerHTML
-        var div = document.createElement('div');
-        var template = templateFn(field.template) || templateFn([decorator['default'].template]);
-        if (f.key) {
-          var key = f.key ?
-                    sfPath.stringify(f.key).replace(/"/g, '&quot;') : '';
-          template = template.replace(
-            /\$\$value\$\$/g,
-            'model' + (key[0] !== '[' ? '.' : '') + key
-          );
-        }
-
-        div.innerHTML = template;
-
-        // Move node to a document fragment, we don't want the div.
-        tmpl = document.createDocumentFragment();
-        while (div.childNodes.length > 0) {
-          tmpl.appendChild(div.childNodes[0]);
-        }
-
-
-        tmpl.firstChild.setAttribute('sf-field',path + '[' + index + ']');
-
-        // Possible builder, often a noop
-        field.builder({
-          fieldFrag: tmpl,
-          form: f,
-          path: path + '[' + index + ']',
-
-          // Recursive build fn
-          build: function(items, path) {
-            return build(items, decorator, templateFn, slots, path);
-          },
-
-        });
-
-        // Append
-        (checkForSlot(f, slots) || frag).appendChild(tmpl);
-      }
-      return frag;
-    }, container);
-
-    return container;
-  };
-
-
-/* FIXME: make a utility function of this ordinary case
-var transclusion = function() {
-  // We might be able to micro optimize here with some kind of setting
-  // or by checking the schema for the type (when we have those.)
-  // but a quick jsperf did 55 000 querySelectorAll per second (on my laptop),
-  // so I think this isn't the main performance hog.
-  var transclusions = tmpl.querySelectorAll('[sf-transclude]');
-
-  if (transclusions.length) {
-    // Before we do any transclusion we need clone the cache for later use, but just the first time.
-    if ([f.type] === tmpl) {
-      [f.type] = [f.type].cloneNode(true);
-    }
-
-    for (var i = 0; i < transclusions.length; i++) {
-      var n = transclusions[i];
-
-      // The sf-transclude attribute is not a directive, but has the name of what we're supposed to
-      // traverse. FIXME: Tabs? How do we loop over something that is not a list of forms?
-      // maybe callback?
-      var sub = form[n.getAttribute('sf-transclude')];
-      if (sub) {
-        sub = Array.isArray(sub) ? sub : [sub];
-
-        // Build the subform recursivly
-        n.appendChild( build(sub, templates, ) );
-
-      }
-    }
-  }
-
-}*/
-
-
-  var builder = {
-      /**
-       * Builds a form from a canonical form definition
-       */
-      build: function(form, decorator, slots) {
-        return build(form, decorator, function(url) {
-          return $templateCache.get(url) || '';
-        }, slots);
-
-      },
-      internalBuild: build
-  };
-  return builder;
-
-}]);
-
-angular.module('schemaForm').provider('schemaFormDecorators',
-['$compileProvider', 'sfPathProvider', function($compileProvider, sfPathProvider) {
-  var defaultDecorator = '';
-  var decorators = {};
-
-  // Map template after decorator and type.
-  var templateUrl = function(name, form) {
-    //schemaDecorator is alias for whatever is set as default
-    if (name === 'sfDecorator') {
-      name = defaultDecorator;
-    }
-
-    var decorator = decorators[name];
-    if (decorator[form.type]) {
-      return decorator[form.type].template;
-    }
-
-    //try default
-    return decorator['default'].template;
-  };
-
-  var createDirective = function(name) {
-    $compileProvider.directive(name,
-      ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage',
-       'sfPath','sfSelect',
-      function($parse,  $compile,  $http,  $templateCache, $interpolate, $q, sfErrorMessage,
-               sfPath, sfSelect) {
-
-        return {
-          restrict: 'AE',
-          replace: false,
-          transclude: false,
-          scope: true,
-          require: '?^sfSchema',
-          link: function(scope, element, attrs, sfSchema) {
-
-            //The ngModelController is used in some templates and
-            //is needed for error messages,
-            scope.$on('schemaFormPropagateNgModelController', function(event, ngModel) {
-              event.stopPropagation();
-              event.preventDefault();
-              scope.ngModel = ngModel;
-            });
-
-            //Keep error prone logic from the template
-            scope.showTitle = function() {
-              return scope.form && scope.form.notitle !== true && scope.form.title;
-            };
-
-            scope.listToCheckboxValues = function(list) {
-              var values = {};
-              angular.forEach(list, function(v) {
-                values[v] = true;
-              });
-              return values;
-            };
-
-            scope.checkboxValuesToList = function(values) {
-              var lst = [];
-              angular.forEach(values, function(v, k) {
-                if (v) {
-                  lst.push(k);
-                }
-              });
-              return lst;
-            };
-
-            scope.buttonClick = function($event, form) {
-              if (angular.isFunction(form.onClick)) {
-                form.onClick($event, form);
-              } else if (angular.isString(form.onClick)) {
-                if (sfSchema) {
-                  //evaluating in scope outside of sfSchemas isolated scope
-                  sfSchema.evalInParentScope(form.onClick, {'$event': $event, form: form});
-                } else {
-                  scope.$eval(form.onClick, {'$event': $event, form: form});
-                }
-              }
-            };
-
-            /**
-             * Evaluate an expression, i.e. scope.$eval
-             * but do it in sfSchemas parent scope sf-schema directive is used
-             * @param {string} expression
-             * @param {Object} locals (optional)
-             * @return {Any} the result of the expression
-             */
-            scope.evalExpr = function(expression, locals) {
-              if (sfSchema) {
-                //evaluating in scope outside of sfSchemas isolated scope
-                return sfSchema.evalInParentScope(expression, locals);
-              }
-
-              return scope.$eval(expression, locals);
-            };
-
-            /**
-             * Evaluate an expression, i.e. scope.$eval
-             * in this decorators scope
-             * @param {string} expression
-             * @param {Object} locals (optional)
-             * @return {Any} the result of the expression
-             */
-            scope.evalInScope = function(expression, locals) {
-              if (expression) {
-                return scope.$eval(expression, locals);
-              }
-            };
-
-            /**
-             * Interpolate the expression.
-             * Similar to `evalExpr()` and `evalInScope()`
-             * but will not fail if the expression is
-             * text that contains spaces.
-             *
-             * Use the Angular `{{ interpolation }}`
-             * braces to access properties on `locals`.
-             *
-             * @param  {string} content The string to interpolate.
-             * @param  {Object} locals (optional) Properties that may be accessed in the
-             *                         `expression` string.
-             * @return {Any} The result of the expression or `undefined`.
-             */
-            scope.interp = function(expression, locals) {
-              return (expression && $interpolate(expression)(locals));
-            };
-
-            //This works since we ot the ngModel from the array or the schema-validate directive.
-            scope.hasSuccess = function() {
-              if (!scope.ngModel) {
-                return false;
-              }
-              return scope.ngModel.$valid &&
-                  (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue));
-            };
-
-            scope.hasError = function() {
-              if (!scope.ngModel) {
-                return false;
-              }
-              return scope.ngModel.$invalid && !scope.ngModel.$pristine;
-            };
-
-            /**
-             * DEPRECATED: use sf-messages instead.
-             * Error message handler
-             * An error can either be a schema validation message or a angular js validtion
-             * error (i.e. required)
-             */
-            scope.errorMessage = function(schemaError) {
-              return sfErrorMessage.interpolate(
-                (schemaError && schemaError.code + '') || 'default',
-                (scope.ngModel && scope.ngModel.$modelValue) || '',
-                (scope.ngModel && scope.ngModel.$viewValue) || '',
-                scope.form,
-                scope.options && scope.options.validationMessage
-              );
-            };
-
-            // Rebind our part of the form to the scope.
-            var once = scope.$watch(attrs.form, function(form) {
-              if (form) {
-                // Workaround for 'updateOn' error from ngModelOptions
-                // see https://github.com/Textalk/angular-schema-form/issues/255
-                // and https://github.com/Textalk/angular-schema-form/issues/206
-                form.ngModelOptions = form.ngModelOptions || {};
-                scope.form  = form;
-
-                //ok let's replace that template!
-                //We do this manually since we need to bind ng-model properly and also
-                //for fieldsets to recurse properly.
-                var templatePromise;
-
-                // type: "template" is a special case. It can contain a template inline or an url.
-                // otherwise we find out the url to the template and load them.
-                if (form.type === 'template' && form.template) {
-                  templatePromise = $q.when(form.template);
-                } else {
-                  var url = form.type === 'template' ? form.templateUrl : templateUrl(name, form);
-                  templatePromise = $http.get(url, {cache: $templateCache}).then(function(res) {
-                                      return res.data;
-                                    });
-                }
-
-                templatePromise.then(function(template) {
-                  if (form.key) {
-                    var key = form.key ?
-                              sfPathProvider.stringify(form.key).replace(/"/g, '&quot;') : '';
-                    template = template.replace(
-                      /\$\$value\$\$/g,
-                      'model' + (key[0] !== '[' ? '.' : '') + key
-                    );
-                  }
-                  element.html(template);
-
-                  // Do we have a condition? Then we slap on an ng-if on all children,
-                  // but be nice to existing ng-if.
-                  if (form.condition) {
-
-                    var evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';
-                    if (form.key) {
-                      evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model' + sfPath.stringify(form.key) + '})';
-                    }
-
-                    angular.forEach(element.children(), function(child) {
-                      var ngIf = child.getAttribute('ng-if');
-                      child.setAttribute(
-                        'ng-if',
-                        ngIf ?
-                        '(' + ngIf +
-                        ') || (' + evalExpr +')'
-                        : evalExpr
-                      );
-                    });
-                  }
-                  $compile(element.contents())(scope);
-                });
-
-                // Where there is a key there is probably a ngModel
-                if (form.key) {
-                  // It looks better with dot notation.
-                  scope.$on(
-                    'schemaForm.error.' + form.key.join('.'),
-                    function(event, error, validationMessage, validity) {
-                      if (validationMessage === true || validationMessage === false) {
-                        validity = validationMessage;
-                        validationMessage = undefined;
-                      }
-
-                      if (scope.ngModel && error) {
-                        if (scope.ngModel.$setDirty) {
-                          scope.ngModel.$setDirty();
-                        } else {
-                          // FIXME: Check that this actually works on 1.2
-                          scope.ngModel.$dirty = true;
-                          scope.ngModel.$pristine = false;
-                        }
-
-                        // Set the new validation message if one is supplied
-                        // Does not work when validationMessage is just a string.
-                        if (validationMessage) {
-                          if (!form.validationMessage) {
-                            form.validationMessage = {};
-                          }
-                          form.validationMessage[error] = validationMessage;
-                        }
-
-                        scope.ngModel.$setValidity(error, validity === true);
-
-                        if (validity === true) {
-                          // Setting or removing a validity can change the field to believe its valid
-                          // but its not. So lets trigger its validation as well.
-                          scope.$broadcast('schemaFormValidate');
-                        }
-                      }
-                  });
-
-                  // Clean up the model when the corresponding form field is $destroy-ed.
-                  // Default behavior can be supplied as a globalOption, and behavior can be overridden in the form definition.
-                  scope.$on('$destroy', function() {
-                    // If the entire schema form is destroyed we don't touch the model
-                    if (!scope.externalDestructionInProgress) {
-                      var destroyStrategy = form.destroyStrategy ||
-                                            (scope.options && scope.options.destroyStrategy) || 'remove';
-                      // No key no model, and we might have strategy 'retain'
-                      if (form.key && destroyStrategy !== 'retain') {
-
-                        // Get the object that has the property we wan't to clear.
-                        var obj = scope.model;
-                        if (form.key.length > 1) {
-                          obj = sfSelect(form.key.slice(0, form.key.length - 1), obj);
-                        }
-
-                        // We can get undefined here if the form hasn't been filled out entirely
-                        if (obj === undefined) {
-                          return;
-                        }
-
-                        // Type can also be a list in JSON Schema
-                        var type = (form.schema && form.schema.type) || '';
-
-                        // Empty means '',{} and [] for appropriate types and undefined for the rest
-                        if (destroyStrategy === 'empty' && type.indexOf('string') !== -1) {
-                          obj[form.key.slice(-1)] = '';
-                        } else if (destroyStrategy === 'empty' && type.indexOf('object') !== -1) {
-                          obj[form.key.slice(-1)] = {};
-                        } else if (destroyStrategy === 'empty' && type.indexOf('array') !== -1) {
-                          obj[form.key.slice(-1)] = [];
-                        } else if (destroyStrategy === 'null') {
-                          obj[form.key.slice(-1)] = null;
-                        } else {
-                          delete obj[form.key.slice(-1)];
-                        }
-                      }
-                    }
-                  });
-                }
-
-                once();
-              }
-            });
-          }
-        };
-      }
-    ]);
-  };
-
-  var createManualDirective = function(type, templateUrl, transclude) {
-    transclude = angular.isDefined(transclude) ? transclude : false;
-    $compileProvider.directive('sf' + angular.uppercase(type[0]) + type.substr(1), function() {
-      return {
-        restrict: 'EAC',
-        scope: true,
-        replace: true,
-        transclude: transclude,
-        template: '<sf-decorator form="form"></sf-decorator>',
-        link: function(scope, element, attrs) {
-          var watchThis = {
-            'items': 'c',
-            'titleMap': 'c',
-            'schema': 'c'
-          };
-          var form = {type: type};
-          var once = true;
-          angular.forEach(attrs, function(value, name) {
-            if (name[0] !== '$' && name.indexOf('ng') !== 0 && name !== 'sfField') {
-
-              var updateForm = function(val) {
-                if (angular.isDefined(val) && val !== form[name]) {
-                  form[name] = val;
-
-                  //when we have type, and if specified key we apply it on scope.
-                  if (once && form.type && (form.key || angular.isUndefined(attrs.key))) {
-                    scope.form = form;
-                    once = false;
-                  }
-                }
-              };
-
-              if (name === 'model') {
-                //"model" is bound to scope under the name "model" since this is what the decorators
-                //know and love.
-                scope.$watch(value, function(val) {
-                  if (val && scope.model !== val) {
-                    scope.model = val;
-                  }
-                });
-              } else if (watchThis[name] === 'c') {
-                //watch collection
-                scope.$watchCollection(value, updateForm);
-              } else {
-                //$observe
-                attrs.$observe(name, updateForm);
-              }
-            }
-          });
-        }
-      };
-    });
-  };
-
-  /**
-   * DEPRECATED: use defineDecorator instead.
-   * Create a decorator directive and its sibling "manual" use decorators.
-   * The directive can be used to create form fields or other form entities.
-   * It can be used in conjunction with <schema-form> directive in which case the decorator is
-   * given it's configuration via a the "form" attribute.
-   *
-   * ex. Basic usage
-   *   <sf-decorator form="myform"></sf-decorator>
-   **
-   * @param {string} name directive name (CamelCased)
-   * @param {Object} templates, an object that maps "type" => "templateUrl"
-   */
-  this.createDecorator = function(name, templates) {
-    console.warn('schemaFormDecorators.createDecorator is DEPRECATED, use defineDecorator instead.');
-    decorators[name] = {'__name': name};
-
-    angular.forEach(templates, function(url, type) {
-      decorators[name][type] = {template: url, replace: false, builder: angular.noop};
-    });
-
-    if (!decorators[defaultDecorator]) {
-      defaultDecorator = name;
-    }
-    createDirective(name);
-  };
-
-
-  /**
-   * Create a decorator directive and its sibling "manual" use decorators.
-   * The directive can be used to create form fields or other form entities.
-   * It can be used in conjunction with <schema-form> directive in which case the decorator is
-   * given it's configuration via a the "form" attribute.
-   *
-   * ex. Basic usage
-   *   <sf-decorator form="myform"></sf-decorator>
-   **
-   * @param {string} name directive name (CamelCased)
-   * @param {Object} fields, an object that maps "type" => `{ template, builder, replace}`.
-                     attributes `builder` and `replace` are optional, and replace defaults to true.
-   */
-  this.defineDecorator = function(name, fields) {
-    decorators[name] = {'__name': name}; // TODO: this feels like a hack, come up with a better way.
-
-    angular.forEach(fields, function(field, type) {
-      field.builder = field.builder || angular.noop;
-      field.replace = angular.isDefined(field.replace) ? field.replace : true;
-      decorators[name][type] = field;
-    });
-
-    if (!decorators[defaultDecorator]) {
-      defaultDecorator = name;
-    }
-    createDirective(name);
-  };
-
-  /**
-   * Creates a directive of a decorator
-   * Usable when you want to use the decorators without using <schema-form> directive.
-   * Specifically when you need to reuse styling.
-   *
-   * ex. createDirective('text','...')
-   *  <sf-text title="foobar" model="person" key="name" schema="schema"></sf-text>
-   *
-   * @param {string}  type The type of the directive, resulting directive will have sf- prefixed
-   * @param {string}  templateUrl
-   * @param {boolean} transclude (optional) sets transclude option of directive, defaults to false.
-   */
-  this.createDirective = createManualDirective;
-
-  /**
-   * Same as createDirective, but takes an object where key is 'type' and value is 'templateUrl'
-   * Useful for batching.
-   * @param {Object} templates
-   */
-  this.createDirectives = function(templates) {
-    angular.forEach(templates, function(url, type) {
-      createManualDirective(type, url);
-    });
-  };
-
-  /**
-   * Getter for decorator settings
-   * @param {string} name (optional) defaults to defaultDecorator
-   * @return {Object} rules and templates { rules: [],templates: {}}
-   */
-  this.decorator = function(name) {
-    name = name || defaultDecorator;
-    return decorators[name];
-  };
-
-
-  /**
-   * Adds a mapping to an existing decorator.
-   * @param {String} name Decorator name
-   * @param {String} type Form type for the mapping
-   * @param {String} url  The template url
-   * @param {Function} builder (optional) builder function
-   * @param {boolean} replace (optional) defaults to false. Replace decorator directive with template.
-   */
-  this.addMapping = function(name, type, url, builder, replace) {
-    if (decorators[name]) {
-      decorators[name][type] = {
-        template: url,
-        builder: builder,
-        replace: !!replace
-      };
-    }
-  };
-
-  //Service is just a getter for directive templates and rules
-  this.$get = function() {
-    return {
-      decorator: function(name) {
-        return decorators[name] || decorators[defaultDecorator];
-      },
-      defaultDecorator: defaultDecorator
-    };
-  };
-
-  //Create a default directive
-  createDirective('sfDecorator');
-
-}]);
-
-angular.module('schemaForm').provider('sfErrorMessage', function() {
-
-  // The codes are tv4 error codes.
-  // Not all of these can actually happen in a field, but for
-  // we never know when one might pop up so it's best to cover them all.
-
-  // TODO: Humanize these.
-  var defaultMessages = {
-    'default': 'Field does not validate',
-    0: 'Invalid type, expected {{schema.type}}',
-    1: 'No enum match for: {{value}}',
-    10: 'Data does not match any schemas from "anyOf"',
-    11: 'Data does not match any schemas from "oneOf"',
-    12: 'Data is valid against more than one schema from "oneOf"',
-    13: 'Data matches schema from "not"',
-    // Numeric errors
-    100: 'Value is not a multiple of {{schema.divisibleBy}}',
-    101: '{{viewValue}} is less than the allowed minimum of {{schema.minimum}}',
-    102: '{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}',
-    103: '{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}',
-    104: '{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}',
-    105: 'Value is not a valid number',
-    // String errors
-    200: 'String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}',
-    201: 'String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}',
-    202: 'String does not match pattern: {{schema.pattern}}',
-    // Object errors
-    300: 'Too few properties defined, minimum {{schema.minProperties}}',
-    301: 'Too many properties defined, maximum {{schema.maxProperties}}',
-    302: 'Required',
-    303: 'Additional properties not allowed',
-    304: 'Dependency failed - key must exist',
-    // Array errors
-    400: 'Array is too short ({{value.length}}), minimum {{schema.maxItems}}',
-    401: 'Array is too long ({{value.length}}), maximum {{schema.minItems}}',
-    402: 'Array items are not unique',
-    403: 'Additional items not allowed',
-    // Format errors
-    500: 'Format validation failed',
-    501: 'Keyword failed: "{{title}}"',
-    // Schema structure
-    600: 'Circular $refs',
-    // Non-standard validation options
-    1000: 'Unknown property (not in schema)'
-  };
-
-  // In some cases we get hit with an angular validation error
-  defaultMessages.number    = defaultMessages[105];
-  defaultMessages.required  = defaultMessages[302];
-  defaultMessages.min       = defaultMessages[101];
-  defaultMessages.max       = defaultMessages[103];
-  defaultMessages.maxlength = defaultMessages[201];
-  defaultMessages.minlength = defaultMessages[200];
-  defaultMessages.pattern   = defaultMessages[202];
-
-  this.setDefaultMessages = function(messages) {
-    defaultMessages = messages;
-  };
-
-  this.getDefaultMessages = function() {
-    return defaultMessages;
-  };
-
-  this.setDefaultMessage = function(error, msg) {
-    defaultMessages[error] = msg;
-  };
-
-  this.$get = ['$interpolate', function($interpolate) {
-
-    var service = {};
-    service.defaultMessages = defaultMessages;
-
-    /**
-     * Interpolate and return proper error for an eror code.
-     * Validation message on form trumps global error messages.
-     * and if the message is a function instead of a string that function will be called instead.
-     * @param {string} error the error code, i.e. tv4-xxx for tv4 errors, otherwise it's whats on
-     *                       ngModel.$error for custom errors.
-     * @param {Any} value the actual model value.
-     * @param {Any} viewValue the viewValue
-     * @param {Object} form a form definition object for this field
-     * @param  {Object} global the global validation messages object (even though its called global
-     *                         its actually just shared in one instance of sf-schema)
-     * @return {string} The error message.
-     */
-    service.interpolate = function(error, value, viewValue, form, global) {
-      global = global || {};
-      var validationMessage = form.validationMessage || {};
-
-      // Drop tv4 prefix so only the code is left.
-      if (error.indexOf('tv4-') === 0) {
-        error = error.substring(4);
-      }
-
-      // First find apropriate message or function
-      var message = validationMessage['default'] || global['default'] || '';
-
-      [validationMessage, global, defaultMessages].some(function(val) {
-        if (angular.isString(val) || angular.isFunction(val)) {
-          message = val;
-          return true;
-        }
-        if (val && val[error]) {
-          message = val[error];
-          return true;
-        }
-      });
-
-      var context = {
-        error: error,
-        value: value,
-        viewValue: viewValue,
-        form: form,
-        schema: form.schema,
-        title: form.title || (form.schema && form.schema.title)
-      };
-      if (angular.isFunction(message)) {
-        return message(context);
-      } else {
-        return $interpolate(message)(context);
-      }
-    };
-
-    return service;
-  }];
-
-});
-
-/**
- * Schema form service.
- * This service is not that useful outside of schema form directive
- * but makes the code more testable.
- */
-angular.module('schemaForm').provider('schemaForm',
-['sfPathProvider', function(sfPathProvider) {
-  var stripNullType = function(type) {
-    if (Array.isArray(type) && type.length == 2) {
-      if (type[0] === 'null')
-        return type[1];
-      if (type[1] === 'null')
-        return type[0];
-    }
-    return type;
-  };
-
-  //Creates an default titleMap list from an enum, i.e. a list of strings.
-  var enumToTitleMap = function(enm) {
-    var titleMap = []; //canonical titleMap format is a list.
-    enm.forEach(function(name) {
-      titleMap.push({name: name, value: name});
-    });
-    return titleMap;
-  };
-
-  // Takes a titleMap in either object or list format and returns one in
-  // in the list format.
-  var canonicalTitleMap = function(titleMap, originalEnum) {
-    if (!angular.isArray(titleMap)) {
-      var canonical = [];
-      if (originalEnum) {
-        angular.forEach(originalEnum, function(value, index) {
-          canonical.push({name: titleMap[value], value: value});
-        });
-      } else {
-        angular.forEach(titleMap, function(name, value) {
-          canonical.push({name: name, value: value});
-        });
-      }
-      return canonical;
-    }
-    return titleMap;
-  };
-
-  var defaultFormDefinition = function(name, schema, options) {
-    var rules = defaults[stripNullType(schema.type)];
-    if (rules) {
-      var def;
-      for (var i = 0; i < rules.length; i++) {
-        def = rules[i](name, schema, options);
-
-        //first handler in list that actually returns something is our handler!
-        if (def) {
-
-          // Do we have form defaults in the schema under the x-schema-form-attribute?
-          if (def.schema['x-schema-form'] && angular.isObject(def.schema['x-schema-form'])) {
-            def = angular.extend(def, def.schema['x-schema-form']);
-          }
-
-          return def;
-        }
-      }
-    }
-  };
-
-  //Creates a form object with all common properties
-  var stdFormObj = function(name, schema, options) {
-    options = options || {};
-    var f = options.global && options.global.formDefaults ?
-            angular.copy(options.global.formDefaults) : {};
-    if (options.global && options.global.supressPropertyTitles === true) {
-      f.title = schema.title;
-    } else {
-      f.title = schema.title || name;
-    }
-
-    if (schema.description) { f.description = schema.description; }
-    if (options.required === true || schema.required === true) { f.required = true; }
-    if (schema.maxLength) { f.maxlength = schema.maxLength; }
-    if (schema.minLength) { f.minlength = schema.maxLength; }
-    if (schema.readOnly || schema.readonly) { f.readonly  = true; }
-    if (schema.minimum) { f.minimum = schema.minimum + (schema.exclusiveMinimum ? 1 : 0); }
-    if (schema.maximum) { f.maximum = schema.maximum - (schema.exclusiveMaximum ? 1 : 0); }
-
-    // Non standard attributes (DONT USE DEPRECATED)
-    // If you must set stuff like this in the schema use the x-schema-form attribute
-    if (schema.validationMessage) { f.validationMessage = schema.validationMessage; }
-    if (schema.enumNames) { f.titleMap = canonicalTitleMap(schema.enumNames, schema['enum']); }
-    f.schema = schema;
-
-    // Ng model options doesn't play nice with undefined, might be defined
-    // globally though
-    f.ngModelOptions = f.ngModelOptions || {};
-
-    return f;
-  };
-
-  var text = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'string' && !schema['enum']) {
-      var f = stdFormObj(name, schema, options);
-      f.key  = options.path;
-      f.type = 'text';
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-      return f;
-    }
-  };
-
-  //default in json form for number and integer is a text field
-  //input type="number" would be more suitable don't ya think?
-  var number = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'number') {
-      var f = stdFormObj(name, schema, options);
-      f.key  = options.path;
-      f.type = 'number';
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-      return f;
-    }
-  };
-
-  var integer = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'integer') {
-      var f = stdFormObj(name, schema, options);
-      f.key  = options.path;
-      f.type = 'number';
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-      return f;
-    }
-  };
-
-  var checkbox = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'boolean') {
-      var f = stdFormObj(name, schema, options);
-      f.key  = options.path;
-      f.type = 'checkbox';
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-      return f;
-    }
-  };
-
-  var select = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'string' && schema['enum']) {
-      var f = stdFormObj(name, schema, options);
-      f.key  = options.path;
-      f.type = 'select';
-      if (!f.titleMap) {
-        f.titleMap = enumToTitleMap(schema['enum']);
-      }
-      f.trackBy = 'value';
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-      return f;
-    }
-  };
-
-  var checkboxes = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'array' && schema.items && schema.items['enum']) {
-      var f = stdFormObj(name, schema, options);
-      f.key  = options.path;
-      f.type = 'checkboxes';
-      if (!f.titleMap) {
-        f.titleMap = enumToTitleMap(schema.items['enum']);
-      }
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-      return f;
-    }
-  };
-
-  var fieldset = function(name, schema, options) {
-    if (stripNullType(schema.type) === 'object') {
-      var f   = stdFormObj(name, schema, options);
-      f.type  = 'fieldset';
-      f.items = [];
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-
-      //recurse down into properties
-      angular.forEach(schema.properties, function(v, k) {
-        var path = options.path.slice();
-        path.push(k);
-        if (options.ignore[sfPathProvider.stringify(path)] !== true) {
-          var required = schema.required && schema.required.indexOf(k) !== -1;
-
-          var def = defaultFormDefinition(k, v, {
-            path: path,
-            required: required || false,
-            lookup: options.lookup,
-            ignore: options.ignore,
-            global: options.global
-          });
-          if (def) {
-            f.items.push(def);
-          }
-        }
-      });
-
-      return f;
-    }
-
-  };
-
-  var array = function(name, schema, options) {
-
-    if (stripNullType(schema.type) === 'array') {
-      var f   = stdFormObj(name, schema, options);
-      f.type  = 'array';
-      f.key   = options.path;
-      options.lookup[sfPathProvider.stringify(options.path)] = f;
-
-      var required = schema.required &&
-                     schema.required.indexOf(options.path[options.path.length - 1]) !== -1;
-
-      // The default is to always just create one child. This works since if the
-      // schemas items declaration is of type: "object" then we get a fieldset.
-      // We also follow json form notatation, adding empty brackets "[]" to
-      // signify arrays.
-
-      var arrPath = options.path.slice();
-      arrPath.push('');
-
-      f.items = [defaultFormDefinition(name, schema.items, {
-        path: arrPath,
-        required: required || false,
-        lookup: options.lookup,
-        ignore: options.ignore,
-        global: options.global
-      })];
-
-      return f;
-    }
-
-  };
-
-  //First sorted by schema type then a list.
-  //Order has importance. First handler returning an form snippet will be used.
-  var defaults = {
-    string:  [select, text],
-    object:  [fieldset],
-    number:  [number],
-    integer: [integer],
-    boolean: [checkbox],
-    array:   [checkboxes, array]
-  };
-
-  var postProcessFn = function(form) { return form; };
-
-  /**
-   * Provider API
-   */
-  this.defaults              = defaults;
-  this.stdFormObj            = stdFormObj;
-  this.defaultFormDefinition = defaultFormDefinition;
-
-  /**
-   * Register a post process function.
-   * This function is called with the fully merged
-   * form definition (i.e. after merging with schema)
-   * and whatever it returns is used as form.
-   */
-  this.postProcess = function(fn) {
-    postProcessFn = fn;
-  };
-
-  /**
-   * Append default form rule
-   * @param {string}   type json schema type
-   * @param {Function} rule a function(propertyName,propertySchema,options) that returns a form
-   *                        definition or undefined
-   */
-  this.appendRule = function(type, rule) {
-    if (!defaults[type]) {
-      defaults[type] = [];
-    }
-    defaults[type].push(rule);
-  };
-
-  /**
-   * Prepend default form rule
-   * @param {string}   type json schema type
-   * @param {Function} rule a function(propertyName,propertySchema,options) that returns a form
-   *                        definition or undefined
-   */
-  this.prependRule = function(type, rule) {
-    if (!defaults[type]) {
-      defaults[type] = [];
-    }
-    defaults[type].unshift(rule);
-  };
-
-  /**
-   * Utility function to create a standard form object.
-   * This does *not* set the type of the form but rather all shared attributes.
-   * You probably want to start your rule with creating the form with this method
-   * then setting type and any other values you need.
-   * @param {Object} schema
-   * @param {Object} options
-   * @return {Object} a form field defintion
-   */
-  this.createStandardForm = stdFormObj;
-  /* End Provider API */
-
-  this.$get = function() {
-
-    var service = {};
-
-    service.merge = function(schema, form, ignore, options, readonly) {
-      form  = form || ['*'];
-      options = options || {};
-
-      // Get readonly from root object
-      readonly = readonly || schema.readonly || schema.readOnly;
-
-      var stdForm = service.defaults(schema, ignore, options);
-
-      //simple case, we have a "*", just put the stdForm there
-      var idx = form.indexOf('*');
-      if (idx !== -1) {
-        form  = form.slice(0, idx)
-                    .concat(stdForm.form)
-                    .concat(form.slice(idx + 1));
-      }
-
-      //ok let's merge!
-      //We look at the supplied form and extend it with schema standards
-      var lookup = stdForm.lookup;
-
-      return postProcessFn(form.map(function(obj) {
-
-        //handle the shortcut with just a name
-        if (typeof obj === 'string') {
-          obj = {key: obj};
-        }
-
-        if (obj.key) {
-          if (typeof obj.key === 'string') {
-            obj.key = sfPathProvider.parse(obj.key);
-          }
-        }
-
-        //If it has a titleMap make sure it's a list
-        if (obj.titleMap) {
-          obj.titleMap = canonicalTitleMap(obj.titleMap);
-        }
-
-        if(obj.type === 'select') {
-          obj.trackBy = obj.trackBy || 'value';
-        }
-
-        //
-        if (obj.itemForm) {
-          obj.items = [];
-          var str = sfPathProvider.stringify(obj.key);
-          var stdForm = lookup[str];
-          angular.forEach(stdForm.items, function(item) {
-            var o = angular.copy(obj.itemForm);
-            o.key = item.key;
-            obj.items.push(o);
-          });
-        }
-
-        //extend with std form from schema.
-        if (obj.key) {
-          var strid = sfPathProvider.stringify(obj.key);
-          if (lookup[strid]) {
-            var schemaDefaults = lookup[strid];
-            angular.forEach(schemaDefaults, function(value, attr) {
-              if (obj[attr] === undefined) {
-                obj[attr] = schemaDefaults[attr];
-              }
-            });
-          }
-        }
-
-        // Are we inheriting readonly?
-        if (readonly === true) { // Inheriting false is not cool.
-          obj.readonly = true;
-        }
-
-        //if it's a type with items, merge 'em!
-        if (obj.items) {
-          obj.items = service.merge(schema, obj.items, ignore, options, obj.readonly);
-        }
-
-        //if its has tabs, merge them also!
-        if (obj.tabs) {
-          angular.forEach(obj.tabs, function(tab) {
-            tab.items = service.merge(schema, tab.items, ignore, options, obj.readonly);
-          });
-        }
-
-        // Special case: checkbox
-        // Since have to ternary state we need a default
-        if (obj.type === 'checkbox' && angular.isUndefined(obj.schema['default'])) {
-          obj.schema['default'] = false;
-        }
-
-        return obj;
-      }));
-    };
-
-    /**
-     * Create form defaults from schema
-     */
-    service.defaults = function(schema, ignore, globalOptions) {
-      var form   = [];
-      var lookup = {}; //Map path => form obj for fast lookup in merging
-      ignore = ignore || {};
-      globalOptions = globalOptions || {};
-
-      if (stripNullType(schema.type) === 'object') {
-        angular.forEach(schema.properties, function(v, k) {
-          if (ignore[k] !== true) {
-            var required = schema.required && schema.required.indexOf(k) !== -1;
-            var def = defaultFormDefinition(k, v, {
-              path: [k],         // Path to this property in bracket notation.
-              lookup: lookup,    // Extra map to register with. Optimization for merger.
-              ignore: ignore,    // The ignore list of paths (sans root level name)
-              required: required, // Is it required? (v4 json schema style)
-              global: globalOptions // Global options, including form defaults
-            });
-            if (def) {
-              form.push(def);
-            }
-          }
-        });
-
-      } else {
-        throw new Error('Not implemented. Only type "object" allowed at root level of schema.');
-      }
-      return {form: form, lookup: lookup};
-    };
-
-    //Utility functions
-    /**
-     * Traverse a schema, applying a function(schema,path) on every sub schema
-     * i.e. every property of an object.
-     */
-    service.traverseSchema = function(schema, fn, path, ignoreArrays) {
-      ignoreArrays = angular.isDefined(ignoreArrays) ? ignoreArrays : true;
-
-      path = path || [];
-
-      var traverse = function(schema, fn, path) {
-        fn(schema, path);
-        angular.forEach(schema.properties, function(prop, name) {
-          var currentPath = path.slice();
-          currentPath.push(name);
-          traverse(prop, fn, currentPath);
-        });
-
-        //Only support type "array" which have a schema as "items".
-        if (!ignoreArrays && schema.items) {
-          var arrPath = path.slice(); arrPath.push('');
-          traverse(schema.items, fn, arrPath);
-        }
-      };
-
-      traverse(schema, fn, path || []);
-    };
-
-    service.traverseForm = function(form, fn) {
-      fn(form);
-      angular.forEach(form.items, function(f) {
-        service.traverseForm(f, fn);
-      });
-
-      if (form.tabs) {
-        angular.forEach(form.tabs, function(tab) {
-          angular.forEach(tab.items, function(f) {
-            service.traverseForm(f, fn);
-          });
-        });
-      }
-    };
-
-    return service;
-  };
-
-}]);
-
-/*  Common code for validating a value against its form and schema definition */
-/* global tv4 */
-angular.module('schemaForm').factory('sfValidator', [function() {
-
-  var validator = {};
-
-  /**
-   * Validate a value against its form definition and schema.
-   * The value should either be of proper type or a string, some type
-   * coercion is applied.
-   *
-   * @param {Object} form A merged form definition, i.e. one with a schema.
-   * @param {Any} value the value to validate.
-   * @return a tv4js result object.
-   */
-  validator.validate = function(form, value) {
-    if (!form) {
-      return {valid: true};
-    }
-    var schema = form.schema;
-
-    if (!schema) {
-      return {valid: true};
-    }
-
-    // Input of type text and textareas will give us a viewValue of ''
-    // when empty, this is a valid value in a schema and does not count as something
-    // that breaks validation of 'required'. But for our own sanity an empty field should
-    // not validate if it's required.
-    if (value === '') {
-      value = undefined;
-    }
-
-    // Numbers fields will give a null value, which also means empty field
-    if (form.type === 'number' && value === null) {
-      value = undefined;
-    }
-
-    // Version 4 of JSON Schema has the required property not on the
-    // property itself but on the wrapping object. Since we like to test
-    // only this property we wrap it in a fake object.
-    var wrap = {type: 'object', 'properties': {}};
-    var propName = form.key[form.key.length - 1];
-    wrap.properties[propName] = schema;
-
-    if (form.required) {
-      wrap.required = [propName];
-    }
-    var valueWrap = {};
-    if (angular.isDefined(value)) {
-      valueWrap[propName] = value;
-    }
-    return tv4.validateResult(valueWrap, wrap);
-
-  };
-
-  return validator;
-}]);
-
-/**
- * Directive that handles the model arrays
- */
-angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sfValidator', 'sfPath',
-  function(sfSelect, schemaForm, sfValidator, sfPath) {
-
-    var setIndex = function(index) {
-      return function(form) {
-        if (form.key) {
-          form.key[form.key.indexOf('')] = index;
-        }
-      };
-    };
-
-    return {
-      restrict: 'A',
-      scope: true,
-      require: '?ngModel',
-      link: function(scope, element, attrs, ngModel) {
-        var formDefCache = {};
-
-        scope.validateArray = angular.noop;
-
-        if (ngModel) {
-          // We need the ngModelController on several places,
-          // most notably for errors.
-          // So we emit it up to the decorator directive so it can put it on scope.
-          scope.$emit('schemaFormPropagateNgModelController', ngModel);
-        }
-
-
-        // Watch for the form definition and then rewrite it.
-        // It's the (first) array part of the key, '[]' that needs a number
-        // corresponding to an index of the form.
-        var once = scope.$watch(attrs.sfArray, function(form) {
-
-          // An array model always needs a key so we know what part of the model
-          // to look at. This makes us a bit incompatible with JSON Form, on the
-          // other hand it enables two way binding.
-          var list = sfSelect(form.key, scope.model);
-
-          // We only modify the same array instance but someone might change the array from
-          // the outside so let's watch for that. We use an ordinary watch since the only case
-          // we're really interested in is if its a new instance.
-          scope.$watch('model' + sfPath.normalize(form.key), function(value) {
-            list = scope.modelArray = value;
-          });
-
-          // Since ng-model happily creates objects in a deep path when setting a
-          // a value but not arrays we need to create the array.
-          if (angular.isUndefined(list)) {
-            list = [];
-            sfSelect(form.key, scope.model, list);
-          }
-          scope.modelArray = list;
-
-          // Arrays with titleMaps, i.e. checkboxes doesn't have items.
-          if (form.items) {
-
-            // To be more compatible with JSON Form we support an array of items
-            // in the form definition of "array" (the schema just a value).
-            // for the subforms code to work this means we wrap everything in a
-            // section. Unless there is just one.
-            var subForm = form.items[0];
-            if (form.items.length > 1) {
-              subForm = {
-                type: 'section',
-                items: form.items.map(function(item) {
-                  item.ngModelOptions = form.ngModelOptions;
-                  if (angular.isUndefined(item.readonly)) {
-                    item.readonly = form.readonly;
-                  }
-                  return item;
-                })
-              };
-            }
-
-          }
-
-          // We ceate copies of the form on demand, caching them for
-          // later requests
-          scope.copyWithIndex = function(index) {
-            if (!formDefCache[index]) {
-              if (subForm) {
-                var copy = angular.copy(subForm);
-                copy.arrayIndex = index;
-                schemaForm.traverseForm(copy, setIndex(index));
-                formDefCache[index] = copy;
-              }
-            }
-            return formDefCache[index];
-          };
-
-          scope.appendToArray = function() {
-            var len = list.length;
-            var copy = scope.copyWithIndex(len);
-            schemaForm.traverseForm(copy, function(part) {
-
-              if (part.key) {
-                var def;
-                if (angular.isDefined(part['default'])) {
-                  def = part['default'];
-                }
-                if (angular.isDefined(part.schema) &&
-                    angular.isDefined(part.schema['default'])) {
-                  def = part.schema['default'];
-                }
-
-                if (angular.isDefined(def)) {
-                  sfSelect(part.key, scope.model, def);
-                }
-              }
-            });
-
-            // If there are no defaults nothing is added so we need to initialize
-            // the array. undefined for basic values, {} or [] for the others.
-            if (len === list.length) {
-              var type = sfSelect('schema.items.type', form);
-              var dflt;
-              if (type === 'object') {
-                dflt = {};
-              } else if (type === 'array') {
-                dflt = [];
-              }
-              list.push(dflt);
-            }
-
-            // Trigger validation.
-            scope.validateArray();
-            return list;
-          };
-
-          scope.deleteFromArray = function(index) {
-            list.splice(index, 1);
-
-            // Trigger validation.
-            scope.validateArray();
-
-            // Angular 1.2 lacks setDirty
-            if (ngModel && ngModel.$setDirty) {
-              ngModel.$setDirty();
-            }
-            return list;
-          };
-
-          // Always start with one empty form unless configured otherwise.
-          // Special case: don't do it if form has a titleMap
-          if (!form.titleMap && form.startEmpty !== true && list.length === 0) {
-            scope.appendToArray();
-          }
-
-          // Title Map handling
-          // If form has a titleMap configured we'd like to enable looping over
-          // titleMap instead of modelArray, this is used for intance in
-          // checkboxes. So instead of variable number of things we like to create
-          // a array value from a subset of values in the titleMap.
-          // The problem here is that ng-model on a checkbox doesn't really map to
-          // a list of values. This is here to fix that.
-          if (form.titleMap && form.titleMap.length > 0) {
-            scope.titleMapValues = [];
-
-            // We watch the model for changes and the titleMapValues to reflect
-            // the modelArray
-            var updateTitleMapValues = function(arr) {
-              scope.titleMapValues = [];
-              arr = arr || [];
-
-              form.titleMap.forEach(function(item) {
-                scope.titleMapValues.push(arr.indexOf(item.value) !== -1);
-              });
-            };
-            //Catch default values
-            updateTitleMapValues(scope.modelArray);
-            scope.$watchCollection('modelArray', updateTitleMapValues);
-
-            //To get two way binding we also watch our titleMapValues
-            scope.$watchCollection('titleMapValues', function(vals, old) {
-              if (vals && vals !== old) {
-                var arr = scope.modelArray;
-
-                // Apparently the fastest way to clear an array, readable too.
-                // http://jsperf.com/array-destroy/32
-                while (arr.length > 0) {
-                  arr.pop();
-                }
-                form.titleMap.forEach(function(item, index) {
-                  if (vals[index]) {
-                    arr.push(item.value);
-                  }
-                });
-
-                // Time to validate the rebuilt array.
-                scope.validateArray();
-              }
-            });
-          }
-
-          // If there is a ngModel present we need to validate when asked.
-          if (ngModel) {
-            var error;
-
-            scope.validateArray = function() {
-              // The actual content of the array is validated by each field
-              // so we settle for checking validations specific to arrays
-
-              // Since we prefill with empty arrays we can get the funny situation
-              // where the array is required but empty in the gui but still validates.
-              // Thats why we check the length.
-              var result = sfValidator.validate(
-                form,
-                scope.modelArray.length > 0 ? scope.modelArray : undefined
-              );
-
-              // TODO: DRY this up, it has a lot of similarities with schema-validate
-              // Since we might have different tv4 errors we must clear all
-              // errors that start with tv4-
-              Object.keys(ngModel.$error)
-                    .filter(function(k) { return k.indexOf('tv4-') === 0; })
-                    .forEach(function(k) { ngModel.$setValidity(k, true); });
-
-              if (result.valid === false &&
-                  result.error &&
-                  (result.error.dataPath === '' ||
-                  result.error.dataPath === '/' + form.key[form.key.length - 1])) {
-
-                // Set viewValue to trigger $dirty on field. If someone knows a
-                // a better way to do it please tell.
-                ngModel.$setViewValue(scope.modelArray);
-                error = result.error;
-                ngModel.$setValidity('tv4-' + result.error.code, false);
-              }
-            };
-
-            scope.$on('schemaFormValidate', scope.validateArray);
-
-            scope.hasSuccess = function() {
-              return ngModel.$valid && !ngModel.$pristine;
-            };
-
-            scope.hasError = function() {
-              return ngModel.$invalid;
-            };
-
-            scope.schemaError = function() {
-              return error;
-            };
-
-          }
-
-          once();
-        });
-      }
-    };
-  }
-]);
-
-/**
- * A version of ng-changed that only listens if
- * there is actually a onChange defined on the form
- *
- * Takes the form definition as argument.
- * If the form definition has a "onChange" defined as either a function or
- */
-angular.module('schemaForm').directive('sfChanged', function() {
-  return {
-    require: 'ngModel',
-    restrict: 'AC',
-    scope: false,
-    link: function(scope, element, attrs, ctrl) {
-      var form = scope.$eval(attrs.sfChanged);
-      //"form" is really guaranteed to be here since the decorator directive
-      //waits for it. But best be sure.
-      if (form && form.onChange) {
-        ctrl.$viewChangeListeners.push(function() {
-          if (angular.isFunction(form.onChange)) {
-            form.onChange(ctrl.$modelValue, form);
-          } else {
-            scope.evalExpr(form.onChange, {'modelValue': ctrl.$modelValue, form: form});
-          }
-        });
-      }
-    }
-  };
-});
-
-angular.module('schemaForm').directive('sfField',
-    ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage',
-     'sfPath','sfSelect',
-    function($parse,  $compile,  $http,  $templateCache, $interpolate, $q, sfErrorMessage,
-             sfPath, sfSelect) {
-
-      return {
-        restrict: 'AE',
-        replace: false,
-        transclude: false,
-        scope: true,
-        require: '?^sfSchema',
-        link: {
-          pre: function(scope) {
-            //The ngModelController is used in some templates and
-            //is needed for error messages,
-            scope.$on('schemaFormPropagateNgModelController', function(event, ngModel) {
-              event.stopPropagation();
-              event.preventDefault();
-              scope.ngModel = ngModel;
-            });
-          },
-          post: function(scope, element, attrs, sfSchema) {
-
-            //Keep error prone logic from the template
-            scope.showTitle = function() {
-              return scope.form && scope.form.notitle !== true && scope.form.title;
-            };
-
-            scope.listToCheckboxValues = function(list) {
-              var values = {};
-              angular.forEach(list, function(v) {
-                values[v] = true;
-              });
-              return values;
-            };
-
-            scope.checkboxValuesToList = function(values) {
-              var lst = [];
-              angular.forEach(values, function(v, k) {
-                if (v) {
-                  lst.push(k);
-                }
-              });
-              return lst;
-            };
-
-            scope.buttonClick = function($event, form) {
-              if (angular.isFunction(form.onClick)) {
-                form.onClick($event, form);
-              } else if (angular.isString(form.onClick)) {
-                if (sfSchema) {
-                  //evaluating in scope outside of sfSchemas isolated scope
-                  sfSchema.evalInParentScope(form.onClick, {'$event': $event, form: form});
-                } else {
-                  scope.$eval(form.onClick, {'$event': $event, form: form});
-                }
-              }
-            };
-
-            /**
-             * Evaluate an expression, i.e. scope.$eval
-             * but do it in sfSchemas parent scope sf-schema directive is used
-             * @param {string} expression
-             * @param {Object} locals (optional)
-             * @return {Any} the result of the expression
-             */
-            scope.evalExpr = function(expression, locals) {
-              if (sfSchema) {
-                //evaluating in scope outside of sfSchemas isolated scope
-                return sfSchema.evalInParentScope(expression, locals);
-              }
-
-              return scope.$eval(expression, locals);
-            };
-
-            /**
-             * Evaluate an expression, i.e. scope.$eval
-             * in this decorators scope
-             * @param {string} expression
-             * @param {Object} locals (optional)
-             * @return {Any} the result of the expression
-             */
-            scope.evalInScope = function(expression, locals) {
-              if (expression) {
-                return scope.$eval(expression, locals);
-              }
-            };
-
-            /**
-             * Interpolate the expression.
-             * Similar to `evalExpr()` and `evalInScope()`
-             * but will not fail if the expression is
-             * text that contains spaces.
-             *
-             * Use the Angular `{{ interpolation }}`
-             * braces to access properties on `locals`.
-             *
-             * @param  {string} content The string to interpolate.
-             * @param  {Object} locals (optional) Properties that may be accessed in the
-             *                         `expression` string.
-             * @return {Any} The result of the expression or `undefined`.
-             */
-            scope.interp = function(expression, locals) {
-              return (expression && $interpolate(expression)(locals));
-            };
-
-            //This works since we ot the ngModel from the array or the schema-validate directive.
-            scope.hasSuccess = function() {
-              if (!scope.ngModel) {
-                return false;
-              }
-              return scope.ngModel.$valid &&
-                  (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue));
-            };
-
-            scope.hasError = function() {
-              if (!scope.ngModel) {
-                return false;
-              }
-              return scope.ngModel.$invalid && !scope.ngModel.$pristine;
-            };
-
-            /**
-             * DEPRECATED: use sf-messages instead.
-             * Error message handler
-             * An error can either be a schema validation message or a angular js validtion
-             * error (i.e. required)
-             */
-            scope.errorMessage = function(schemaError) {
-              return sfErrorMessage.interpolate(
-                (schemaError && schemaError.code + '') || 'default',
-                (scope.ngModel && scope.ngModel.$modelValue) || '',
-                (scope.ngModel && scope.ngModel.$viewValue) || '',
-                scope.form,
-                scope.options && scope.options.validationMessage
-              );
-            };
-
-            // Rebind our part of the form to the scope.
-            var once = scope.$watch(attrs.sfField, function(form) {
-              if (form) {
-                // Workaround for 'updateOn' error from ngModelOptions
-                // see https://github.com/Textalk/angular-schema-form/issues/255
-                // and https://github.com/Textalk/angular-schema-form/issues/206
-                form.ngModelOptions = form.ngModelOptions || {};
-                scope.form  = form;
-
-
-                // Where there is a key there is probably a ngModel
-                if (form.key) {
-                  // It looks better with dot notation.
-                  scope.$on(
-                    'schemaForm.error.' + form.key.join('.'),
-                    function(event, error, validationMessage, validity) {
-                      if (validationMessage === true || validationMessage === false) {
-                        validity = validationMessage;
-                        validationMessage = undefined;
-                      }
-
-                      if (scope.ngModel && error) {
-                        if (scope.ngModel.$setDirty) {
-                          scope.ngModel.$setDirty();
-                        } else {
-                          // FIXME: Check that this actually works on 1.2
-                          scope.ngModel.$dirty = true;
-                          scope.ngModel.$pristine = false;
-                        }
-
-                        // Set the new validation message if one is supplied
-                        // Does not work when validationMessage is just a string.
-                        if (validationMessage) {
-                          if (!form.validationMessage) {
-                            form.validationMessage = {};
-                          }
-                          form.validationMessage[error] = validationMessage;
-                        }
-
-                        scope.ngModel.$setValidity(error, validity === true);
-
-                        if (validity === true) {
-                          // Setting or removing a validity can change the field to believe its valid
-                          // but its not. So lets trigger its validation as well.
-                          scope.$broadcast('schemaFormValidate');
-                        }
-                      }
-                  });
-
-                  // Clean up the model when the corresponding form field is $destroy-ed.
-                  // Default behavior can be supplied as a globalOption, and behavior can be overridden in the form definition.
-                  scope.$on('$destroy', function() {
-                    // If the entire schema form is destroyed we don't touch the model
-                    if (!scope.externalDestructionInProgress) {
-                      var destroyStrategy = form.destroyStrategy ||
-                                            (scope.options && scope.options.destroyStrategy) || 'remove';
-                      // No key no model, and we might have strategy 'retain'
-                      if (form.key && destroyStrategy !== 'retain') {
-
-                        // Get the object that has the property we wan't to clear.
-                        var obj = scope.model;
-                        if (form.key.length > 1) {
-                          obj = sfSelect(form.key.slice(0, form.key.length - 1), obj);
-                        }
-
-                        // We can get undefined here if the form hasn't been filled out entirely
-                        if (obj === undefined) {
-                          return;
-                        }
-
-                        // Type can also be a list in JSON Schema
-                        var type = (form.schema && form.schema.type) || '';
-
-                        // Empty means '',{} and [] for appropriate types and undefined for the rest
-                        //console.log('destroy', destroyStrategy, form.key, type, obj);
-                        if (destroyStrategy === 'empty' && type.indexOf('string') !== -1) {
-                          obj[form.key.slice(-1)] = '';
-                        } else if (destroyStrategy === 'empty' && type.indexOf('object') !== -1) {
-                          obj[form.key.slice(-1)] = {};
-                        } else if (destroyStrategy === 'empty' && type.indexOf('array') !== -1) {
-                          obj[form.key.slice(-1)] = [];
-                        } else if (destroyStrategy === 'null') {
-                          obj[form.key.slice(-1)] = null;
-                        } else {
-                          delete obj[form.key.slice(-1)];
-                        }
-                      }
-                    }
-                  });
-                }
-
-                once();
-              }
-            });
-          }
-        }
-      };
-    }
-  ]);
-
-angular.module('schemaForm').directive('sfMessage',
-['$injector', 'sfErrorMessage', function($injector, sfErrorMessage) {
-  return {
-    scope: false,
-    restrict: 'EA',
-    link: function(scope, element, attrs) {
-
-      //Inject sanitizer if it exists
-      var $sanitize = $injector.has('$sanitize') ?
-                      $injector.get('$sanitize') : function(html) { return html; };
-
-      var message = '';
-
-      if (attrs.sfMessage) {
-        scope.$watch(attrs.sfMessage, function(msg) {
-          if (msg) {
-            message = $sanitize(msg);
-            if (scope.ngModel) {
-              update(scope.ngModel.$valid);
-            } else {
-              update();
-            }
-          }
-        });
-      }
-
-      var update = function(valid) {
-        if (valid && !scope.hasError()) {
-          element.html(message);
-        } else {
-
-
-          var errors = [];
-          angular.forEach(((scope.ngModel && scope.ngModel.$error) || {}), function(status, code) {
-            if (status) {
-              // if true then there is an error
-              // Angular 1.3 removes properties, so we will always just have errors.
-              // Angular 1.2 sets them to false.
-              errors.push(code);
-            }
-          });
-
-          // In Angular 1.3 we use one $validator to stop the model value from getting updated.
-          // this means that we always end up with a 'schemaForm' error.
-          errors = errors.filter(function(e) { return e !== 'schemaForm'; });
-
-          // We only show one error.
-          // TODO: Make that optional
-          var error = errors[0];
-          if (error) {
-            element.html(sfErrorMessage.interpolate(
-              error,
-              scope.ngModel.$modelValue,
-              scope.ngModel.$viewValue,
-              scope.form,
-              scope.options && scope.options.validationMessage
-            ));
-          } else {
-            element.html(message);
-          }
-        }
-      };
-      update();
-
-      scope.$watchCollection('ngModel.$error', function() {
-        if (scope.ngModel) {
-          update(scope.ngModel.$valid);
-        }
-      });
-
-    }
-  };
-}]);
-
-/*
-FIXME: real documentation
-<form sf-form="form"  sf-schema="schema" sf-decorator="foobar"></form>
-*/
-
-angular.module('schemaForm')
-       .directive('sfSchema',
-['$compile', 'schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath', 'sfBuilder',
-  function($compile,  schemaForm,  schemaFormDecorators, sfSelect, sfPath, sfBuilder) {
-
-    return {
-      scope: {
-        schema: '=sfSchema',
-        initialForm: '=sfForm',
-        model: '=sfModel',
-        options: '=sfOptions'
-      },
-      controller: ['$scope', function($scope) {
-        this.evalInParentScope = function(expr, locals) {
-          return $scope.$parent.$eval(expr, locals);
-        };
-      }],
-      replace: false,
-      restrict: 'A',
-      transclude: true,
-      require: '?form',
-      link: function(scope, element, attrs, formCtrl, transclude) {
-
-        //expose form controller on scope so that we don't force authors to use name on form
-        scope.formCtrl = formCtrl;
-
-        //We'd like to handle existing markup,
-        //besides using it in our template we also
-        //check for ng-model and add that to an ignore list
-        //i.e. even if form has a definition for it or form is ["*"]
-        //we don't generate it.
-        var ignore = {};
-        transclude(scope, function(clone) {
-          clone.addClass('schema-form-ignore');
-          element.prepend(clone);
-
-          if (element[0].querySelectorAll) {
-            var models = element[0].querySelectorAll('[ng-model]');
-            if (models) {
-              for (var i = 0; i < models.length; i++) {
-                var key = models[i].getAttribute('ng-model');
-                //skip first part before .
-                ignore[key.substring(key.indexOf('.') + 1)] = true;
-              }
-            }
-          }
-        });
-
-        var lastDigest = {};
-        var childScope;
-
-        // Common renderer function, can either be triggered by a watch or by an event.
-        var render = function(schema, form) {
-          var merged = schemaForm.merge(schema, form, ignore, scope.options);
-
-          // Create a new form and destroy the old one.
-          // Not doing keeps old form elements hanging around after
-          // they have been removed from the DOM
-          // https://github.com/Textalk/angular-schema-form/issues/200
-          if (childScope) {
-            // Destroy strategy should not be acted upon
-            scope.externalDestructionInProgress = true;
-            childScope.$destroy();
-            scope.externalDestructionInProgress = false;
-          }
-          childScope = scope.$new();
-
-          //make the form available to decorators
-          childScope.schemaForm  = {form:  merged, schema: schema};
-
-          //clean all but pre existing html.
-          element.children(':not(.schema-form-ignore)').remove();
-
-          // Find all slots.
-          var slots = {};
-          var slotsFound = element[0].querySelectorAll('*[sf-insert-field]');
-
-          for (var i = 0; i < slotsFound.length; i++) {
-            slots[slotsFound[i].getAttribute('sf-insert-field')] = slotsFound[i];
-          }
-
-          // if sfUseDecorator is undefined the default decorator is used.
-          var decorator = schemaFormDecorators.decorator(attrs.sfUseDecorator);
-
-          // Use the builder to build it and append the result
-          element[0].appendChild( sfBuilder.build(merged, decorator, slots) );
-
-          //compile only children
-          $compile(element.children())(childScope);
-
-          //ok, now that that is done let's set any defaults
-          if (!scope.options || scope.options.setSchemaDefaults !== false) {
-            schemaForm.traverseSchema(schema, function(prop, path) {
-              if (angular.isDefined(prop['default'])) {
-                var val = sfSelect(path, scope.model);
-                if (angular.isUndefined(val)) {
-                  sfSelect(path, scope.model, prop['default']);
-                }
-              }
-            });
-          }
-
-          scope.$emit('sf-render-finished', element);
-        };
-
-        //Since we are dependant on up to three
-        //attributes we'll do a common watch
-        scope.$watch(function() {
-
-          var schema = scope.schema;
-          var form   = scope.initialForm || ['*'];
-
-          //The check for schema.type is to ensure that schema is not {}
-          if (form && schema && schema.type &&
-              (lastDigest.form !== form || lastDigest.schema !== schema) &&
-              Object.keys(schema.properties).length > 0) {
-            lastDigest.schema = schema;
-            lastDigest.form = form;
-
-            render(schema, form);
-          }
-        });
-
-        // We also listen to the event schemaFormRedraw so you can manually trigger a change if
-        // part of the form or schema is chnaged without it being a new instance.
-        scope.$on('schemaFormRedraw', function() {
-          var schema = scope.schema;
-          var form   = scope.initialForm || ['*'];
-          if (schema) {
-            render(schema, form);
-          }
-        });
-
-        scope.$on('$destroy', function() {
-          // Each field listens to the $destroy event so that it can remove any value
-          // from the model if that field is removed from the form. This is the default
-          // destroy strategy. But if the entire form (or at least the part we're on)
-          // gets removed, like when routing away to another page, then we definetly want to
-          // keep the model intact. So therefore we set a flag to tell the others it's time to just
-          // let it be.
-          scope.externalDestructionInProgress = true;
-        });
-      }
-    };
-  }
-]);
-
-angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse', 'sfSelect',
-  function(sfValidator, $parse, sfSelect) {
-
-    return {
-      restrict: 'A',
-      scope: false,
-      // We want the link function to be *after* the input directives link function so we get access
-      // the parsed value, ex. a number instead of a string
-      priority: 500,
-      require: 'ngModel',
-      link: function(scope, element, attrs, ngModel) {
-
-        // We need the ngModelController on several places,
-        // most notably for errors.
-        // So we emit it up to the decorator directive so it can put it on scope.
-        scope.$emit('schemaFormPropagateNgModelController', ngModel);
-
-        var error = null;
-
-        // When using the new builder we might not have form just yet
-        var once = scope.$watch(attrs.schemaValidate, function(form) {
-          if (!form) {
-            return;
-          }
-
-          if (form.copyValueTo) {
-            ngModel.$viewChangeListeners.push(function() {
-              var paths = form.copyValueTo;
-              angular.forEach(paths, function(path) {
-                sfSelect(path, scope.model, ngModel.$modelValue);
-              });
-            });
-          }
-
-          // Validate against the schema.
-
-          var validate = function(viewValue) {
-            //Still might be undefined
-            if (!form) {
-              return viewValue;
-            }
-
-            // Omit TV4 validation
-            if (scope.options && scope.options.tv4Validation === false) {
-              return viewValue;
-            }
-
-            var result =  sfValidator.validate(form, viewValue);
-
-
-            // Since we might have different tv4 errors we must clear all
-            // errors that start with tv4-
-            Object.keys(ngModel.$error)
-                .filter(function(k) { return k.indexOf('tv4-') === 0; })
-                .forEach(function(k) { ngModel.$setValidity(k, true); });
-
-            if (!result.valid) {
-              // it is invalid, return undefined (no model update)
-              ngModel.$setValidity('tv4-' + result.error.code, false);
-              error = result.error;
-
-              // In Angular 1.3+ return the viewValue, otherwise we inadvertenly
-              // will trigger a 'parse' error.
-              // we will stop the model value from updating with our own $validator
-              // later.
-              if (ngModel.$validators) {
-                return viewValue;
-              }
-              // Angular 1.2 on the other hand lacks $validators and don't add a 'parse' error.
-              return undefined;
-            }
-            return viewValue;
-          };
-
-          // Custom validators, parsers, formatters etc
-          if (typeof form.ngModel === 'function') {
-            form.ngModel(ngModel);
-          }
-
-          ['$parsers', '$viewChangeListeners', '$formatters'].forEach(function(attr) {
-            if (form[attr] && ngModel[attr]) {
-              form[attr].forEach(function(fn) {
-                ngModel[attr].push(fn);
-              });
-            }
-          });
-
-          ['$validators', '$asyncValidators'].forEach(function(attr) {
-            // Check if our version of angular has validators, i.e. 1.3+
-            if (form[attr] && ngModel[attr]) {
-              angular.forEach(form[attr], function(fn, name) {
-                ngModel[attr][name] = fn;
-              });
-            }
-          });
-
-          // Get in last of the parses so the parsed value has the correct type.
-          // We don't use $validators since we like to set different errors depending tv4 error codes
-          ngModel.$parsers.push(validate);
-
-          // But we do use one custom validator in the case of Angular 1.3 to stop the model from
-          // updating if we've found an error.
-          if (ngModel.$validators) {
-            ngModel.$validators.schemaForm = function() {
-              // Any error and we're out of here!
-              return !Object.keys(ngModel.$error).some(function(e) { return e !== 'schemaForm'});
-            }
-          }
-
-          // Listen to an event so we can validate the input on request
-          scope.$on('schemaFormValidate', function() {
-
-            // We set the viewValue to trigger parsers,
-            // since modelValue might be empty and validating just that
-            // might change an existing error to a "required" error message.
-            if (ngModel.$setDirty) {
-
-              // Angular 1.3+
-              ngModel.$setDirty();
-              ngModel.$setViewValue(ngModel.$viewValue);
-              ngModel.$commitViewValue();
-
-              // In Angular 1.3 setting undefined as a viewValue does not trigger parsers
-              // so we need to do a special required check. Fortunately we have $isEmpty
-              if (form.required && ngModel.$isEmpty(ngModel.$modelValue)) {
-                ngModel.$setValidity('tv4-302', false);
-              }
-
-            } else {
-              // Angular 1.2
-              // In angular 1.2 setting a viewValue of undefined will trigger the parser.
-              // hence required works.
-              ngModel.$setViewValue(ngModel.$viewValue);
-            }
-
-          });
-
-          scope.schemaError = function() {
-            return error;
-          };
-
-          // Just watch once.
-          once();
-        });
-      }
-    };
-  }]);
-
-return schemaForm;
-}));
diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js
deleted file mode 100644
index 7f85995c0..000000000
--- a/dist/schema-form.min.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(e,t){"function"==typeof define&&define.amd?define(["angular","ObjectPath","tv4"],t):"object"==typeof exports?module.exports=t(require("angular"),require("ObjectPath"),require("tv4")):e.schemaForm=t(e.angular,e.ObjectPath,e.tv4)}(this,function(e,t,r){var n=[];try{e.module("ngSanitize"),n.push("ngSanitize")}catch(i){}try{e.module("ui.sortable"),n.push("ui.sortable")}catch(i){}try{e.module("angularSpectrumColorpicker"),n.push("angularSpectrumColorpicker")}catch(i){}var o=e.module("schemaForm",n);return e.module("schemaForm").provider("sfPath",[function(){var r={parse:t.parse};1===e.version.major&&e.version.minor<3?r.stringify=function(e){return Array.isArray(e)?e.join("."):e.toString()}:r.stringify=t.stringify,r.normalize=function(e,t){return r.stringify(Array.isArray(e)?e:r.parse(e),t)},this.parse=r.parse,this.stringify=r.stringify,this.normalize=r.normalize,this.$get=function(){return r}}]),e.module("schemaForm").factory("sfSelect",["sfPath",function(e){var t=/^\d+$/;return function(r,n,i){n||(n=this);var o="string"==typeof r?e.parse(r):r;if("undefined"!=typeof i&&1===o.length)return n[o[0]]=i,n;"undefined"!=typeof i&&"undefined"==typeof n[o[0]]&&(n[o[0]]=o.length>2&&t.test(o[1])?[]:{});for(var a=n[o[0]],l=1;l<o.length;l++){if(""===o[l])return void 0;if("undefined"!=typeof i){if(l===o.length-1)return a[o[l]]=i,i;var s=a[o[l]];("undefined"==typeof s||null===s)&&(s=t.test(o[l+1])?[]:{},a[o[l]]=s),a=s}else a&&(a=a[o[l]])}return a}}]),e.module("schemaForm").factory("sfBuilder",["$templateCache","schemaFormDecorators","sfPath",function(e,t,r){var n=/[A-Z]/g,i=function(e,t){return t=t||"_",e.replace(n,function(e,r){return(r?t:"")+e.toLowerCase()})},o=function(e,t){if(e.key){var n=t[r.stringify(e.key)];if(n){for(;n.firstChild;)n.removeChild(n.firstChild);return n}}},a=function(e,t,n,l,s){s=s||"schemaForm.form";var c=document.createDocumentFragment();return e.reduce(function(e,c,u){if(c.type){var f=t[c.type]||t["default"];if(f.replace){var m,d=document.createElement("div"),p=n(f.template)||n([t["default"].template]);if(c.key){var h=c.key?r.stringify(c.key).replace(/"/g,"&quot;"):"";p=p.replace(/\$\$value\$\$/g,"model"+("["!==h[0]?".":"")+h)}for(d.innerHTML=p,m=document.createDocumentFragment();d.childNodes.length>0;)m.appendChild(d.childNodes[0]);m.firstChild.setAttribute("sf-field",s+"["+u+"]"),f.builder({fieldFrag:m,form:c,path:s+"["+u+"]",build:function(e,r){return a(e,t,n,l,r)}}),(o(c,l)||e).appendChild(m)}else{var v=document.createElement(i(t.__name,"-"));v.setAttribute("form",s+"["+u+"]"),(o(c,l)||e).appendChild(v)}return e}},c),c},l={build:function(t,r,n){return a(t,r,function(t){return e.get(t)||""},n)},internalBuild:a};return l}]),e.module("schemaForm").provider("schemaFormDecorators",["$compileProvider","sfPathProvider",function(t,r){var n="",i={},o=function(e,t){"sfDecorator"===e&&(e=n);var r=i[e];return r[t.type]?r[t.type].template:r["default"].template},a=function(n){t.directive(n,["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,i,a,l,s,c,u,f,m){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,d,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return u.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var u;if("template"===s.type&&s.template)u=c.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);u=a.get(p,{cache:l}).then(function(e){return e.data})}u.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"&quot;"):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(d.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(d.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(d.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=m(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'<sf-decorator form="form"></sf-decorator>',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){console.warn("schemaFormDecorators.createDecorator is DEPRECATED, use defineDecorator instead."),i[t]={__name:t},e.forEach(r,function(r,n){i[t][n]={template:r,replace:!1,builder:e.noop}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||e.noop,r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.$get=function(){return{decorator:function(e){return i[e]||i[n]},defaultDecorator:n}},a("sfDecorator")}]),e.module("schemaForm").provider("sfErrorMessage",function(){var t={"default":"Field does not validate",0:"Invalid type, expected {{schema.type}}",1:"No enum match for: {{value}}",10:'Data does not match any schemas from "anyOf"',11:'Data does not match any schemas from "oneOf"',12:'Data is valid against more than one schema from "oneOf"',13:'Data matches schema from "not"',100:"Value is not a multiple of {{schema.divisibleBy}}",101:"{{viewValue}} is less than the allowed minimum of {{schema.minimum}}",102:"{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}",103:"{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}",104:"{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}",105:"Value is not a valid number",200:"String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}",201:"String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}",202:"String does not match pattern: {{schema.pattern}}",300:"Too few properties defined, minimum {{schema.minProperties}}",301:"Too many properties defined, maximum {{schema.maxProperties}}",302:"Required",303:"Additional properties not allowed",304:"Dependency failed - key must exist",400:"Array is too short ({{value.length}}), minimum {{schema.maxItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.minItems}}",402:"Array items are not unique",403:"Additional items not allowed",500:"Format validation failed",501:'Keyword failed: "{{title}}"',600:"Circular $refs",1e3:"Unknown property (not in schema)"};t.number=t[105],t.required=t[302],t.min=t[101],t.max=t[103],t.maxlength=t[201],t.minlength=t[200],t.pattern=t[202],this.setDefaultMessages=function(e){t=e},this.getDefaultMessages=function(){return t},this.setDefaultMessage=function(e,r){t[e]=r},this.$get=["$interpolate",function(r){var n={};return n.defaultMessages=t,n.interpolate=function(n,i,o,a,l){l=l||{};var s=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var c=s["default"]||l["default"]||"";[s,l,t].some(function(t){return e.isString(t)||e.isFunction(t)?(c=t,!0):t&&t[n]?(c=t[n],!0):void 0});var u={error:n,value:i,viewValue:o,form:a,schema:a.schema,title:a.title||a.schema&&a.schema.title};return e.isFunction(c)?c(u):r(c)(u)},n}]}),e.module("schemaForm").provider("schemaForm",["sfPathProvider",function(t){var r=function(e){if(Array.isArray(e)&&2==e.length){if("null"===e[0])return e[1];if("null"===e[1])return e[0]}return e},n=function(e){var t=[];return e.forEach(function(e){t.push({name:e,value:e})}),t},i=function(t,r){if(!e.isArray(t)){var n=[];return r?e.forEach(r,function(e,r){n.push({name:t[e],value:e})}):e.forEach(t,function(e,t){n.push({name:e,value:t})}),n}return t},o=function(t,n,i){var o=h[r(n.type)];if(o)for(var a,l=0;l<o.length;l++)if(a=o[l](t,n,i))return a.schema["x-schema-form"]&&e.isObject(a.schema["x-schema-form"])&&(a=e.extend(a,a.schema["x-schema-form"])),a},a=function(t,r,n){n=n||{};var o=n.global&&n.global.formDefaults?e.copy(n.global.formDefaults):{};return n.global&&n.global.supressPropertyTitles===!0?o.title=r.title:o.title=r.title||t,r.description&&(o.description=r.description),(n.required===!0||r.required===!0)&&(o.required=!0),r.maxLength&&(o.maxlength=r.maxLength),r.minLength&&(o.minlength=r.maxLength),(r.readOnly||r.readonly)&&(o.readonly=!0),r.minimum&&(o.minimum=r.minimum+(r.exclusiveMinimum?1:0)),r.maximum&&(o.maximum=r.maximum-(r.exclusiveMaximum?1:0)),r.validationMessage&&(o.validationMessage=r.validationMessage),r.enumNames&&(o.titleMap=i(r.enumNames,r["enum"])),o.schema=r,o.ngModelOptions=o.ngModelOptions||{},o},l=function(e,n,i){if("string"===r(n.type)&&!n["enum"]){var o=a(e,n,i);return o.key=i.path,o.type="text",i.lookup[t.stringify(i.path)]=o,o}},s=function(e,n,i){if("number"===r(n.type)){var o=a(e,n,i);return o.key=i.path,o.type="number",i.lookup[t.stringify(i.path)]=o,o}},c=function(e,n,i){if("integer"===r(n.type)){var o=a(e,n,i);return o.key=i.path,o.type="number",i.lookup[t.stringify(i.path)]=o,o}},u=function(e,n,i){if("boolean"===r(n.type)){var o=a(e,n,i);return o.key=i.path,o.type="checkbox",i.lookup[t.stringify(i.path)]=o,o}},f=function(e,i,o){if("string"===r(i.type)&&i["enum"]){var l=a(e,i,o);return l.key=o.path,l.type="select",l.titleMap||(l.titleMap=n(i["enum"])),l.trackBy="value",o.lookup[t.stringify(o.path)]=l,l}},m=function(e,i,o){if("array"===r(i.type)&&i.items&&i.items["enum"]){var l=a(e,i,o);return l.key=o.path,l.type="checkboxes",l.titleMap||(l.titleMap=n(i.items["enum"])),o.lookup[t.stringify(o.path)]=l,l}},d=function(n,i,l){if("object"===r(i.type)){var s=a(n,i,l);return s.type="fieldset",s.items=[],l.lookup[t.stringify(l.path)]=s,e.forEach(i.properties,function(e,r){var n=l.path.slice();if(n.push(r),l.ignore[t.stringify(n)]!==!0){var a=i.required&&-1!==i.required.indexOf(r),c=o(r,e,{path:n,required:a||!1,lookup:l.lookup,ignore:l.ignore,global:l.global});c&&s.items.push(c)}}),s}},p=function(e,n,i){if("array"===r(n.type)){var l=a(e,n,i);l.type="array",l.key=i.path,i.lookup[t.stringify(i.path)]=l;var s=n.required&&-1!==n.required.indexOf(i.path[i.path.length-1]),c=i.path.slice();return c.push(""),l.items=[o(e,n.items,{path:c,required:s||!1,lookup:i.lookup,ignore:i.ignore,global:i.global})],l}},h={string:[f,l],object:[d],number:[s],integer:[c],"boolean":[u],array:[m,p]},v=function(e){return e};this.defaults=h,this.stdFormObj=a,this.defaultFormDefinition=o,this.postProcess=function(e){v=e},this.appendRule=function(e,t){h[e]||(h[e]=[]),h[e].push(t)},this.prependRule=function(e,t){h[e]||(h[e]=[]),h[e].unshift(t)},this.createStandardForm=a,this.$get=function(){var n={};return n.merge=function(r,o,a,l,s){o=o||["*"],l=l||{},s=s||r.readonly||r.readOnly;var c=n.defaults(r,a,l),u=o.indexOf("*");-1!==u&&(o=o.slice(0,u).concat(c.form).concat(o.slice(u+1)));var f=c.lookup;return v(o.map(function(o){if("string"==typeof o&&(o={key:o}),o.key&&"string"==typeof o.key&&(o.key=t.parse(o.key)),o.titleMap&&(o.titleMap=i(o.titleMap)),"select"===o.type&&(o.trackBy=o.trackBy||"value"),o.itemForm){o.items=[];var c=t.stringify(o.key),u=f[c];e.forEach(u.items,function(t){var r=e.copy(o.itemForm);r.key=t.key,o.items.push(r)})}if(o.key){var m=t.stringify(o.key);if(f[m]){var d=f[m];e.forEach(d,function(e,t){void 0===o[t]&&(o[t]=d[t])})}}return s===!0&&(o.readonly=!0),o.items&&(o.items=n.merge(r,o.items,a,l,o.readonly)),o.tabs&&e.forEach(o.tabs,function(e){e.items=n.merge(r,e.items,a,l,o.readonly)}),"checkbox"===o.type&&e.isUndefined(o.schema["default"])&&(o.schema["default"]=!1),o}))},n.defaults=function(t,n,i){var a=[],l={};if(n=n||{},i=i||{},"object"!==r(t.type))throw new Error('Not implemented. Only type "object" allowed at root level of schema.');return e.forEach(t.properties,function(e,r){if(n[r]!==!0){var s=t.required&&-1!==t.required.indexOf(r),c=o(r,e,{path:[r],lookup:l,ignore:n,required:s,global:i});c&&a.push(c)}}),{form:a,lookup:l}},n.traverseSchema=function(t,r,n,i){i=e.isDefined(i)?i:!0,n=n||[];var o=function(t,r,n){if(r(t,n),e.forEach(t.properties,function(e,t){var i=n.slice();i.push(t),o(e,r,i)}),!i&&t.items){var a=n.slice();a.push(""),o(t.items,r,a)}};o(t,r,n||[])},n.traverseForm=function(t,r){r(t),e.forEach(t.items,function(e){n.traverseForm(e,r)}),t.tabs&&e.forEach(t.tabs,function(t){e.forEach(t.items,function(e){n.traverseForm(e,r)})})},n}}]),e.module("schemaForm").factory("sfValidator",[function(){var t={};return t.validate=function(t,n){if(!t)return{valid:!0};var i=t.schema;if(!i)return{valid:!0};""===n&&(n=void 0),"number"===t.type&&null===n&&(n=void 0);var o={type:"object",properties:{}},a=t.key[t.key.length-1];o.properties[a]=i,t.required&&(o.required=[a]);var l={};return e.isDefined(n)&&(l[a]=n),r.validateResult(l,o)},t}]),e.module("schemaForm").directive("sfArray",["sfSelect","schemaForm","sfValidator","sfPath",function(t,r,n,i){var o=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}};return{restrict:"A",scope:!0,require:"?ngModel",link:function(a,l,s,c){var u={};a.validateArray=e.noop,c&&a.$emit("schemaFormPropagateNgModelController",c);var f=a.$watch(s.sfArray,function(l){var s=t(l.key,a.model);if(a.$watch("model"+i.normalize(l.key),function(e){s=a.modelArray=e}),e.isUndefined(s)&&(s=[],t(l.key,a.model,s)),a.modelArray=s,l.items){var m=l.items[0];l.items.length>1&&(m={type:"section",items:l.items.map(function(t){return t.ngModelOptions=l.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=l.readonly),t})})}if(a.copyWithIndex=function(t){if(!u[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),u[t]=n}return u[t]},a.appendToArray=function(){var n=s.length,i=a.copyWithIndex(n);if(r.traverseForm(i,function(r){if(r.key){var n;e.isDefined(r["default"])&&(n=r["default"]),e.isDefined(r.schema)&&e.isDefined(r.schema["default"])&&(n=r.schema["default"]),e.isDefined(n)&&t(r.key,a.model,n)}}),n===s.length){var o,c=t("schema.items.type",l);"object"===c?o={}:"array"===c&&(o=[]),s.push(o)}return a.validateArray(),s},a.deleteFromArray=function(e){return s.splice(e,1),a.validateArray(),c&&c.$setDirty&&c.$setDirty(),s},l.titleMap||l.startEmpty===!0||0!==s.length||a.appendToArray(),l.titleMap&&l.titleMap.length>0){a.titleMapValues=[];var d=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};d(a.modelArray),a.$watchCollection("modelArray",d),a.$watchCollection("titleMapValues",function(e,t){if(e&&e!==t){for(var r=a.modelArray;r.length>0;)r.pop();l.titleMap.forEach(function(t,n){e[n]&&r.push(t.value)}),a.validateArray()}})}if(c){var p;a.validateArray=function(){var e=n.validate(l,a.modelArray.length>0?a.modelArray:void 0);Object.keys(c.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){c.$setValidity(e,!0)}),e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+l.key[l.key.length-1]||(c.$setViewValue(a.modelArray),p=e.error,c.$setValidity("tv4-"+e.error.code,!1))},a.$on("schemaFormValidate",a.validateArray),a.hasSuccess=function(){return c.$valid&&!c.$pristine},a.hasError=function(){return c.$invalid},a.schemaError=function(){return p}}f()})}}}]),e.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(t,r,n,i){var o=t.$eval(n.sfChanged);o&&o.onChange&&i.$viewChangeListeners.push(function(){e.isFunction(o.onChange)?o.onChange(i.$modelValue,o):t.evalExpr(o.onChange,{modelValue:i.$modelValue,form:o})})}}}),e.module("schemaForm").directive("sfField",["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,r,n,i,o,a,l,s,c){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:{pre:function(e){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r})},post:function(t,r,n,i){t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(i?i.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return i?i.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&o(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return l.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var a=t.$watch(n.sfField,function(e){e&&(e.ngModelOptions=e.ngModelOptions||{},t.form=e,e.key&&(t.$on("schemaForm.error."+e.key.join("."),function(r,n,i,o){(i===!0||i===!1)&&(o=i,i=void 0),t.ngModel&&n&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),i&&(e.validationMessage||(e.validationMessage={}),e.validationMessage[n]=i),t.ngModel.$setValidity(n,o===!0),o===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var r=e.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(e.key&&"retain"!==r){var n=t.model;if(e.key.length>1&&(n=c(e.key.slice(0,e.key.length-1),n)),void 0===n)return;var i=e.schema&&e.schema.type||"";"empty"===r&&-1!==i.indexOf("string")?n[e.key.slice(-1)]="":"empty"===r&&-1!==i.indexOf("object")?n[e.key.slice(-1)]={}:"empty"===r&&-1!==i.indexOf("array")?n[e.key.slice(-1)]=[]:"null"===r?n[e.key.slice(-1)]=null:delete n[e.key.slice(-1)]}}})),a())})}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){return{scope:!1,restrict:"EA",link:function(n,i,o){var a=t.has("$sanitize")?t.get("$sanitize"):function(e){return e},l="";o.sfMessage&&n.$watch(o.sfMessage,function(e){e&&(l=a(e),n.ngModel?s(n.ngModel.$valid):s())});var s=function(t){if(t&&!n.hasError())i.html(l);else{var o=[];e.forEach(n.ngModel&&n.ngModel.$error||{},function(e,t){e&&o.push(t)}),o=o.filter(function(e){return"schemaForm"!==e});var a=o[0];a?i.html(r.interpolate(a,n.ngModel.$modelValue,n.ngModel.$viewValue,n.form,n.options&&n.options.validationMessage)):i.html(l)}};s(),n.$watchCollection("ngModel.$error",function(){n.ngModel&&s(n.ngModel.$valid)})}}}]),e.module("schemaForm").directive("sfSchema",["$compile","schemaForm","schemaFormDecorators","sfSelect","sfPath","sfBuilder",function(t,r,n,i,o,a){return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(t,r){return e.$parent.$eval(t,r)}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(o,l,s,c,u){o.formCtrl=c;var f={};u(o,function(e){if(e.addClass("schema-form-ignore"),l.prepend(e),l[0].querySelectorAll){var t=l[0].querySelectorAll("[ng-model]");if(t)for(var r=0;r<t.length;r++){var n=t[r].getAttribute("ng-model");f[n.substring(n.indexOf(".")+1)]=!0}}});var m,d={},p=function(c,u){var d=r.merge(c,u,f,o.options);m&&(o.externalDestructionInProgress=!0,m.$destroy(),o.externalDestructionInProgress=!1),m=o.$new(),m.schemaForm={form:d,schema:c},l.children(":not(.schema-form-ignore)").remove();for(var p={},h=l[0].querySelectorAll("*[sf-insert-field]"),v=0;v<h.length;v++)p[h[v].getAttribute("sf-insert-field")]=h[v];var y=n.decorator(s.sfUseDecorator);l[0].appendChild(a.build(d,y,p)),t(l.children())(m),o.options&&o.options.setSchemaDefaults===!1||r.traverseSchema(c,function(t,r){if(e.isDefined(t["default"])){var n=i(r,o.model);e.isUndefined(n)&&i(r,o.model,t["default"])}}),o.$emit("sf-render-finished",l)};o.$watch(function(){var e=o.schema,t=o.initialForm||["*"];t&&e&&e.type&&(d.form!==t||d.schema!==e)&&Object.keys(e.properties).length>0&&(d.schema=e,d.form=t,p(e,t))}),o.$on("schemaFormRedraw",function(){var e=o.schema,t=o.initialForm||["*"];e&&p(e,t)}),o.$on("$destroy",function(){o.externalDestructionInProgress=!0})}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","$parse","sfSelect",function(t,r,n){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(r,i,o,a){r.$emit("schemaFormPropagateNgModelController",a);var l=null,s=r.$watch(o.schemaValidate,function(i){if(i){i.copyValueTo&&a.$viewChangeListeners.push(function(){var t=i.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var o=function(e){if(!i)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(i,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),n.valid?e:(a.$setValidity("tv4-"+n.error.code,!1),l=n.error,a.$validators?e:void 0)};"function"==typeof i.ngModel&&i.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){i[e]&&a[e]&&i[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){i[t]&&a[t]&&e.forEach(i[t],function(e,r){a[t][r]=e})}),a.$parsers.push(o),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})}),r.$on("schemaFormValidate",function(){a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),i.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue)}),r.schemaError=function(){return l},s()}})}}}]),o});
\ No newline at end of file
diff --git a/src/directives/decorators/bootstrap/array.html b/src/directives/decorators/bootstrap/array.html
index 8749b1872..b7871856d 100644
--- a/src/directives/decorators/bootstrap/array.html
+++ b/src/directives/decorators/bootstrap/array.html
@@ -22,7 +22,7 @@ <h3 ng-show="form.title && form.notitle !== true">{{ form.title }}</h3>
       {{ form.add || 'Add'}}
     </button>
   </div>
-  <div class="help-block"
+  <div class="help-block {{form.validationHtmlClass}}"
        ng-show="(hasError() && errorMessage(schemaError())) || form.description"
        ng-bind-html="(hasError() && errorMessage(schemaError())) || form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/checkbox.html b/src/directives/decorators/bootstrap/checkbox.html
index d6ad64d4b..e11f60d51 100644
--- a/src/directives/decorators/bootstrap/checkbox.html
+++ b/src/directives/decorators/bootstrap/checkbox.html
@@ -11,5 +11,5 @@
            name="{{form.key.slice(-1)[0]}}">
     <span ng-bind-html="form.title"></span>
   </label>
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/checkboxes.html b/src/directives/decorators/bootstrap/checkboxes.html
index 45b514135..6057ce99b 100644
--- a/src/directives/decorators/bootstrap/checkboxes.html
+++ b/src/directives/decorators/bootstrap/checkboxes.html
@@ -14,5 +14,5 @@
     </label>
 
   </div>
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/default.html b/src/directives/decorators/bootstrap/default.html
index b5685042b..8b5c15f32 100644
--- a/src/directives/decorators/bootstrap/default.html
+++ b/src/directives/decorators/bootstrap/default.html
@@ -50,5 +50,5 @@
         id="{{form.key.slice(-1)[0] + 'Status'}}"
         class="sr-only">{{ hasSuccess() ? '(success)' : '(error)' }}</span>
 
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/fieldset-trcl.html b/src/directives/decorators/bootstrap/fieldset-trcl.html
index e4069bd77..31efa521e 100644
--- a/src/directives/decorators/bootstrap/fieldset-trcl.html
+++ b/src/directives/decorators/bootstrap/fieldset-trcl.html
@@ -1,5 +1,5 @@
 <fieldset ng-disabled="form.readonly" class="schema-form-fieldset {{form.htmlClass}}">
   <legend ng-class="{'sr-only': !showTitle() }">{{ form.title }}</legend>
-  <div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" ng-show="form.description" ng-bind-html="form.description"></div>
   <div ng-transclude></div>
 </fieldset>
diff --git a/src/directives/decorators/bootstrap/fieldset.html b/src/directives/decorators/bootstrap/fieldset.html
index 4db3f059b..0248ea9bd 100644
--- a/src/directives/decorators/bootstrap/fieldset.html
+++ b/src/directives/decorators/bootstrap/fieldset.html
@@ -1,5 +1,5 @@
 <fieldset ng-disabled="form.readonly" class="schema-form-fieldset {{form.htmlClass}}">
   <legend ng-class="{'sr-only': !showTitle() }">{{ form.title }}</legend>
-  <div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" ng-show="form.description" ng-bind-html="form.description"></div>
   <sf-decorator ng-repeat="item in form.items" form="item"></sf-decorator>
 </fieldset>
diff --git a/src/directives/decorators/bootstrap/radio-buttons.html b/src/directives/decorators/bootstrap/radio-buttons.html
index 5a12dc86a..5c369d946 100644
--- a/src/directives/decorators/bootstrap/radio-buttons.html
+++ b/src/directives/decorators/bootstrap/radio-buttons.html
@@ -20,5 +20,5 @@
       <span ng-bind-html="item.name"></span>
     </label>
   </div>
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/radios-inline.html b/src/directives/decorators/bootstrap/radios-inline.html
index 6c3d07928..81a264c82 100644
--- a/src/directives/decorators/bootstrap/radios-inline.html
+++ b/src/directives/decorators/bootstrap/radios-inline.html
@@ -14,5 +14,5 @@
       <span ng-bind-html="item.name"></span>
     </label>
   </div>
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/radios.html b/src/directives/decorators/bootstrap/radios.html
index f3b73189b..4b9e1cf3f 100644
--- a/src/directives/decorators/bootstrap/radios.html
+++ b/src/directives/decorators/bootstrap/radios.html
@@ -14,5 +14,5 @@
       <span ng-bind-html="item.name"></span>
     </label>
   </div>
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/select.html b/src/directives/decorators/bootstrap/select.html
index dbefbd208..68feabdf3 100644
--- a/src/directives/decorators/bootstrap/select.html
+++ b/src/directives/decorators/bootstrap/select.html
@@ -12,5 +12,5 @@
           ng-options="item.value as item.name group by item.group for item in form.titleMap track by item[form.trackBy]"
           name="{{form.key.slice(-1)[0]}}">
   </select>
-  <div class="help-block" sf-message="form.description"></div>
+  <div class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></div>
 </div>
diff --git a/src/directives/decorators/bootstrap/textarea.html b/src/directives/decorators/bootstrap/textarea.html
index 06364edfc..64813acb4 100644
--- a/src/directives/decorators/bootstrap/textarea.html
+++ b/src/directives/decorators/bootstrap/textarea.html
@@ -31,5 +31,5 @@
           ng-bind-html="form.fieldAddonRight"></span>
   </div>
 
-  <span class="help-block" sf-message="form.description"></span>
+  <span class="help-block {{form.validationHtmlClass}}" sf-message="form.description"></span>
 </div>