diff --git a/src/dropdownToggle/dropdownToggle.js b/src/dropdownToggle/dropdownToggle.js index ab7be56..161c1f4 100644 --- a/src/dropdownToggle/dropdownToggle.js +++ b/src/dropdownToggle/dropdownToggle.js @@ -10,7 +10,9 @@ */ -angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.foundation.mediaQueries' ]) +angular.module('mm.foundation.dropdownToggle', ['mm.foundation.position', 'mm.foundation.mediaQueries', + 'mm.foundation.stylesheets' +]) .controller('DropdownToggleController', ['$scope', '$attrs', 'mediaQueries', function($scope, $attrs, mediaQueries) { this.small = function() { @@ -18,9 +20,10 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f }; }]) -.directive('dropdownToggle', ['$document', '$window', '$location', '$position', function ($document, $window, $location, $position) { +.directive('dropdownToggle', ['$document', '$window', '$location', '$position', 'stylesheetFactory', function($document, + $window, $location, $position, stylesheetFactory) { var openElement = null, - closeMenu = angular.noop; + closeMenu = angular.noop; return { restrict: 'CA', scope: { @@ -30,12 +33,13 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f link: function(scope, element, attrs, controller) { var parent = element.parent(); var dropdown = angular.element($document[0].querySelector(scope.dropdownToggle)); + var sheet = stylesheetFactory(); var parentHasDropdown = function() { return parent.hasClass('has-dropdown'); }; - var onClick = function (event) { + var onClick = function(event) { dropdown = angular.element($document[0].querySelector(scope.dropdownToggle)); var elementWasOpen = (element === openElement); @@ -60,13 +64,12 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f css.position = 'absolute'; css.width = '95%'; css['max-width'] = 'none'; - } - else { + } 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'); + left = rightThreshold; + dropdown.removeClass('left').addClass('right'); } css.left = left + 'px'; css.position = null; @@ -75,22 +78,61 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f dropdown.css(css); + var dropdownLeft = $position.offset(dropdown).left; + var pipWidth = parseInt( + getComputedStyle(dropdown[0], '::before').getPropertyValue('width'), 10 + ); + var pipLeft = offset.left - dropdownLeft + Math.round((offset.width - pipWidth) / 2); + sheet + .css('#' + dropdown[0].id + '::before', { + left: pipLeft + 'px' + }) + .css('#' + dropdown[0].id + '::after', { + left: pipLeft - 1 + 'px' + }) + .sync(); + if (parentHasDropdown()) { parent.addClass('hover'); } openElement = element; - - closeMenu = function (event) { - $document.off('click', closeMenu); - dropdown.css('display', 'none'); - closeMenu = angular.noop; - openElement = null; - if (parent.hasClass('hover')) { - parent.removeClass('hover'); + var shouldUnbind = true; + closeMenu = function(event) { + if (shouldUnbind) { + $document.off('click', closeMenu); + dropdown.css('display', 'none'); + closeMenu = angular.noop; + openElement = null; + if (parent.hasClass('hover')) { + parent.removeClass('hover'); + } } + shouldUnbind = true; }; + $document.on('click', closeMenu); + + if (dropdown.attr('show-on-click')) { + dropdown.bind('click', function(evt) { + shouldUnbind = false; + dropdown.css('display', 'block'); + }); + } + + var closeButton = angular.element($document[0].querySelector('.close.button')); + closeButton.bind('click', function(e) { + shouldUnbind = true; + dropdown.unbind('click'); + closeMenu(); + }); + + var sendButton = angular.element($document[0].querySelector('.send.button')); + sendButton.bind('click', function(e) { + shouldUnbind = true; + dropdown.unbind('click'); + closeMenu(); + }); } }; @@ -98,7 +140,9 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f dropdown.css('display', 'none'); } - scope.$watch('$location.path', function() { closeMenu(); }); + scope.$watch('$location.path', function() { + closeMenu(); + }); element.on('click', onClick); element.on('$destroy', function() { @@ -106,4 +150,4 @@ angular.module('mm.foundation.dropdownToggle', [ 'mm.foundation.position', 'mm.f }); } }; -}]); +}]); \ No newline at end of file diff --git a/src/dropdownToggle/test/dropdownToggle.spec.js b/src/dropdownToggle/test/dropdownToggle.spec.js index c46750c..5047d16 100644 --- a/src/dropdownToggle/test/dropdownToggle.spec.js +++ b/src/dropdownToggle/test/dropdownToggle.spec.js @@ -1,5 +1,5 @@ describe('dropdownToggle', function() { - var $compile, $rootScope, $document, $location, $window, elm, toggleElm, targetElm; + var $compile, $rootScope, $document, $location, $window, elm, toggleElm, targetElm, $position; function dropdown(id) { if (!id) { @@ -20,12 +20,13 @@ describe('dropdownToggle', function() { beforeEach(module('mm.foundation.dropdownToggle')); - beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _$location_, _$window_) { + beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _$location_, _$window_, _$position_) { $compile = _$compile_; $rootScope = _$rootScope_; $document = _$document_; $window = _$window_; $location = _$location_; + $position = _$position_; $scope = $rootScope.$new(); })); @@ -53,6 +54,12 @@ describe('dropdownToggle', function() { elm.click(); expect(targetElm.css('display')).toBe('none'); }); + + it('should not close on click if elm has show-on-click attribute', function() { + targetElm.attr('show-on-click', ''); + toggleElm.click(); + expect(targetElm.css('display')).toBe('block'); + }); it('should close on document click', function() { toggleElm.click(); @@ -91,19 +98,33 @@ describe('dropdownToggle', function() { return {small: trueFn, medium: falseFn, large: falseFn }; }); - it('should be full-width', function() { + beforeEach(function() { elm = dropdown('responsive'); toggleElm = elm.find('a'); targetElm = elm.find('ul'); toggleElm.click(); + }); + it('should be full-width', function() { expect(targetElm.css('position')).toBe('absolute'); expect(targetElm.css('max-width')).toBe('none'); var expectedWidth = Math.round($window.innerWidth * 0.95); expect(targetElm.css('width')).toBe(expectedWidth + 'px'); }); + + it('should position pip', function() { + var offset = $position.offset(toggleElm); + var targetLeftOffset = parseInt($position.offset(targetElm).left, 10); + + // Find whatever left offset it should have + var styles = getComputedStyle(targetElm[0], '::before'); + var left = styles.getPropertyValue('left'); + var pipWidth = styles.getPropertyValue('width').slice(0, -2); + var expectedLeft = Math.round((offset.width - pipWidth) / 2, 10) + offset.left - targetLeftOffset; + expect(left).toBe(expectedLeft + 'px'); + }); }); describe('when the parent element has a "has-dropdown" class', function() { diff --git a/src/stylesheets/readme.md b/src/stylesheets/readme.md new file mode 100644 index 0000000..3ad55c2 --- /dev/null +++ b/src/stylesheets/readme.md @@ -0,0 +1 @@ +Stylesheets provides the `stylesheetFactory` helper to manipulate ** based stylesheets. It'll allow you to easily inject a new stylesheet and give it a sensible API. diff --git a/src/stylesheets/stylesheets.js b/src/stylesheets/stylesheets.js new file mode 100644 index 0000000..02c866e --- /dev/null +++ b/src/stylesheets/stylesheets.js @@ -0,0 +1,71 @@ +/* + * stylesheets - Manipulate sheets in style + * @example: + + function(stylesheetFactory) { + var element = angular.element(document.head).find('styles'); + stylesheetFactory(element).css('#myid:before', { + 'background-color': 'red', + width: '500px' + }); + } + */ +angular.module('mm.foundation.stylesheets', []) + +.factory('stylesheetFactory', ['$document', function ($document) { + var rulesAsTextContent = function(rules) { + var textContent = ''; + for (var selector in rules) { + var props = rules[selector]; + textContent += selector + ' {\n'; + for (var prop in props) { + textContent += '\t' + prop + ': ' + props[prop] + ';\n'; + } + textContent += '}\n'; + } + return textContent.slice(0, -1); + }; + return function Stylesheet(element) { + var $head = angular.element($document[0].querySelector('head')); + if (!element) { + element = $document[0].createElement('style'); + element = angular.element(element); + } + var currentContent = element.text(); + var write = function(textContent) { + if (textContent !== currentContent) { + currentContent = textContent; + element.text(textContent); + if (currentContent === '') { + element.remove(); + } + else if (!$head[0].contains(element[0])) { + $head.append(element); + } + } + }; + + var rules = {}; + return { + element: function() { return element; }, + css: function(selector, content) { + var exists = selector in rules; + if (typeof content === 'undefined') { + return exists ? rules[selector] : null; + } + if (!exists || content != rules[selector]) { + if (content === null) { + delete rules[selector]; + } + else { + rules[selector] = content; + } + } + return this; + }, + sync: function() { + write(rulesAsTextContent(rules)); + } + }; + }; + }]); diff --git a/src/stylesheets/test/stylesheets.spec.js b/src/stylesheets/test/stylesheets.spec.js new file mode 100644 index 0000000..4b290b4 --- /dev/null +++ b/src/stylesheets/test/stylesheets.spec.js @@ -0,0 +1,42 @@ +describe('stylesheets', function() { + var $compile, $rootScope, $document, stylesheetFactory; + + beforeEach(module('mm.foundation.stylesheets')); + + beforeEach(inject(function(_$compile_, _$rootScope_, _$document_, _stylesheetFactory_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $document = _$document_; + stylesheetFactory = _stylesheetFactory_; + $scope = $rootScope.$new(); + })); + + it('should create and inject new stylesheet', function() { + var sheetId = 'stylesheets-test'; + var selector = '#id::before'; + var prop = 'color'; + var value = 'red'; + + var content = {}; + content[prop] = value; + + var sheet = stylesheetFactory(); + sheet.element().attr('id', sheetId); + + var cssEquals = function(expected) { + var $sheet = $document.find('#' + sheetId); + expect($sheet.text()).toEqual(expected); + expect(sheet.element().text()).toEqual(expected); + }; + + sheet.css(selector, content).sync(); + cssEquals('#id::before {\n\tcolor: red;\n}'); + + content[prop] = 'green'; + sheet.css(selector, content).sync(); + cssEquals('#id::before {\n\tcolor: green;\n}'); + + sheet.css(selector, null).sync(); + cssEquals(''); + }); +});