Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Add an append-to-body attribute for improved layering. #718

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
132 changes: 132 additions & 0 deletions examples/demo-append-to-body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en" ng-app="demo">
<head>
<meta charset="utf-8">
<title>AngularJS ui-select</title>

<!--
IE8 support, see AngularJS Internet Explorer Compatibility http://docs.angularjs.org/guide/ie
For Firefox 3.6, you will also need to include jQuery and ECMAScript 5 shim
-->
<!--[if lt IE 9]>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/2.2.0/es5-shim.js"></script>
<script>
document.createElement('ui-select');
document.createElement('ui-select-match');
document.createElement('ui-select-choices');
</script>
<![endif]-->

<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular-sanitize.js"></script>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.css">

<!-- ui-select files -->
<script src="../dist/select.js"></script>
<link rel="stylesheet" href="../dist/select.css">

<script src="demo.js"></script>

<!-- Select2 theme -->
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/select2/3.4.5/select2.css">

<!--
Selectize theme
Less versions are available at https://github.com/brianreavis/selectize.js/tree/master/dist/less
-->
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.default.css">
<!-- <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.bootstrap2.css"> -->
<!-- <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.bootstrap3.css"> -->

<style>
body {
padding: 15px;
}

.select2 > .select2-choice.ui-select-match {
/* Because of the inclusion of Bootstrap */
height: 29px;
}

.selectize-control > .selectize-dropdown {
top: 36px;
}

/* Some additional styling to demonstrate that append-to-body helps achieve the proper z-index layering. */
.select-box {
background: #fff;
position: relative;
z-index: 1;
}
.alert-info.positioned {
margin-top: 1em;
position: relative;
z-index: 10000; // The select2 dropdown has a z-index of 9999
}
</style>
</head>

<body ng-controller="DemoCtrl">
<script src="demo.js"></script>

<button class="btn btn-default btn-xs" ng-click="enable()">Enable ui-select</button>
<button class="btn btn-default btn-xs" ng-click="disable()">Disable ui-select</button>
<button class="btn btn-default btn-xs" ng-click="appendToBodyDemo.startToggleTimer()"
ng-disabled="appendToBodyDemo.remainingTime">
{{ appendToBodyDemo.remainingTime ? 'Toggling in ' + (appendToBodyDemo.remainingTime / 1000) + ' seconds' : 'Toggle ui-select presence' }}
</button>
<button class="btn btn-default btn-xs" ng-click="clear()">Clear ng-model</button>

<div class="select-box" ng-if="appendToBodyDemo.present">
<h3>Bootstrap theme</h3>
<p>Selected: {{address.selected.formatted_address}}</p>
<ui-select ng-model="address.selected"
theme="bootstrap"
ng-disabled="disabled"
reset-search-input="false"
style="width: 300px;"
title="Choose an address">
<ui-select-match placeholder="Enter an address...">{{$select.selected.formatted_address}}</ui-select-match>
<ui-select-choices repeat="address in addresses track by $index"
append-to-body="true"
refresh="refreshAddresses($select.search)"
refresh-delay="0">
<div ng-bind-html="address.formatted_address | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<p class="alert alert-info positioned">The select dropdown menu should be displayed above this element.</p>
</div>

<div class="select-box" ng-if="appendToBodyDemo.present">
<h3>Select2 theme</h3>
<p>Selected: {{person.selected}}</p>
<ui-select ng-model="person.selected" theme="select2" ng-disabled="disabled" style="min-width: 300px;" title="Choose a person">
<ui-select-match placeholder="Select a person in the list or search his name/age...">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="person in people | propsFilter: {name: $select.search, age: $select.search}"
append-to-body="true">
<div ng-bind-html="person.name | highlight: $select.search"></div>
<small>
email: {{person.email}}
age: <span ng-bind-html="''+person.age | highlight: $select.search"></span>
</small>
</ui-select-choices>
</ui-select>
<p class="alert alert-info positioned">The select dropdown menu should be displayed above this element.</p>
</div>

<div class="select-box" ng-if="appendToBodyDemo.present">
<h3>Selectize theme</h3>
<p>Selected: {{country.selected}}</p>
<ui-select ng-model="country.selected" theme="selectize" ng-disabled="disabled" style="width: 300px;" title="Choose a country">
<ui-select-match placeholder="Select or search a country in the list...">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="country in countries | filter: $select.search"
append-to-body="true">
<span ng-bind-html="country.name | highlight: $select.search"></span>
<small ng-bind-html="country.code | highlight: $select.search"></small>
</ui-select-choices>
</ui-select>
<p class="alert alert-info positioned">The select dropdown menu should be displayed above this element.</p>
</div>
</body>
</html>
19 changes: 18 additions & 1 deletion examples/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ app.filter('propsFilter', function() {
};
});

app.controller('DemoCtrl', function($scope, $http, $timeout) {
app.controller('DemoCtrl', function($scope, $http, $timeout, $interval) {
$scope.disabled = undefined;
$scope.searchEnabled = undefined;

Expand Down Expand Up @@ -143,6 +143,23 @@ app.controller('DemoCtrl', function($scope, $http, $timeout) {
$scope.multipleDemo.selectedPeopleWithGroupBy = [$scope.people[8], $scope.people[6]];
$scope.multipleDemo.selectedPeopleSimple = ['[email protected]','[email protected]'];

$scope.appendToBodyDemo = {
remainingToggleTime: 0,
present: true,
startToggleTimer: function() {
var scope = $scope.appendToBodyDemo;
var promise = $interval(function() {
if (scope.remainingTime < 1000) {
$interval.cancel(promise);
scope.present = !scope.present;
scope.remainingTime = 0;
} else {
scope.remainingTime -= 1000;
}
}, 1000);
scope.remainingTime = 3000;
}
};

$scope.address = {};
$scope.refreshAddresses = function(address) {
Expand Down
5 changes: 3 additions & 2 deletions src/bootstrap/choices.tpl.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<ul class="ui-select-choices ui-select-choices-content dropdown-menu"
<ul class="ui-select-choices ui-select-choices-bootstrap ui-select-choices-content dropdown-menu"
style="display: block"
role="listbox"
ng-show="$select.items.length > 0">
<li class="ui-select-choices-group" id="ui-select-choices-{{ $select.generatedId }}" >
<div class="divider" ng-show="$select.isGrouped && $index > 0"></div>
<div ng-show="$select.isGrouped" class="ui-select-choices-group-label dropdown-header" ng-bind="$group.name"></div>
<div id="ui-select-choices-row-{{ $select.generatedId }}-{{$index}}" class="ui-select-choices-row"
<div id="ui-select-choices-row-{{ $select.generatedId }}-{{$index}}" class="ui-select-choices-row"
ng-class="{active: $select.isActive(this), disabled: $select.isDisabled(this)}" role="option">
<a href="javascript:void(0)" class="ui-select-choices-row-inner"></a>
</div>
Expand Down
13 changes: 7 additions & 6 deletions src/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
}

/* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */
.ui-select-bootstrap > .ui-select-choices {
.ui-select-choices-bootstrap {
width: 100%;
height: auto;
max-height: 200px;
Expand Down Expand Up @@ -163,7 +163,7 @@
border-right: 1px solid #428bca;
}

.ui-select-bootstrap .ui-select-choices-row>a {
.ui-select-choices-bootstrap .ui-select-choices-row>a {
display: block;
padding: 3px 20px;
clear: both;
Expand All @@ -173,21 +173,22 @@
white-space: nowrap;
}

.ui-select-bootstrap .ui-select-choices-row>a:hover, .ui-select-bootstrap .ui-select-choices-row>a:focus {
.ui-select-choices-bootstrap .ui-select-choices-row>a:hover,
.ui-select-choices-bootstrap .ui-select-choices-row>a:focus {
text-decoration: none;
color: #262626;
background-color: #f5f5f5;
}

.ui-select-bootstrap .ui-select-choices-row.active>a {
.ui-select-choices-bootstrap .ui-select-choices-row.active>a {
color: #fff;
text-decoration: none;
outline: 0;
background-color: #428bca;
}

.ui-select-bootstrap .ui-select-choices-row.disabled>a,
.ui-select-bootstrap .ui-select-choices-row.active.disabled>a {
.ui-select-choices-bootstrap .ui-select-choices-row.disabled>a,
.ui-select-choices-bootstrap .ui-select-choices-row.active.disabled>a {
color: #777;
cursor: not-allowed;
background-color: #fff;
Expand Down
22 changes: 21 additions & 1 deletion src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,25 @@ var uis = angular.module('ui.select', [])
return function(matchItem, query) {
return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
};
});
})

/**
* A read-only equivalent of jQuery's offset function: http://api.jquery.com/offset/
*
* Taken from AngularUI Bootstrap Position:
* See https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js#L70
*/
.factory('uisOffset',
['$document', '$window',
function ($document, $window) {

return function(element) {
var boundingClientRect = element[0].getBoundingClientRect();
return {
width: boundingClientRect.width || element.prop('offsetWidth'),
height: boundingClientRect.height || element.prop('offsetHeight'),
top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
};
};
}]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for keeping this lightweight 👍

52 changes: 50 additions & 2 deletions src/uiSelectChoicesDirective.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
uis.directive('uiSelectChoices',
['uiSelectConfig', 'RepeatParser', 'uiSelectMinErr', '$compile',
function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile) {
['uiSelectConfig', 'RepeatParser', 'uiSelectMinErr', 'uisOffset', '$compile', '$document',
function(uiSelectConfig, RepeatParser, uiSelectMinErr, uisOffset, $compile, $document) {

return {
restrict: 'EA',
Expand All @@ -21,6 +21,7 @@ uis.directive('uiSelectChoices',

// var repeat = RepeatParser.parse(attrs.repeat);
var groupByExp = attrs.groupBy;
var appendToBody = scope.$eval(attrs.appendToBody);

$select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult

Expand Down Expand Up @@ -60,6 +61,53 @@ uis.directive('uiSelectChoices',
var refreshDelay = scope.$eval(attrs.refreshDelay);
$select.refreshDelay = refreshDelay !== undefined ? refreshDelay : uiSelectConfig.refreshDelay;
});

if (appendToBody) {
scope.$watch('$select.open', function(isOpen) {
if (isOpen) {
positionDropdown();
} else {
resetDropdown();
}
});

// Move the dropdown back to its original location when the scope is destroyed. Otherwise
// it might stick around when the user routes away or the select field is otherwise removed
scope.$on('$destroy', function() {
resetDropdown();
});
}

// Hold on to a reference to the .ui-select-container element for appendToBody support
var parent = element.parent();

function positionDropdown() {
if (!appendToBody) {
return;
}

// Move the dropdown element to the end of the body
$document.find('body').append(element);

var position = uisOffset(parent);
element[0].style.left = position.left + 'px';
element[0].style.top = position.top + parent[0].offsetHeight + 'px';
element[0].style.width = parent[0].offsetWidth + 'px';
}

function resetDropdown() {
if (!appendToBody) {
return;
}

// Move the dropdown element back to its original location in the DOM
parent.append(element);

element[0].style.left = '';
element[0].style.top = '';
element[0].style.width = '';
}

};
}
};
Expand Down