Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dropdownToggle): Prevent dropdown from closing automatically #123

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d5d32ae
fix(modal): Make off screen modals accessible
circlingthesun Aug 12, 2014
1304c91
fix(modal): Removed redundant offset
circlingthesun Aug 18, 2014
290de12
fix(modal): Revert the conditional 100px offset.
circlingthesun Aug 18, 2014
b622269
chore(docs): Add badges to README
jbrowning Aug 27, 2014
9a60585
fix(modal): Dynamically determine the margin
circlingthesun Aug 27, 2014
88d7039
fix(modal): Focus the correct element
circlingthesun Aug 28, 2014
b60df2c
fix(interchange): Support IE9 via matchMedia shim
nervetattoo Sep 5, 2014
ac41e65
feat(stylesheets): Enable modifying head based stylesheets
nervetattoo Sep 23, 2014
052aa2f
feat(stylesheets): Support removing rules + from DOM when empty
nervetattoo Sep 23, 2014
d7019dc
style(stylesheets): Missing if-else-braces
nervetattoo Sep 24, 2014
74c1f63
chore(stylesheets): Unbroke grunt build
nervetattoo Sep 24, 2014
701a625
feat(dropdownToggle): Place pip relative to center of initiator
nervetattoo Sep 24, 2014
229042c
fix(dropdownToggle): Pip :after position off-by-one
nervetattoo Sep 24, 2014
eb3c38c
feat(dropdownToggle): Not close content dropdown when clicking on it
hainp2604 Sep 30, 2014
eae98b6
Merge remote-tracking branch 'origin/master'
hainp2604 Oct 19, 2014
9110de7
Merge remote-tracking branch 'origin/master'
hainp2604 Oct 19, 2014
606ca4f
fix(dropdownToggle): Check attribute instead of HTML class
hainp2604 Oct 19, 2014
4806193
Merge remote-tracking branch 'origin/master'
hainp2604 Nov 8, 2014
ebfff42
fix(dropdownToggle): Keep bubling event
hainp2604 Nov 8, 2014
a066f9b
Merge upstream master
hainp2604 Dec 26, 2014
413bd85
fix(dropdownToggle): Duplicate code when handling click
hainp2604 Dec 26, 2014
72425f5
Merge remote-tracking branch 'upstream/master'
hainp2604 Mar 4, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 62 additions & 18 deletions src/dropdownToggle/dropdownToggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@
</li>
</ul>
*/
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() {
return mediaQueries.small() && !mediaQueries.medium();
};
}])

.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: {
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -75,35 +78,76 @@ 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();
});
}
};

if (dropdown) {
dropdown.css('display', 'none');
}

scope.$watch('$location.path', function() { closeMenu(); });
scope.$watch('$location.path', function() {
closeMenu();
});

element.on('click', onClick);
element.on('$destroy', function() {
element.off('click', onClick);
});
}
};
}]);
}]);
27 changes: 24 additions & 3 deletions src/dropdownToggle/test/dropdownToggle.spec.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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();
}));

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions src/stylesheets/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stylesheets provides the `stylesheetFactory` helper to manipulate *<head>* based stylesheets. It'll allow you to easily inject a new stylesheet and give it a sensible API.
71 changes: 71 additions & 0 deletions src/stylesheets/stylesheets.js
Original file line number Diff line number Diff line change
@@ -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));
}
};
};
}]);
42 changes: 42 additions & 0 deletions src/stylesheets/test/stylesheets.spec.js
Original file line number Diff line number Diff line change
@@ -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('');
});
});