Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
AngularJS 
for Interactive Application Behavior 
Brent Goldstein 
brent@kubilateam.com 
Practical Directives
Back Story 
● Building an app and want rich behavior 
○ Don’t limit goals because “it’s a web app” 
● Need an interactive ...
Requirements - (be a product mgr) 
● Hierarchical Data Set 
o 3 Row Levels - Person, Projects, Tasks 
o Columns are calend...
How to Render Complex Data? 
● Resist thinking like an ‘engineer’ 
● Visual representation must be intuitive 
● Even more ...
Digression - Dogfooding? 
● OK, this is one of many overused tech industry terms 
● Seems to put a rather unappetizing spi...
Grid Hierarchy Rendering 
● Go “Flat” -- flat visual data modeling that is 
● Basically, de-normalize where it makes sense...
So how to build it? 
<div grid-editor> 
<div class=’grid-person’> 
div div div div div div ….. div 
….. 
….. 
<div> 
….. 
...
DOM Location and Navigation 
Let’s consider one use case, Find the Next cell to the RIGHT 
<div grid-editor> 
<div> 
<div ...
Getting down to Business -- jqLite 
Assuming we know the selected cell, we can find the next like this: 
<div grid-editor>...
Going down, same column, new row 
What about other cases, like the down arrow key? 
Need the next row, but column needs to...
How to capture the arrow keys? 
Can be useful to step outside of Angular for event handling 
element.bind('keydown keypres...
Rendering the Selection Band 
To indicate selected cell, a “rectangle select band” 
is typically used to surround the cell...
Draw band using actual grid cells? 
Altering the display of underlying cells can be tricky. 
How to actually make the high...
Overlay Rendering 
What about creating a new element/set of 
elements that display ON TOP of the grid? 
Let’s timewarp bac...
The Magic Select Rectangle Band 
How to render the select rectangle in DOM? 
We’ll construct with separate banding element...
Dynamic Element Creation 
The select band is managed by the GridEditor Directive: 
- Create: When the directive is instant...
Dynamically alter DOM properties 
Properties are updated to adjust the rectangle band size 
when needed 
Note, _unitWidth ...
Let’s take a look - Initial View 
Top-level view, summary data 
User sees this view when page is loaded 
● Threshold color...
Let’s take a look - Expanded View 
Details for each person visible when expanded 
● Project-task rows at same indent level...
Let’s take a look - Editing 
Behaviors to streamline complex editing needs 
● Single Select and Range select 
● Smart Navi...
Coding tidbit - $emit for messaging 
Both $scope.$broadcast() and $scope.$emit() can be used 
to communicate between direc...
$emit for messaging 
Message bus approach can often replace $broadcast 
1) Bubble Up 2) Message Bus 
Higher level controll...
thanks! 
contact: brent@kubilateam.com
Upcoming SlideShare
Loading in …5
×

Angular.js Directives for Interactive Web Applications

How to build an interactive hierarchical data-grid using custom directives.
Shows how to capture keyboard input, navigate the DOM tree with jqLite and display google spreadsheet like selection rectangles.

