Skip to content

Instantly share code, notes, and snippets.

@sebastianhenneberg
Created June 8, 2015 15:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sebastianhenneberg/acb0a5e7b0e63006c699 to your computer and use it in GitHub Desktop.
Save sebastianhenneberg/acb0a5e7b0e63006c699 to your computer and use it in GitHub Desktop.
Monkey patch for dropdownService of angular-ui/bootstrap to support nested dropdowns
(function() {
'use strict';
/**
* This monkey-patch for the dropdownService enables to nest dropdowns.
* Issue: https://github.com/angular-ui/bootstrap/issues/2421
* PR: https://github.com/angular-ui/bootstrap/pull/3776
*/
angular
.module('arctictenApp')
.config(dropdownServiceDecorator)
.service('nestedDropdownService', nestedDropdownService);
function dropdownServiceDecorator($provide) {
$provide.decorator('dropdownService', function($delegate, nestedDropdownService) {
return nestedDropdownService;
});
}
function nestedDropdownService($document, $rootScope) {
var openScopes = [];
this.open = function(dropdownScope) {
var upperScope = getUpperScope();
if (!upperScope) {
$document.bind('click', closeDropdown);
$document.bind('keydown', escapeKeyBind);
}
while(upperScope && upperScope !== dropdownScope) {
// contained dropdown, do not close dropdown
if (upperScope.getElement()[0].contains(dropdownScope.getElement()[0])) {
break;
}
openScopes.pop().isOpen = false;
upperScope = getUpperScope();
}
openScopes.push(dropdownScope);
};
this.close = function(dropdownScope) {
if (openScopes.indexOf(dropdownScope) > -1) {
var upperScope = getUpperScope();
while (upperScope) {
openScopes.pop().isOpen = false;
if (upperScope === dropdownScope) {
break;
}
}
}
if ( !getUpperScope() ) {
$document.unbind('click', closeDropdown);
$document.unbind('keydown', escapeKeyBind);
}
};
var getUpperScope = function() {
if (openScopes.length === 0) {
return null;
}
return openScopes[openScopes.length-1];
};
var closeDropdown = function(evt) {
var upperScope = getUpperScope();
while (upperScope) {
// This method may still be called during the same mouse event that
// unbound this event handler. So check upperScope before proceeding.
if (!upperScope) { break; }
if (evt && upperScope.getAutoClose() === 'disabled' ) { break; }
var toggleElement = upperScope.getToggleElement();
if (evt && toggleElement && toggleElement[0].contains(evt.target) ) {
break;
}
var $element = upperScope.getElement();
if (evt && upperScope.getAutoClose() === 'outsideClick' && $element && $element[0].contains(evt.target) ) {
break;
}
upperScope.isOpen = false;
openScopes.pop();
if (!$rootScope.$$phase) {
upperScope.$apply();
}
// just close upper dropdown if ESC was pressed
if (angular.isUndefined(evt)) {
break;
}
upperScope = getUpperScope();
}
};
var escapeKeyBind = function(evt) {
if (evt.which === 27) {
getUpperScope().focusToggleElement();
closeDropdown();
}
};
}
})();
@Mike-Loffland
Copy link

FYI... this doesn't work if minified.

Here is the same code with $inject property annotation included. The code below works with minification.

(function () {

    'use strict';

    /**
     * This monkey-patch for the dropdownService enables to nest dropdowns.
     * Issue: https://github.com/angular-ui/bootstrap/issues/2421
     * PR:    https://github.com/angular-ui/bootstrap/pull/3776
     */

    angular.module('Spr.Shared')
      .config(dropdownServiceDecorator)
      .service('nestedDropdownService', nestedDropdownService);

    function dropdownServiceDecorator($provide) {
        $provide.decorator('dropdownService', ['$delegate', 'nestedDropdownService', function ($delegate, nestedDropdownService) {
            return nestedDropdownService;
        }]);
    }

    dropdownServiceDecorator.$inject = ['$provide'];

    function nestedDropdownService($document, $rootScope) {
        var openScopes = [];

        this.open = function (dropdownScope) {
            var upperScope = getUpperScope();

            if (!upperScope) {
                $document.bind('click', closeDropdown);
                $document.bind('keydown', escapeKeyBind);
            }

            while (upperScope && upperScope !== dropdownScope) {
                // contained dropdown, do not close dropdown
                if (upperScope.getElement()[0].contains(dropdownScope.getElement()[0])) {
                    break;
                }

                openScopes.pop().isOpen = false;
                upperScope = getUpperScope();
            }

            openScopes.push(dropdownScope);
        };

        this.close = function (dropdownScope) {

            if (openScopes.indexOf(dropdownScope) > -1) {
                var upperScope = getUpperScope();
                while (upperScope) {
                    openScopes.pop().isOpen = false;
                    if (upperScope === dropdownScope) {
                        break;
                    }
                }
            }

            if (!getUpperScope()) {
                $document.unbind('click', closeDropdown);
                $document.unbind('keydown', escapeKeyBind);
            }
        };

        var getUpperScope = function () {
            if (openScopes.length === 0) {
                return null;
            }

            return openScopes[openScopes.length - 1];
        };

        var closeDropdown = function (evt) {
            if (evt.target.id != '_nci_') {
                var upperScope = getUpperScope();
                while (upperScope) {

                    // This method may still be called during the same mouse event that
                    // unbound this event handler. So check upperScope before proceeding.
                    if (!upperScope) { break; }

                    if (evt && upperScope.getAutoClose() === 'disabled') { break; }

                    var toggleElement = upperScope.getToggleElement();
                    if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
                        break;
                    }

                    var $element = upperScope.getElement();
                    if (evt && upperScope.getAutoClose() === 'outsideClick' && $element && $element[0].contains(evt.target)) {
                        break;
                    }

                    upperScope.isOpen = false;
                    openScopes.pop();

                    if (!$rootScope.$$phase) {
                        upperScope.$apply();
                    }

                    // just close upper dropdown if ESC was pressed
                    if (angular.isUndefined(evt)) {
                        break;
                    }

                    upperScope = getUpperScope();
                }
            }
        };

        var escapeKeyBind = function (evt) {
            if (evt.which === 27) {
                getUpperScope().focusToggleElement();
                closeDropdown();
            }
        };
    }

    nestedDropdownService.$inject = ['$document', '$rootScope'];

})();

@RazkoSA
Copy link

RazkoSA commented Nov 25, 2015

As an older programmer late to the party, the language in current programming is hilarious. I would never dream that saying I can monkey-patch looks good on a resume. I noticed that ui.bootstrap.dropdown is deprecated along with alot of angular things in Version: 0.14.2 - 2015-10-13. So are you going to monkey patch the uibDropdownService as well? Old green horn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment