Skip to content

Instantly share code, notes, and snippets.

@deyceg
Last active January 31, 2017 21:08
Show Gist options
  • Save deyceg/84cd2428a6c8291de66a to your computer and use it in GitHub Desktop.
Save deyceg/84cd2428a6c8291de66a to your computer and use it in GitHub Desktop.
Angular Schema Form Accordion
<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>
<div ng-show="form.description" ng-bind-html="form.description"></div>
<accordion ng-model="modelArray" ww-collection="{{form}}" ng-form="{{form.key.slice(-1)[0]}}">
<accordion-group heading="{{group.title}}" ng-form="{{form.key.slice(-1)[0]}}_{{$index}}"
ng-repeat="item in modelArray track by $index" ww-collection-item >
<accordion-heading>
<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>
{{form.title}} {{$index + 1}}
</accordion-heading>
<sf-decorator ng-init="arrayIndex = $index" form="copyWithIndex($index)"></sf-decorator>
</accordion-group>
<div class="clearfix" style="padding: 15px;">
<button ng-hide="form.readonly || form.add === null"
ng-click="canAppend() && 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()))"
ng-bind-html="(hasError() && errorMessage(schemaError()))"></div>
</div>
'use strict';
angular
.module('form.directives')
.directive('wwCollection', collection);
function collection() {
var ddo = {
restrict: 'A',
require: ['^ngModel', '^accordion', '^form'],
scope: false,
link: linker
};
return ddo;
//////////
function linker(scope, elem, attrs, ctrls) {
var obj = scope.$eval(attrs.wwCollection);
var accordionCtrl = ctrls[1];
var expandedAccordionIndex;
var accordionFormCtrl = ctrls[2];
scope.canAppend = function () {
var expandedAccordionFormCtrl;
//if there are none in the collection then thats ok
if (accordionCtrl.groups.length === 0) {
return true
}
//grab the index of the currently open group
expandedAccordionIndex = _.findIndex(accordionCtrl.groups, {isOpen: true});
//none open, ok to add
if (expandedAccordionIndex === -1) {
return true;
}
expandedAccordionFormCtrl = accordionFormCtrl[accordionFormCtrl.$name + '_' + expandedAccordionIndex];
//if expanded accordion hasnt been touched then validate - can't add
if (expandedAccordionFormCtrl.$pristine || expandedAccordionFormCtrl.$dirty) {
runValidators(expandedAccordionFormCtrl);
return false;
}
//if expanded accordion has already been validated and is invalid - can't add
if (expandedAccordionFormCtrl.$invalid) {
//rerun validations to ensure all form controls are checked
runValidators(expandedAccordionFormCtrl);
return false;
}
//bit of a catch all
return !accordionFormCtrl.$invalid;
};
//We want to know when we're adding/removing from the group
scope.$watch(function () {
return accordionCtrl.groups.length
}, function (newVal, oldVal) {
//Open first
if (oldVal === 0 && newVal === 1) {
return accordionCtrl.groups[0].toggleOpen();
}
//Open last (possibly)
if (newVal > oldVal) {
//Close others (edge case - think it happens when loading a session that has multiple items in the colleciton)
if (oldVal === 0 && newVal > 1) {
//Force all groups to close
_.every(accordionCtrl.groups, function (g) {
g.isOpen = false;
});
return;
}
//Really open last
return accordionCtrl.groups[(newVal - 1)].toggleOpen();
}
});
//TODO DRY
function runValidators(form) {
angular.forEach(form, function (value, key) {
if (key !== '$$parentForm' && value && value.hasOwnProperty('$addControl')) { //nested form
runValidators(value)
}
else {
if (typeof key === 'string' && key.indexOf('$') !== 0 && key.indexOf('__proto__') !== 0) {
var ctrl = value;
ctrl.$setDirty(true);
ctrl.$setTouched(true); //Lets pretend they touched it!
ctrl.$validate();
}
}
});
}
}
}
'use strict';
angular
.module('form.directives')
.directive('wwCollectionItem', collectionItem);
function collectionItem($timeout) {
var ddo = {
restrict: 'A',
require: ['^ngModel', '^accordion', '^form'],
scope: false,
link: linker
};
return ddo;
//////////
function linker(scope, elem, attrs, ctrls) {
var obj = scope.$eval(attrs.wwCollectionItem);
var accordionCtrl = ctrls[1];
var accordionForm = ctrls[2];
//Need to wait until next $digest so groups have a chance to be added
$timeout(function() {
//We need to know when the accordion is about to collapse
accordionCtrl.groups[scope.$index].$watch('isOpen', function(newVal, oldVal) {
//closing
if (!newVal) {
//Validate the accordion form
runValidators(accordionForm);
//Force the accordion to stay open
if (accordionForm.$invalid) {
accordionCtrl.groups[scope.$index].isOpen = true;
//Ensure all the others are closed
angular.forEach(ctrls[1].groups, function(g, i) {
if (i === scope.$index) return;
accordionCtrl.groups[i].isOpen = false;
})
}
}
})
});
//TODO DRY
function runValidators(form) {
angular.forEach(form, function(value, key) {
if (key !== '$$parentForm' && value && value.hasOwnProperty('$addControl')){ //nested form
runValidators(value)
}
else {
if(typeof key === 'string' && key.indexOf('$') !== 0 && key.indexOf('__proto__') !== 0) {
var ctrl = value;
ctrl.$setDirty(true);
ctrl.$setTouched(true); //Lets pretend they touched it!
ctrl.$validate();
}
}
});
}
}
}
@shaun-here
Copy link

Can you show an example for how to use this add-on with form-schema?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment