From ae7828700ed05b65d8ebf4f8525977b7ac760741 Mon Sep 17 00:00:00 2001
From: Bryce Gilhome <me@bryce.com.au>
Date: Sat, 23 May 2015 11:25:22 +1000
Subject: [PATCH] Adding support for align option, auto-align to opposite
 direction if dropdown overflows off-screen

---
 src/dropdownToggle/dropdownToggle.js | 110 +++++++++++++++++++++++----
 1 file changed, 96 insertions(+), 14 deletions(-)

diff --git a/src/dropdownToggle/dropdownToggle.js b/src/dropdownToggle/dropdownToggle.js
index a3f2dc8..acdc734 100644
--- a/src/dropdownToggle/dropdownToggle.js
+++ b/src/dropdownToggle/dropdownToggle.js
@@ -47,14 +47,24 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f
         }
 
         if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
-          dropdown.css('display', 'block'); // We display the element so that offsetParent is populated
-          var offset = $position.offset(element);
-          var parentOffset = $position.offset(angular.element(dropdown[0].offsetParent));
-          var dropdownWidth = dropdown.prop('offsetWidth');
-          var css = {
-            top: offset.top - parentOffset.top + offset.height + 'px'
-          };
+          // Set default align to bottom
+          var align = 'bottom';
+          var position = 'absolute';
+          var css = {};
+
+          // Check data-options for alignment
+          var options = element.attr('data-options') ? element.attr('data-options').split(';') : [];
+          for (var i = 0; i < options.length; i++) {
+            var split = options[i].split(':');
+            if (split[0] == 'align') {
+              align = split[1];
+            }
+            if (split[0].trim() == 'position') {
+              position = split[1];
+            }
+          }
 
+          // Override positioning in some cases (small device)
           if (controller.small()) {
             css.left = Math.max((parentOffset.width - dropdownWidth) / 2, 8) + 'px';
             css.position = 'absolute';
@@ -62,17 +72,89 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f
             css['max-width'] = 'none';
           }
           else {
-            var left = Math.round(offset.left - parentOffset.left);
-            var rightThreshold = $window.innerWidth - dropdownWidth - 8;
-            if (left > rightThreshold) {
-                left = rightThreshold;
-                dropdown.removeClass('left').addClass('right');
+            // Set position = fixed if dropdown is fixed via CSS
+            if (dropdown.css('position') == 'fixed') {
+              position = 'fixed';
             }
-            css.left = left + 'px';
-            css.position = null;
+
+            // Get offsets
+            dropdown.css('display', 'block'); // We display the element so that offsetParent is populated
+            var offset = $position.offset(element);
+            if (position != 'fixed') {
+              var offsetParent = angular.element(dropdown[0].offsetParent);
+            } else {
+              // offsetParent doesn't always return body for some reason
+              var offsetParent = $('body');
+            }
+            var parentOffset = $position.offset(offsetParent);
+            var pipWidth = 8;
+            var dropdownWidth = dropdown.prop('offsetWidth') + pipWidth; // (for drop left/right)
+            var dropdownHeight = dropdown.prop('offsetHeight') + pipWidth; // (for drop top/bottom)
+
+            // Set css dependent on alignment
+            var done = false;
+            var tries = 0;
+            while (!done && tries < 2) {
+              tries++;
+              // Start from target element's top left and adjust from there
+              if (position != 'fixed') {
+                css.top = offset.top - parentOffset.top;
+                css.left = offset.left - parentOffset.left;
+              } else {
+                // this isn't exactly right ...
+                css.top = parentOffset.top;
+                css.left = offset.left;
+              }
+              switch (align) {
+                case 'top':
+                  css.top -= dropdownHeight;
+                  if (css.top < 0) {
+                    align = 'bottom';
+                  } else {
+                    done = true;
+                  }
+                  break;
+
+                case 'left':
+                  css.left -= dropdownWidth;
+                  if (css.left < 0) {
+                    align = 'right';
+                  } else {
+                    done = true;
+                  }
+                  break;
+
+                case 'right':
+                  css.left += offset.width + pipWidth;
+                  if (css.left + dropdownWidth > $window.innerWidth) {
+                    align = 'left';
+                  } else {
+                    done = true;
+                  }
+                  break;
+
+                case 'bottom':
+                  css.top += offset.height + pipWidth;
+                  if (css.top + dropdownHeight > $window.innerHeight) {
+                    align = 'top';
+                  } else {
+                    done = true;
+                  }
+                  break;
+              }
+            }
+
+            css.top = css.top + 'px';
+            css.left = css.left + 'px';
+            css.position = position;
             css['max-width'] = null;
           }
 
+          var aligns = ['top', 'left', 'bottom', 'right'];
+          aligns.forEach(function(el) {
+            dropdown.removeClass('drop-' + el);
+          });
+          dropdown.addClass('drop-' + align);
           dropdown.css(css);
           element.addClass('expanded');