Angular.js Directives for Interactive Web Applications

  1. 1. AngularJS for Interactive Application Behavior Brent Goldstein brent@kubilateam.com Practical Directives
  2. 2. Back Story ● Building an app and want rich behavior ○ Don’t limit goals because “it’s a web app” ● Need an interactive Spreadsheet-like Grid ○ Hierarchical data, specialized data entry needs ○ Similarities to Google Sheets, some differences ● Yikes, this seemed daunting at first ○ Hierarchical Data Binding ○ Navigation ○ Complex element/range selection
  3. 3. Requirements - (be a product mgr) ● Hierarchical Data Set o 3 Row Levels - Person, Projects, Tasks o Columns are calendar weeks ● Navigate and Select like a Spreadsheet o Move w/ arrows, tab, shift-tab; mouse-click o Select single cell or range of cells ● Specialized Editing o Business logic applied to keystrokes before display
  4. 4. How to Render Complex Data? ● Resist thinking like an ‘engineer’ ● Visual representation must be intuitive ● Even more important that ‘being’ intuitive → will someone actually use it? ● Really must pass the dog food test
  5. 5. Digression - Dogfooding? ● OK, this is one of many overused tech industry terms ● Seems to put a rather unappetizing spin on one of the MOST IMPORTANT practices in application design ● It does not matter what you think or say, or what others think or say about using your app ● The application must draw you in (yes, YOU) ● Otherwise, it’s just neat tech, and neat is not a product
  6. 6. Grid Hierarchy Rendering ● Go “Flat” -- flat visual data modeling that is ● Basically, de-normalize where it makes sense → use grouping to organize ● Greatly simplifies UI construction and interpretation ● Still possible to “drill down”, but using filters and selection to show more/fewer rows ● Avoid “nested grids”; hard to render, hard to read
  7. 7. So how to build it? <div grid-editor> <div class=’grid-person’> div div div div div div ….. div ….. ….. <div> ….. <div> <div class=’grid-person’> <div> - Custom Directive “grid-editor” provides special behaviors - Grid-Editor looks for child DOM elements with special classes Note, grid constructed from divs, not tables, to support custom layout
  8. 8. DOM Location and Navigation Let’s consider one use case, Find the Next cell to the RIGHT <div grid-editor> <div> <div class=’grid-person’> <div> ... <div class=’grid-project’> <div> <div class=’grid-project’> <div> 1) At last cell, but not last in row 2) At last cell in row 3) At last cell/row in grid-project Actually requires handling several sub-cases...
  9. 9. Getting down to Business -- jqLite Assuming we know the selected cell, we can find the next like this: <div grid-editor> <div class=’grid-person’> <div class=’grid-project’> 1) At last cell, but not last in row col=’1’ col=’2’ col=’3’ col=’4’ col=’5’ ... 2) At last cell in row Case 1) Look for next sibling element, use attribute to be selective var newElement = currEle.next(‘[col]’); Case 2) Look for the FIRST element in the NEXT row (requires some maneuvering) var newElement = currEle.closest(‘grid-project’).next(‘grid-project’).find(‘.first’) Similar ‘traverse up, look for next’ can be applied to higher levels in hierarchy col=’6’ applied applied to first cell div: class=’first’ to every cell div: class=’cell’
  10. 10. Going down, same column, new row What about other cases, like the down arrow key? Need the next row, but column needs to remain the same. Now that’s why the “col=n” attribute is useful…. Look for next sibling row, use attribute to find the correct column var newElement = currEle.closest(‘grid-project’).next(‘grid-project’) .find(‘.cell[col=’ + currEle.attr(‘col’) + ‘]’); <div grid-editor> <div class=’grid-person’> <div class=’grid-project’> col=’1’ col=’2’ col=’3’ col=’4’ col=’5’ col=’6’ col=’4’
  11. 11. How to capture the arrow keys? Can be useful to step outside of Angular for event handling element.bind('keydown keypress', function (e){ if(!scope.editActive) return; // track the editing state var navArrow = function(e){ var arrowKeys = { 37:'left', 38:'up', 39:'right', 40:'down' }; return arrowKeys[e.which]; }; var navTab = function(e){ // tab key return (e.which === 9) ? (e.shiftKey?"left":"right") : null; }; var doNav = navFromArrow(e) || navFromTab(e); if(doNav){ e.preventDefault(); // prevent the browser default action theFunctionThatNavigatesToNextCell(doNav) } }; The ‘element’ above could be the $document root, or a sub-document, depending on the desired behavior. If a sub-doc, the keys will only be captured when that element or children have focus
  12. 12. Rendering the Selection Band To indicate selected cell, a “rectangle select band” is typically used to surround the cells How to accomplish this in DOM? A couple of options: 1) Identify and alter the display the underlying DIVs 2) Add new elements that Overlay the native grid
  13. 13. Draw band using actual grid cells? Altering the display of underlying cells can be tricky. How to actually make the highlight band? - Alter the div/span border(s) Not ideal. Positioning is limited to element border Also harder to make a multi-cell rectangle
  14. 14. Overlay Rendering What about creating a new element/set of elements that display ON TOP of the grid? Let’s timewarp back to 1998 when graphics were rendered into framebuffers. Remember the Overlay Framebuffer? Let’s apply the same logical concept
  15. 15. The Magic Select Rectangle Band How to render the select rectangle in DOM? We’ll construct with separate banding elements for max control It will simplify hover/select options since we can know exactly which side of the band is under the mouse Top DIV Bottom DIV Left Div Right Div With this approach we can also add other visual elements, like a cursor select target for dragging, etc.
  16. 16. Dynamic Element Creation The select band is managed by the GridEditor Directive: - Create: When the directive is instantiated - Move: When the arrow/tab keys are captured - Grow/Shrink: Based on shift-click This function is called inside the directive link() function to instantiate the band elements: var _installBand = function(){ var html = "<div id='magic-square'>"+ "<div class='band-horiz top'></div>" + "<div class='band-horiz bottom'></div>"+ "<div class='band-vert left'></div>"+ "<div class='band-vert right'></div>" + "<div class='band-grabbox'></div>" + "</div>"; _band = angular.element(html); // this creates the dom element _gridEditorElement.prepend(_band); // attach to directive element return _band; // to adjust properties and location later };
  17. 17. Dynamically alter DOM properties Properties are updated to adjust the rectangle band size when needed Note, _unitWidth is already set with the pixel width of a single cell var _setBandWidth = function(){ var rangeWidth = _range(); // gets the desired #cells to cover // update the top/bottom horizontal div widths _band.children(".band-horiz").width((_unitWidth-1) * rangeWidth); // update the right vertical div position var rightVert = _magicSquare.children(".band-vert.right"); var posLeft = (_unitWidth-1) * rangeWidth -1; rightVert.css({left: posLeft}); };
  18. 18. Let’s take a look - Initial View Top-level view, summary data User sees this view when page is loaded ● Threshold coloring (red means too much) ● Click on row to expand ● Expand All/Collapse All buttons
  19. 19. Let’s take a look - Expanded View Details for each person visible when expanded ● Project-task rows at same indent level ● Grouping by project ● Optional project sub-total rows
  20. 20. Let’s take a look - Editing Behaviors to streamline complex editing needs ● Single Select and Range select ● Smart Navigation across projects/people ● Shortcut keys for pre-defined operations
  21. 21. Coding tidbit - $emit for messaging Both $scope.$broadcast() and $scope.$emit() can be used to communicate between directives and controllers What’s the difference? Well, direction: $emit goes UP, $broadcast goes DOWN As a result, $emit is more efficient bubbling only to parents $broadcast fans out to all child scopes Maybe you really need $broadcast... but often not $emit can be applied in a message-bus pattern to achieve efficient dedicated communication across an app
  22. 22. $emit for messaging Message bus approach can often replace $broadcast 1) Bubble Up 2) Message Bus Higher level controller or directive $scope.$on(‘hello.world’, function(event){//dosomething)); $scope.$emit(‘hello.world’); Originating Controller or Directive RootScope actiing as message Bus $rootScope.$on(‘hello.world’, function(event){//dosomething)); $rootScope.$emit(‘hello.world’); Originating Controller or Directive Listening Directive or Controller
  23. 23. thanks! contact: brent@kubilateam.com

×