var hCollapseDirective = function () {
return {
link: function (scope, elem, attrs, ctrl) {
scope.collapsed = false;
scope.$watch('collapse', function (collapsed) {
elem.toggleClass('collapse', !!collapsed);
});
},
scope: {},
templateUrl: 'collapse.html',
transclude: true
}
}
var hRecursiveDirective = function ($compile) {
return {
link: function (scope, elem, attrs, ctrl) {
ctrl.transclude(scope, function (content) {
elem.after(content);
});
},
controller: function ($element, $transclude) {
var parent = $element.parent().controller('hRecursive');
this.transclude = angular.isObject(parent)
? parent.transclude
: $transclude;
},
priority: 500, // ngInclude < hRecursive < ngIf < ngRepeat < ngSwitch
require: 'hRecursive',
terminal: true,
transclude: 'element',
$$tlb: true // Hack: allow multiple transclusion (ngRepeat and ngIf)
}
}
angular.module('h', [])
.directive('hCollapse', hCollapseDirective)
.directive('hRecursive', hRecursiveDirective)
/* Demo CSS */
* { box-sizing: border-box }
html { line-height: 1.4em }
.task h4, .task h5 { margin: 0 }
.task { background-color: white }
.task.collapse {
max-height: 1.4em;
overflow: hidden;
}
.task.collapse h4::after {
content: '...';
}
.task-list {
padding: 0;
list-style: none;
}
/* Collapse directive */
.h-collapse-expander {
background: inherit;
position: absolute;
left: .5px;
padding: 0 .2em;
}
.h-collapse-expander::before {
content: '•';
}
.h-collapse-item {
border-left: 1px dotted black;
padding-left: .5em;
}
.h-collapse-wrapper {
background: inherit;
padding-left: .5em;
position: relative;
}
<!DOCTYPE html>
<html>
<head>
<link href="collapse.css" rel="stylesheet" />
<link href="style.css" rel="stylesheet" />
<script data-require="angular.js@1.3.15" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js" data-semver="2.1.1" data-require="jquery@*"></script>
<script src="script.js"></script>
<script>
function AppController($scope) {
$scope.toggleCollapsed = function ($event) {
$event.preventDefault();
$event.stopPropagation();
this.collapsed = !this.collapsed;
}
$scope.task = {
name: 'All tasks',
assignees: ['Citizens'],
children: [
{
name: 'Gardening',
assignees: ['Gardeners', 'Horticulture Students'],
children: [
{
name: 'Pull weeds',
assignees: ['Weeding Sub-committee']
}
],
},
{
name: 'Cleaning',
assignees: ['Cleaners', 'Guests']
}
]
}
}
angular.module('app', ['h'])
.controller('AppController', AppController)
</script>
</head>
<body ng-app="app" ng-controller="AppController">
<h1>Task Application</h1>
<p>This is an AngularJS application that demonstrates a generalized
recursive templating directive. Use it to quickly produce recursive
structures in templates.</p>
<p>The recursive directive was developed in order to avoid the need for
recursive structures to be given their own templates and be explicitly
self-referential, as would be required with ngInclude. Owing to its high
priority, it should also be possible to use it for recursive directives
(directives that have templates which include the directive) that would
otherwise send the compiler into infinite recursion.</p>
<p>The directive can be used alongside ng-if
and ng-repeat to create recursive structures without the need for
additional container elements.</p>
<p>Since the directive does not request a scope (either isolated or not)
it should not impair reasoning about scope visibility, which continues to
behave as the template suggests.</p>
<p>Try playing around with the demonstration, below, where the input at
the top provides a way to modify a scope attribute. Observe how the value
is visible at all levels.</p>
<p>The collapse directive is included to further demonstrate that the
recursion can co-exist with other transclusions (not just ngIf, et al)
and that sibling directives are included on the recursive due to the
recursion using whole 'element' transclusion.</p>
<label for="volunteer">Citizen name:</label>
<input id="volunteer" ng-model="you" placeholder="your name">
<h2>Tasks</h2>
<ul class="task-list">
<li class="task" h-collapse h-recursive>
<h4>{{task.name}}</h4>
<h5>Volunteers</h5>
<ul>
<li ng-repeat="who in task.assignees">{{who}}</li>
<li>{{you}} (you)</li>
</ul>
<ul class="task-list">
<li h-recursive ng-repeat="task in task.children"></li>
</ul>
<li>
</ul>
<script type="text/ng-template" id="collapse.html">
<div class="h-collapse-wrapper">
<a class="h-collapse-expander" href="#" ng-click="collapse = !collapse"></a>
<div class="h-collapse-item" ng-transclude></div>
</div>
</script>
</body>
</html>