-
Notifications
You must be signed in to change notification settings - Fork 27
/
menuText.html
166 lines (128 loc) · 10.1 KB
/
menuText.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<!DOCTYPE html>
<html>
<head>
<title>Angular JS Sliding Menu</title>
</head>
<body>
<h2>Angular JS Sliding Menu</h2>
<p>
I'd like to talk about how to create a sliding menu control using <a href="http://angularjs.org" target="_blank">Angular JS</a> and <a href="http://lesscss.org/" target="_blank">LESS CSS</a>. The basic idea is that I want a menu to slide in from either the left or the right side of the screen based on some external controller action. If you're the kind of person who wants to see the big picture instead of having a step by step tutorial laid out for you, I've put together a demo page, which you can see <a href="http://chrisharrington.github.io/demos/menu.html" target="_blank">here</a>. The menu itself will be an Angular JS directive which takes just two arguments: visible and alignment. Visible indicates which controller variable should be responsible for showing the menu, and the alignment variable determines whether or not the menu slides in from the left side of the screen or the right side of the screen.
</p>
<p>
This article will be broken down into three separate sections: the HTML, the JavaScript, and the CSS. The HTML shows how I've got the menu directive laid out, and also provides example usage. The JavaScript is the meat of the article, and will explain how to create the sliding menu directive, as well as how to tie the Angular JS controller into the directive itself. The CSS is a little complicated, as I'm adding fancy animations to do the actual sliding, but outside of that, it's just plain old styling.
</p>
<h3>HTML</h3>
<h4>Sample Usage</h4>
<p>Use the following to add the sliding menu directive to your page, where "visible" and "alignment" are variables on your controller.</p>
<pre>
<menu visible="visible" alignment="left">
<menu-item hash="first-page">First Page></menu-item>
<menu-item hash="second-page">Second Page></menu-item>
<menu-item hash="third-page">Third Page></menu-item>
</menu>
</pre>
<h4>Directive</h4>
<p>Here's the HTML for the directive template. It's relatively straightforward, but I'd still recommend you put this in a separate file for ease of use. The only interesting thing going on here is the use of the <b>ng-class</b> directive, which indicates that a CSS class be added to the indicated element based on the truthiness of the specified variable. In this case, if visible is truthy (meaning not null or undefined), the show class will be added to the parent div. I'm also setting the alignment class here, too, with left assigning the left class, and right the right, respectively. The <b>ng-transclude</b> directive is used to render child elements into the directive. For example, the sample usage above denotes three menu-items; those three nodes would be placed inside the rendered HTML for the menu, as desecribed below.</p>
<pre>
<div ng-class='{ show: visible, left: alignment === \"left\", right: alignment === \"right\" }' ng-transclude></div>
</pre>
<h3>JavaScript</h3>
<p>The JavaScript is broken down into a couple of different sections. First, there's the initialization of the Angular JS application. Second, we'll take a look at the controller that initializes the directive. And finally, we'll take a look at the two directives used to render the menu: the menu itself, and the nested menu items.</p>
<h4>Application</h4>
<p>Here, we're initializing the Angular JS application, and then hooking up some events that we can use to hide the menus once they're revealed. When the user hits escape or clicks anywhere on the document, the menus should hide. Angular JS has event emitters built into its scope objects, so we're making use of those on the $rootScope variable, which is the overarching scope that is the eventual parent of all of the other scopes in your application.</p>
<p><b>Note:</b> You'll have to stop the propagation of the event used to trigger the showing of the menus, because if you don't, this document-wide click handler will immediately hide the menu after it's shown. Take a look at the <a href="http://chrisharrington.github.io/demos/menu.html">test page</a> for an example of how to do this.</p>
<pre>
var app = angular.module("demo", []);
app.run(function($rootScope) {
document.addEventListener("keyup", function(e) {
if (e.keyCode === 27)
$rootScope.$broadcast("escapePressed", e.target);
});
document.addEventListener("click", function(e) {
$rootScope.$broadcast("documentClicked", e.target);
});
});
</pre>
<h4>Controller</h4>
<p>Here's an example as to how your controller would manipulate the menus so as to show and hide them. The showLeft and showRight functions would show the left and right menus, respectively, and we're tying into the aforementioned event emiiter on the $rootScope variable via the $on methods to close the menus when appropriate.</p>
<pre>
app.controller("modalDemo", function($scope, $rootScope) {
$scope.leftVisible = false;
$scope.rightVisible = false;
$scope.close = function() {
$scope.leftVisible = false;
$scope.rightVisible = false;
};
$scope.showLeft = function(e) {
$scope.leftVisible = true;
e.stopPropagation();
};
$scope.showRight = function(e) {
$scope.rightVisible = true;
e.stopPropagation();
}
$rootScope.$on("documentClicked", _close);
$rootScope.$on("escapePressed", _close);
function _close() {
$scope.$apply(function() {
$scope.close();
});
}
});
</pre>
<h4>Directives</h4>
<p>Here's the code to control the two directives in the menu. Both are restricted to elements (via the restrict: "E" option), and we're transcluding the inner content of both into each directive's content. This means that the inner contents of the menu directive in the example above get written into the menu's immediate child div. The inner content of each menu-item tag (namely the text we want to show in the menu items) get rendered within each menu item, too. Angular JS knows where to put the transcluded contents via the <b>ng-transclude</b> directive, which you see in both templates below. For the menu directive, we've created an isolated scope containing the visible two-way binding and the alignment one-way binding. This allows us to read the visible variable's value and the alignment's string value. We're doing similarly with the hash variable in the menu item's isolated scope. Finally, the menu item has a method added to its scope via the link option's function, navigate, which is fired when the user clicks on the menu item, as seen by the <b>ng-click</b> directive.</b></p>
<pre>
app.directive("menu", function() {
return {
restrict: "E",
template: "<div ng-class='{ show: visible, left: alignment === \"left\", right: alignment === \"right\" }' ng-transclude></div>",
transclude: true,
scope: {
visible: "=",
alignment: "@"
}
};
});
app.directive("menuItem", function() {
return {
restrict: "E",
template: "<div ng-click='navigate()' ng-transclude></div>",
transclude: true,
scope: {
hash: "@"
},
link: function($scope) {
$scope.navigate = function() {
window.location.hash = $scope.hash;
}
}
}
});
</pre>
<h4>CSS</h4>
<p>Finally, here's the CSS. The first three lines describe some LESS CSS mixins I've defined to help ease the vendor prefix nightmare. The first is for defining transitions (or animations); the second is for CSS transformations; and the third is to set the border-box value for box-sizing. Below that is the CSS for the menu, and within that, each menu item. I'm making liberal use of the <b>&</b> LESS CSS command, which allows me to specify styles for various conditions, which in this case are additional classes, like left, show, etc. Those are almost exclusively used to define positioning of the menus depending on the specified alignment.</b></p>
<pre>
.transition (@value1,@value2:X,...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; -webkit-transition: @value; -moz-transition: @value; -ms-transition: @value; -o-transition: @value; transition: @value; }
.transform (@value1,@value2:X,...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; transform:@value; -ms-transform:@value; -webkit-transform:@value; -o-transform:@value; -moz-transform:@value; }
.border-box { box-sizing:border-box; -moz-box-sizing:border-box; }
menu { display:block;
@menu-width:250px;
>div { position:absolute; z-index:2; top:0; width:@menu-width; height:100%; .border-box; .transition(-webkit-transform ease 250ms); .transition(transform ease 250ms);
&.left { background:#273D7A; left:@menu-width*-1; }
&.show.left { .transform(translate3d(@menu-width, 0, 0)); }
&.right { background:#6B1919; right:@menu-width*-1; }
&.show.right { .transform(translate3d(@menu-width*-1, 0, 0)); }
>menu-item { display:block;
>div { float:left; width:100%; margin:0; padding:10px 15px; border-bottom:solid 1px #555; cursor:pointer; .border-box; color:#B0B0B0;
&:hover { color:#F0F0F0; }
>span { float:left; color:inherit; }
}
}
}
}
</pre>
<h3>Conclusion</h3>
<p>That's it! I hope this has taught you something as you've read through this. If you'd like to see these menus in action, you can check out the <a href="http://chrisharrington.github.com/demos/menu.html" target="_blank">demo page</a>, which adds both a left and a right menu to the page. Thanks for reading!</p>
</body>
</html