In this example you will use custom AngularJS directives to build elements on the web page that can expand and contract. Each element will have a title and an expand/collapse button on top. When the collapse button is clicked, the contents of the element will be hidden. When the expand button is clicked, the contents will be shown again.
The purpose of this exercise is to solidify implementing custom AngularJS directives that nest inside each other and communicate with each other. In this example you also get to see how a scope gets isolated from the controller but shared between the expand container and the items in the container.
The folder structure for this example is as follows:
■ ./server.js: Node.js web server that serves the static project files.
■ ./images: Folder that contains the images used in the examples.
■ ./ch11: Project folder.
■ ./ch11/expand.html: AngularJS template for the project that implements the custom expandable directives.
■ ./ch11/expand_list.html: AngularJS partial template for the expandable element directive.
■ ./ch11/expand_item.html: AngularJS partial template for each individual item in the expandable element.
■ ./ch11/js/expand.js: AngularJS application supporting the expandable element directives.
The code in Listing 11.10 contains the expand.js
AngularJS application that defines the expandList
and expandItem
custom AngularJS directives. The transclude option is used to keep the contents that get defined in the template.
Note that in the expandList
directive, the scope is an isolate but accepts the attributes title
and exwidth
, which are set to title
and listWidth
in the scope. Note that in line 27 the listWidth
value is used to set the width of the style for items added to the expand list. Also listWidth
is used in line 34 to set the css
attribute width
for the expandable list.
The expandItem
directive requires the expandList
directive to provide access to the addItem()
function tin the expandList
directive’s scope. Note that the myStyle
attribute is used to build a style object that will be set to the ng-style
for the item in the expanded list.
The way that expanding and collapsing work is that the myHide
value is bound to each item in the expanded list using ng-hide
in the template shown in Listing 11.11. The items
property in the scope of expandList
provides a list of scopes for each of the expandItem
elements that get added. Then when the expand/collapse button is clicked, it is a simple matter of setting the myHide
value to true
or false
in the scope for each item in the collapse()
function to show or hide the items in the expanded element.
01 angular.module('myApp', [])
02 .controller('myController', ['$scope', function($scope) {
03 $scope.items = [1,2,3,4,5];
04 }])
05 .directive('expandList', function() {
06 return {
07 restrict: 'E',
08 transclude: true,
09 scope: {title: '@', listWidth: '@exwidth'},
10 controller: function($scope) {
11 $scope.collapsed = false;
12 $scope.expandHandle = "-";
13 items = $scope.items = [];
14 $scope.collapse = function() {
15 if ($scope.collapsed){
16 $scope.collapsed = false;
17 $scope.expandHandle = "-";
18 } else {
19 $scope.collapsed = true;
20 $scope.expandHandle = "+";
21 }
22 angular.forEach($scope.items, function(item) {
23 item.myHide = $scope.collapsed;
24 });
25 };
26 this.addItem = function(item) {
27 item.myStyle.width = $scope.listWidth;
28 items.push(item);
29 item.myHide=false;
30 };
31 },
32 link: function(scope, element, attrs, expandCtrl) {
33 element.css("display", "inline-block");
34 element.css("width", scope.listWidth);
35 },
36 templateUrl: 'expand_list.html',
37 };
38 })
39 .directive('expandItem', function() {
40 return {
41 require: '^expandList',
42 restrict: 'E',
43 transclude: true,
44 scope: {},
45 controller: function($scope){
46 $scope.myHide = false;
47 $scope.myStyle = { width: "100px", "display": "inline-block" };
48 },
49 link: function(scope, element, attrs, expandCtrl) {
50 expandCtrl.addItem(scope);
51 },
52 templateUrl: 'expand_item.html',
53 };
54 });
Listing 11.11 contains the AngularJS partial template expand_list.html
that provides the definition for the expandList
element. The elements for the expand list header, including the expand/collapse button and the title, are added. The <div ng-transclude>
element is where the expandItem
elements will be placed.
01 <div>
02 <div class="expand-header">
03 <span class="expand-button"
04 ng-click="collapse()">{{expandHandle}}</span>
05 {{title}}
06 </div>
07 <div ng-transclude></div>
08 </div>
Listing 11.12 contains the AngularJS partial template expand_item.html
that provides the definition for the expandable items. Notice that ng-hide
is set to myHide
in the scope to expand/collapse the element. ng-style
is set to myStyle
so that we can set the width to the expand list width. The expand-item
class enables us to easily change the item appearance using CSS. The ng-transclude
is used to place the contents from the AngularJS template definition inside the list item.
01 <div ng-hide="myHide"
02 ng-style="myStyle"
03 class="expand-item"
04 ng-transclude>
05 </div>
Listing 11.13 implements the AngularJS template that provides the styles for the page as well as definitions for the <expand-list>
elements. Notice that four different <expand-list>
elements are defined. The first is a simple list where the <expand-item>
element contains just text. The next provides a single <expand-item>
element with form elements. The third contains a mixture of different HTML elements in each <expand-item>
element. The final one contains just an <img>
element.
Note that each <expand-list>
element contains a different value for the attributes title
and exwidth
, which results in lists with different titles and widths on the page. The results of the AngularJS application are shown in Figure 11.4. Notice the expanded and collapsed version of the elements.
01 <!DOCTYPE html>
02 <html ng-app="myApp">
03 <head>
04 <title>Expandable and Collapsible Lists</title>
05 <style>
06 * { vertical-align: top; }
07 expand-list{
08 border: 2px ridge black; }
09 .expand-header{
10 text-align: center;
11 font: bold 16px/24px arial;
12 background-image: linear-gradient(#CCCCCC, #EEEEEE);
13 }
14 .expand-button{
15 float: left; padding: 2px 4px;
16 font: bold 22px/16px courier;
17 color: white; background-color: black;
18 cursor: pointer;
19 border: 3px groove grey; }
20 .expand-item {
21 border: 1px ridge black;}
22 p { margin: 0px; padding: 2px;}
23 label { display: inline-block; width: 80px; padding: 2px; }
24 .small { width: 100px; padding: 2px; }
25 .large { width: 300px; }
26 </style>
27 </head>
28 <body>
29 <h2>Expandable and Collapsible Lists</h2>
30 <hr>
31 <div ng-controller="myController">
32 <expand-list title="Companion" exwidth="120px">
33 <expand-item>Rose</expand-item>
34 <expand-item>Donna</expand-item>
35 <expand-item>Martha</expand-item>
36 <expand-item>Amy</expand-item>
37 <expand-item>Rory</expand-item>
38 </expand-list>
39 <expand-list title="Form" exwidth="280px">
40 <expand-item>
41 <label>Name</label>
42 <input type="text" /><br>
43 <label>Phone</label>
44 <input type="text" /><br>
45 <label>Address</label>
46 <input type="text" /><br>
47 <label>Comment</label>
48 <textarea type="text"></textarea>
49 </expand-item>
50 </expand-list>
51 <hr>
52 <expand-list title="Mixed List" exwidth="300px">
53 <expand-item>Text Item</expand-item>
54 <expand-item><p>I think therefore I am.</p></expand-item>
55 <expand-item>
56 <img class="small" src="/images/jump.jpg" />Sunset
57 </expand-item>
58 <expand-item>
59 <ul>
60 <li>AngularJS</li>
61 <li>jQuery</li>
62 <li>JavaScript</li>
63 </ul>
64 </expand-item>
65 </expand-list>
66 <expand-list title="Image" exwidth="300px">
67 <expand-item>
68 <img class="large" src="/images/lake.jpg" />
69 </expand-item>
70 </expand-list>
71 </div>
72 </body>
73 <script src="http://code.angularjs.org/1.3.0/angular.min.js"></script>
74 <script src="js/expand.js"></script>
75 </html>