├── app
├── main.js
├── controllers
│ ├── slave-controller.js
│ └── master-controller.js
├── css
│ └── demo.css
└── directives
│ ├── slave.js
│ └── master.js
├── README.md
├── LICENSE.md
└── index.htm
/app/main.js:
--------------------------------------------------------------------------------
1 | (function( ng ) {
2 |
3 | "use strict";
4 |
5 | // Define our AngularJS application module.
6 | window.demo = ng.module( "Demo", [] );
7 |
8 | })( angular );
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AngularJS Directive Controllers
3 |
4 | by [Ben Nadel][1]
5 |
6 | [View the demo on GitHub Pages][2]. This is an exploration of the use of Controllers in
7 | Directives as a means to facilitate inter-directive communication.
8 |
9 |
10 | [1]: http://www.bennadel.com
11 | [2]: http://bennadel.github.com/AngularJS-Directive-Controllers
--------------------------------------------------------------------------------
/app/controllers/slave-controller.js:
--------------------------------------------------------------------------------
1 | (function( ng, app ) {
2 |
3 | "use strict";
4 |
5 | app.controller(
6 | "SlaveController",
7 | function( $scope ) {
8 |
9 |
10 | // -- Define Scope Methods. --------------------- //
11 |
12 |
13 | // I remove the current slave from the collection.
14 | $scope.remove = function() {
15 |
16 | // Pass this responsibility up the scope chain to the master controller (and its
17 | // collection of slave instances).
18 | $scope.removeSlave( $scope.slave );
19 |
20 | };
21 |
22 |
23 | // I reposition the current slave.
24 | $scope.reposition = function( x, y ) {
25 |
26 | $scope.slave.x = x;
27 | $scope.slave.y = y;
28 |
29 | };
30 |
31 |
32 | }
33 | );
34 |
35 | })( angular, demo );
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | # The MIT License (MIT)
3 |
4 | Copyright (c) 2013 [Ben Nadel][1]
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | [1]: http://www.bennadel.com
--------------------------------------------------------------------------------
/app/controllers/master-controller.js:
--------------------------------------------------------------------------------
1 | (function( ng, app ) {
2 |
3 | "use strict";
4 |
5 | app.controller(
6 | "MasterController",
7 | function( $scope ) {
8 |
9 |
10 | // -- Define Controller Methods. ---------------- //
11 |
12 |
13 | // I get the next available ID for a new slave.
14 | function getNextID() {
15 |
16 | if ( ! $scope.slaves.length ) {
17 |
18 | return( 1 );
19 |
20 | }
21 |
22 | var lastSlave = $scope.slaves[ $scope.slaves.length - 1 ];
23 |
24 | return( lastSlave.id + 1 );
25 |
26 | }
27 |
28 |
29 | // I get a random coordinate based on the given constraints.
30 | function getRandomCoordinate( minCoordinate, maxCoordinate ) {
31 |
32 | var delta = ( maxCoordinate - minCoordinate );
33 |
34 | return(
35 | minCoordinate + Math.floor( Math.random() * delta )
36 | );
37 |
38 | }
39 |
40 |
41 | // -- Define Scope Methods. --------------------- //
42 |
43 |
44 | // I add a new slave at the given position.
45 | $scope.addSlave = function( x, y ) {
46 |
47 | $scope.slaves.push({
48 | id: getNextID(),
49 | x: x,
50 | y: y
51 | });
52 |
53 | };
54 |
55 |
56 | // I remove the given slave from the collection.
57 | $scope.removeSlave = function( slave ) {
58 |
59 | // Find the slave in the collection.
60 | var index = $scope.slaves.indexOf( slave );
61 |
62 | // Splice out slave.
63 | $scope.slaves.splice( index, 1 );
64 |
65 | };
66 |
67 |
68 | // -- Set Scope Variables. ---------------------- //
69 |
70 |
71 | // This is our list of slaves and their coordinates. Starting with an initial
72 | // collection of one (at a random position).
73 | $scope.slaves = [
74 | {
75 | id: 1,
76 | x: getRandomCoordinate( 50, 400 ),
77 | y: getRandomCoordinate( 50, 200 )
78 | }
79 | ];
80 |
81 |
82 | }
83 | );
84 |
85 | })( angular, demo );
--------------------------------------------------------------------------------
/app/css/demo.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | font-family: tahoma, geneva, sans-serif ;
4 | margin: 0px 0px 0px 0px ;
5 | padding: 0px 0px 0px 0px ;
6 | }
7 |
8 | h1 {
9 | color: #F0F0F0 ;
10 | left: 20px ;
11 | position: fixed ;
12 | top: 20px ;
13 | z-index: 1 ;
14 | }
15 |
16 | div.master {
17 | bottom: 0px ;
18 | left: 0px ;
19 | position: fixed ;
20 | right: 0px ;
21 | top: 0px ;
22 | z-index: 2 ;
23 | }
24 |
25 | ol.handles {
26 | bottom: 0px ;
27 | cursor: pointer ;
28 | left: 0px ;
29 | list-style-type: none ;
30 | margin: 0px 0px 0px 0px ;
31 | padding: 0px 0px 0px 0px ;
32 | position: fixed ;
33 | right: 200px ;
34 | top: 0px ;
35 | }
36 |
37 | ol.handles li {
38 | background-color: #F0F0F0 ;
39 | border: 1px solid #CCCCCC ;
40 | border-radius: 35px 35px 35px 35px ;
41 | cursor: pointer ;
42 | font-weight: bold ;
43 | height: 35px ;
44 | line-height: 34px ;
45 | margin: -17.5px 0px 0px -17.5px ;
46 | padding: 0px 0px 0px 0px ;
47 | position: absolute ;
48 | text-align: center ;
49 | width: 35px ;
50 | }
51 |
52 | ol.leaderboard {
53 | background-color: #F0F0F0 ;
54 | bottom: 0px ;
55 | list-style-type: none ;
56 | margin: 0px 0px 0px 0px ;
57 | overflow: auto ;
58 | padding: 0px 0px 0px 0px ;
59 | position: fixed ;
60 | right: 0px ;
61 | top: 0px ;
62 | width: 200px ;
63 | }
64 |
65 | ol.leaderboard li {
66 | border-bottom: 1px solid #CCCCCC ;
67 | font-size: 16px ;
68 | margin: 0px 0px 0px 0px ;
69 | padding: 5px 0px 0px 0px ;
70 | text-align: center ;
71 | }
72 |
73 | ol.leaderboard div.label {
74 | border: 1px dotted #CCCCCC ;
75 | border-radius: 35px 35px 35px 35px ;
76 | font-weight: bold ;
77 | height: 35px ;
78 | line-height: 34px ;
79 | margin: 0px auto 0px auto ;
80 | width: 35px ;
81 | }
82 |
83 | ol.leaderboard div.position {}
84 |
85 | ol.leaderboard div.position:after {
86 | clear: both ;
87 | content: "" ;
88 | display: block ;
89 | }
90 |
91 | ol.leaderboard span.coordinate {
92 | float: left ;
93 | padding: 5px 0px 10px 0px ;
94 | width: 50% ;
95 | }
--------------------------------------------------------------------------------
/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Using Controllers In Directives In AngularJS
7 |
8 |
9 |
10 |
11 |
12 |
13 | Using Controllers In Directives In AngularJS
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 | -
32 |
33 | {{ slave.id }}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | -
43 |
44 |
45 | {{ slave.id }}
46 |
47 |
48 |
49 | {{ slave.x }}px
50 | {{ slave.y }}px
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
67 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/directives/slave.js:
--------------------------------------------------------------------------------
1 | (function( ng, app ) {
2 |
3 | "use strict";
4 |
5 | app.directive(
6 | "bnSlave",
7 | function( $document ) {
8 |
9 |
10 | // I provide a way for directives to interact using the exposed API.
11 | function Controller( $scope, $element, $attrs ) {
12 |
13 | // -- Define Controller Methods. ------------ //
14 |
15 |
16 | // I move the current element to the given position (delta). Notice that I
17 | // update the CSS of the element directly, rather than using the slave properties.
18 | // This is because the moveTo() will NOT happen inside a $digest (for
19 | // performance reasons). As such, the ngStyle on the element will not have any
20 | // effect on the position resulting from the mouse movement.
21 | function moveTo( deltaX, deltaY ) {
22 |
23 | $element.css({
24 | left: ( $scope.slave.x + deltaX + "px" ),
25 | top: ( $scope.slave.y + deltaY + "px" )
26 | });
27 |
28 | }
29 |
30 |
31 | // I reposition the current slave to the given position (delta). This updates
32 | // the slave directly, as this WILL happen inside of a $digest.
33 | function reposition( deltaX, deltaY ) {
34 |
35 | $scope.reposition(
36 | ( $scope.slave.x + deltaX ),
37 | ( $scope.slave.y + deltaY )
38 | );
39 |
40 | }
41 |
42 |
43 | // -- Define Controller Variables. ---------- //
44 |
45 |
46 | // Return public API.
47 | return({
48 | moveTo: moveTo,
49 | reposition: reposition
50 | });
51 |
52 | }
53 |
54 |
55 | // I link the $scope to the DOM element and UI events.
56 | function link( $scope, element, attributes, controllers ) {
57 |
58 |
59 | // -- Define Link Methods. ------------------ //
60 |
61 |
62 | // I keep track of the initial click and start tracking movement.
63 | function handleMouseDown( event ) {
64 |
65 | $document.on( "mousemove.bnSlave", handleMouseMove );
66 | $document.on( "mouseup.bnSlave", handleMouseUp );
67 |
68 | }
69 |
70 |
71 | // I keep track of whether or not the mouse has been moved; if it has, we are
72 | // no longer going to care about the position of the mouse upon release - we'll
73 | // consider the marker "activated".
74 | function handleMouseMove( event ) {
75 |
76 | $document.off( "mousemove.bnSlave" );
77 | $document.off( "mouseup.bnSlave" );
78 |
79 | }
80 |
81 |
82 | // I keep track of the final mouse release - and, remove the slave. If this
83 | // event handler has fired, it means that the mouse-move event was not triggered,
84 | // which means the element has not been moved.
85 | function handleMouseUp( event ) {
86 |
87 | $document.off( "mousemove.bnSlave" );
88 | $document.off( "mouseup.bnSlave" );
89 |
90 | // Break the connection to the master controller so the master controller
91 | // cannot send any further communications.
92 | masterController.unbind( slaveController );
93 |
94 | // Remove the slave from the collection.
95 | $scope.$apply(
96 | function() {
97 |
98 | $scope.remove();
99 |
100 | }
101 | );
102 |
103 | }
104 |
105 |
106 | // -- Define Link Variables. ---------------- //
107 |
108 |
109 | // Get the required controllers from the link arguments.
110 | var slaveController = controllers[ 0 ];
111 | var masterController = controllers[ 1 ];
112 |
113 | // Listen to position updates from the master controller. When you bind to the
114 | // master controller, we expect to have our controller's moveTo() and reposition()
115 | // methods called.
116 | masterController.bind( slaveController );
117 |
118 | // Listen to the mouse click in order to start tracking movement changes.
119 | element.on( "mousedown.bnSlave", handleMouseDown );
120 |
121 | // When the scope is destroyed, make sure to unbind all event handlers to help
122 | // prevent a memory leak.
123 | $scope.$on(
124 | "$destroy",
125 | function( event ) {
126 |
127 | // Clean up the master-slave binding in case this element is removed outside
128 | // of our internal event handling.
129 | masterController.unbind( slaveController );
130 |
131 | // Clear any existing mouse bindings.
132 | element.off( "mousedown.bnSlave" );
133 | $document.off( "mousemove.bnSlave" );
134 | $document.off( "mouseup.bnSlave" );
135 |
136 | }
137 | );
138 |
139 | }
140 |
141 |
142 | // Return the directives configuration.
143 | return({
144 | controller: Controller,
145 | link: link,
146 | require: [ "bnSlave", "^bnMaster" ],
147 | restrict: "A"
148 | });
149 |
150 |
151 | }
152 | );
153 |
154 | })( angular, demo );
--------------------------------------------------------------------------------
/app/directives/master.js:
--------------------------------------------------------------------------------
1 | (function( ng, app ) {
2 |
3 | "use strict";
4 |
5 | app.directive(
6 | "bnMaster",
7 | function() {
8 |
9 |
10 | // I provide a way for directives to interact using the exposed API.
11 | function Controller( $scope ) {
12 |
13 |
14 | // -- Define Controller Methods. ------------ //
15 |
16 |
17 | // I am utility method that applies the given method to the each item using
18 | // the given arguments.
19 | function applyForEach( collection, methodName, methodArguments ) {
20 |
21 | ng.forEach(
22 | collection,
23 | function( item ) {
24 |
25 | item[ methodName ].apply( item, methodArguments );
26 | }
27 | );
28 |
29 | }
30 |
31 |
32 | // I add the given listern to the list of subscribers. Each listener must expose
33 | // two methods: moveTo( deltaX, deltaY ) and reposition( deltaX, deltaY ).
34 | function bind( listener ) {
35 |
36 | listeners.push( listener );
37 |
38 | }
39 |
40 |
41 | // I invoke the moveTo() method on each bound listener.
42 | function moveTo( deltaX, deltaY ) {
43 |
44 | applyForEach( listeners, "moveTo", [ deltaX, deltaY ] );
45 |
46 | }
47 |
48 |
49 | // I invoke the reposition() method on each bound listener.
50 | function reposition( deltaX, deltaY ) {
51 |
52 | applyForEach( listeners, "reposition", [ deltaX, deltaY ] );
53 |
54 | }
55 |
56 |
57 | // I unbind the given listener from the list of subscribers.
58 | function unbind( listener ) {
59 |
60 | var index = listeners.indexOf( listener );
61 |
62 | if ( index === -1 ) {
63 |
64 | return;
65 |
66 | }
67 |
68 | listeners.splice( index, 1 );
69 |
70 | }
71 |
72 |
73 | // -- Define Controller Variables. ---------- //
74 |
75 |
76 | // I am the collection of listeners that want to know about updated coordinates.
77 | var listeners = [];
78 |
79 |
80 | // Return public API.
81 | return({
82 | bind: bind,
83 | moveTo: moveTo,
84 | reposition: reposition,
85 | unbind: unbind
86 | });
87 |
88 | }
89 |
90 |
91 | // I link the $scope to the DOM element and UI events.
92 | function link( $scope, element, attributes, controller ) {
93 |
94 |
95 | // -- Define Link Methods. ------------------ //
96 |
97 |
98 | // I keep track of the initial mouse click on the master. The behavior differs
99 | // depending on whether a slave was clicked; or, the master canvas was clicked.
100 | function handleMouseDown( event ) {
101 |
102 | var target = $( event.target );
103 |
104 | // Prevent default behavior to stop text selection.
105 | event.preventDefault();
106 |
107 | // The user clicked on a slave.
108 | if ( target.is( "li.slave" ) ) {
109 |
110 | // Record the initial position of the mouse so we can calculate the
111 | // coordinates of the reposition (using deltas).
112 | initialPageX = event.pageX;
113 | initialPageY = event.pageY;
114 |
115 | // Bind to the movement so we can broadcast new coordinates.
116 | element.on( "mousemove.bnMaster", handleMouseMove );
117 | element.on( "mouseup.bnMaster", handleMouseUp );
118 |
119 | // The user clicked on the master canvas directly. We'll use this as an invite
120 | // to create a new slave handle.
121 | } else {
122 |
123 | $scope.$apply(
124 | function() {
125 |
126 | $scope.addSlave( event.pageX, event.pageY );
127 |
128 | }
129 | );
130 |
131 | }
132 |
133 | }
134 |
135 |
136 | // I listen for mouse movements to broadcast new position deltas to all of the slaves.
137 | function handleMouseMove( event ) {
138 |
139 | controller.moveTo(
140 | ( event.pageX - initialPageX ),
141 | ( event.pageY - initialPageY )
142 | );
143 |
144 | }
145 |
146 |
147 | // I listen for mouse ups to determine when movement has ceased and positions
148 | // of the slaves need to be finalized.
149 | function handleMouseUp( event ) {
150 |
151 | // Now that the user has finished moving the mouse, unbind the mouse events.
152 | element.off( "mousemove.bnMaster" );
153 | element.off( "mouseup.bnMaster" );
154 |
155 | // Check to see if the elements have moved at all. If they have not, then there
156 | // is nothing more that the master needs to do.
157 | if ( ! hasMoved( event.pageX, event.pageY ) ) {
158 |
159 | return;
160 |
161 | }
162 |
163 | // Tell all the slaves to finalize positions.
164 | $scope.$apply(
165 | function() {
166 |
167 | controller.reposition(
168 | ( event.pageX - initialPageX ),
169 | ( event.pageY - initialPageY )
170 | );
171 |
172 | }
173 | );
174 |
175 | }
176 |
177 |
178 | // I determine if the given coorindates indicate movement from the original position.
179 | function hasMoved( pageX, pageY ) {
180 |
181 | return(
182 | ( pageX !== initialPageX ) ||
183 | ( pageY !== initialPageY )
184 | );
185 |
186 | }
187 |
188 |
189 | // -- Define Link Variables. ---------------- //
190 |
191 |
192 | // I hold the initial position of the mouse click.
193 | var initialPageX = null;
194 | var initialPageY = null;
195 |
196 | // Bind to the mouse down event so we can interact with the slaves.
197 | element.on( "mousedown.bnMaster", handleMouseDown );
198 |
199 | // When the scope is destroyed, make sure to unbind all event handlers to help
200 | // prevent a memory leak.
201 | $scope.$on(
202 | "$destroy",
203 | function( event ) {
204 |
205 | element.off( "mousedown.bnMaster" );
206 | element.off( "mousemove.bnMaster" );
207 | element.off( "mouseup.bnMaster" );
208 |
209 | }
210 | );
211 |
212 | }
213 |
214 |
215 | // Return the directives configuration.
216 | return({
217 | controller: Controller,
218 | link: link,
219 | require: "bnMaster",
220 | restrict: "A"
221 | });
222 |
223 |
224 | }
225 | );
226 |
227 | })( angular, demo );
--------------------------------------------------------------------------------