Created
November 27, 2015 15:45
-
-
Save bennadel/fea53b74006267216b57 to your computer and use it in GitHub Desktop.
Sometimes, There Is Unavoidable Coupling To The DOM In AngularJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html ng-app="Demo"> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Sometimes, There Is Unavoidable Coupling To The DOM In AngularJS | |
</title> | |
<link rel="stylesheet" type="text/css" href="./demo.css"></link> | |
</head> | |
<body> | |
<h1> | |
Sometimes, There Is Unavoidable Coupling To The DOM In AngularJS | |
</h1> | |
<dot-grid> | |
<ul> | |
<li | |
ng-repeat="dot in dg.dots track by dot.id" | |
ng-style="{ left: dot.x, top: dot.y }"> | |
<span>{{ dot.id }}</span> | |
</li> | |
</ul> | |
</dot-grid> | |
<!-- Load scripts. --> | |
<script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs/angular-1.4.7.min.js"></script> | |
<script type="text/javascript"> | |
// Create an application module for our demo. | |
angular.module( "Demo", [] ); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I provide a dot-grid component that the user can click to add new dots. The | |
// dots rearrange to be evenly distributed as the set-count increases. | |
angular.module( "Demo" ).directive( | |
"dotGrid", | |
function dotGridDirective( $window ) { | |
// Return the directive configuration object. | |
return({ | |
controller: DotGridController, | |
controllerAs: "dg", | |
link: link, | |
restrict: "E" | |
}); | |
// I bind the JavaScript events to the view-model. | |
function link( scope, element, attributes, controller ) { | |
// When the user clicks on the grid, we have to add a new dot. | |
element.on( "click", handleClick ); | |
// When the user resizes the window, we have to adjust the layout. | |
angular.element( $window ).on( "resize", handleResize ); | |
// Report the initial layout to the controller. | |
scope.$evalAsync( reportLayout ); | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// When the user clicks on the grid, we need to tell the controller | |
// to add a new dot at the click coordinates. | |
function handleClick( event ) { | |
scope.$apply( | |
function changeModel() { | |
var offset = element.offset(); | |
controller.addDot( | |
( event.pageX - offset.left ), | |
( event.pageY - offset.top ) | |
); | |
} | |
); | |
} | |
// When the window is resized, it changes the dimensions of the | |
// gird. But, since the Controller doesn't know anything about the | |
// DOM, we have to report the new dimensions. | |
function handleResize() { | |
// NOTE: Using applyAsync() for micro-debouncing. | |
scope.$applyAsync( reportLayout ); | |
} | |
// I report the current physical dimensions of the grid to the | |
// controller so that it knows how to distribute the dots. | |
function reportLayout() { | |
controller.setDimensions( element.width(), element.height() ); | |
} | |
} | |
// I manage the dot-grid. | |
function DotGridController( $scope, $timeout ) { | |
var vm = this; | |
// I hold the collection of rendered dots. | |
vm.dots = []; | |
// I keep track of the dimensions and cardinality of the grid. | |
var grid = { | |
columns: 0, | |
rows: 0, | |
width: 0, | |
height: 0 | |
}; | |
// Expose the public methods. | |
vm.addDot = addDot; | |
vm.setDimensions = setDimensions; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I add a dot at the given x/y pixel coordinates. | |
function addDot( x, y ) { | |
vm.dots.push({ | |
id: ( vm.dots.length + 1 ), | |
x: x, | |
y: y | |
}); | |
// CAUTION: This is the real point of coupling between the DOM | |
// and the controller. The reason we have to use the $timeout() | |
// here is because we want the initial x/y coordinates to render | |
// on the new dot before we adjust the layout to keep the dots | |
// evenly distributed. | |
// | |
// I can't think of a nicer way to do this. We either build the | |
// coupling into this method by way of the timeout; or, we let | |
// the link() function coordinate an explicit call to | |
// adjustLayout(), at which point, the two layers are still | |
// coupled because the controller has to know NOT to call | |
// adjustLayout(), even though it would make sense from a data | |
// point of view, in order to facilitate the animation. | |
$timeout( adjustLayout, 50 ); | |
} | |
// I set the new dimensions of the grid. | |
function setDimensions( width, height ) { | |
grid.width = width; | |
grid.height = height; | |
adjustLayout(); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
// I adjust the layout of the grid - both in column and row count as | |
// well as in spacing - to accommodate the current number of dots. | |
function adjustLayout() { | |
var maxCapacity = ( grid.rows * grid.columns ); | |
// Add columns or rows to be able to fit the dots. | |
if ( vm.dots.length > maxCapacity ) { | |
if ( ! grid.columns ) { | |
grid.columns = grid.rows = 1; | |
} else if ( grid.columns === grid.rows ) { | |
grid.columns++; | |
} else { | |
grid.rows++; | |
} | |
} | |
// Calculate the inter-column and inter-row spacing needed | |
// to keep the dots evenly distributed within the grid. | |
var columnSpacing = Math.floor( grid.width / ( grid.columns + 1 ) ); | |
var rowSpacing = Math.floor( grid.height / ( grid.rows + 1 ) ); | |
// Iterate over each dot and adjust the x/y coordinates to | |
// keep the dots evenly distributed. | |
for ( var i = 0, length = vm.dots.length ; i < length ; i++ ) { | |
var dot = vm.dots[ i ]; | |
var currentColumn = ( i % grid.columns ); | |
var currentRow = Math.floor( i / grid.columns ); | |
dot.x = ( columnSpacing * ( currentColumn + 1 ) ); | |
dot.y = ( rowSpacing * ( currentRow + 1 ) ); | |
} | |
} | |
} | |
} | |
); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment