-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Selectable list directive #6630
Changes from all commits
9c7c4d9
1aab5e0
8455d56
3aa06ab
041c2f6
6939960
27c6f17
ec68aa0
1ff22b1
242fe85
420c1bc
00f5527
b968b0b
1dc9a18
149cc2e
08bae15
f458af2
66a8c7f
2992a66
d90baae
8f1f209
3640960
73a8adb
6f0892d
00c3f21
98003ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
import angular from 'angular'; | ||
import expect from 'expect.js'; | ||
import ngMock from 'ng_mock'; | ||
import _ from 'lodash'; | ||
|
||
var objectList = [ | ||
{ title: 'apple' }, | ||
{ title: 'orange' }, | ||
{ title: 'coconut' }, | ||
{ title: 'banana' }, | ||
{ title: 'grapes' } | ||
]; | ||
|
||
var stringList = [ | ||
'apple', | ||
'orange', | ||
'coconut', | ||
'banana', | ||
'grapes' | ||
]; | ||
|
||
var lists = [objectList, stringList, []]; | ||
|
||
var $scope; | ||
var $element; | ||
var $isolatedScope; | ||
|
||
lists.forEach(function (list) { | ||
var isArrayOfObjects = list.every((item) => { | ||
return _.isPlainObject(item); | ||
}); | ||
|
||
var init = function (arr, willFail) { | ||
// Load the application | ||
ngMock.module('kibana'); | ||
|
||
// Create the scope | ||
ngMock.inject(function ($rootScope, $compile) { | ||
$scope = $rootScope.$new(); | ||
$scope.perPage = 5; | ||
$scope.list = list; | ||
$scope.listProperty = isArrayOfObjects ? 'title' : undefined; | ||
$scope.test = function (val) { return val; }; | ||
|
||
// Create the element | ||
if (willFail) { | ||
$element = angular.element('<paginated-selectable-list per-page="perPage" list="list"' + | ||
'list-property="listProperty" user-make-url="test" user-on-select="test"></paginated-selectable-list>'); | ||
} else { | ||
$element = angular.element('<paginated-selectable-list per-page="perPage" list="list"' + | ||
'list-property="listProperty" user-make-url="test"></paginated-selectable-list>'); | ||
} | ||
|
||
// And compile it | ||
$compile($element)($scope); | ||
|
||
// Fire a digest cycle | ||
$element.scope().$digest(); | ||
|
||
// Grab the isolate scope so we can test it | ||
$isolatedScope = $element.isolateScope(); | ||
}); | ||
}; | ||
|
||
describe('paginatedSelectableList', function () { | ||
it('should throw an error when there is no makeUrl and onSelect attribute', ngMock.inject(function ($compile, $rootScope) { | ||
function errorWrapper() { | ||
$compile(angular.element('<paginated-selectable-list></paginated-selectable-list>'))($rootScope.new()); | ||
} | ||
expect(errorWrapper).to.throwError(); | ||
})); | ||
|
||
it('should throw an error with both makeUrl and onSelect attributes', function () { | ||
function errorWrapper() { | ||
init(list, true); | ||
} | ||
expect(errorWrapper).to.throwError(); | ||
}); | ||
|
||
describe('$scope.hits', function () { | ||
beforeEach(function () { | ||
init(list); | ||
}); | ||
|
||
it('should initially sort an array of objects in ascending order', function () { | ||
var property = $isolatedScope.listProperty; | ||
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list); | ||
|
||
expect($isolatedScope.hits).to.be.an('array'); | ||
|
||
$isolatedScope.hits.forEach(function (hit, index) { | ||
if (property) { | ||
expect(hit[property]).to.equal(sortedList[index][property]); | ||
} else { | ||
expect(hit).to.equal(sortedList[index]); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
describe('$scope.sortHits', function () { | ||
beforeEach(function () { | ||
init(list); | ||
}); | ||
|
||
it('should sort an array of objects in ascending order', function () { | ||
var property = $isolatedScope.listProperty; | ||
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list); | ||
|
||
$isolatedScope.isAscending = false; | ||
$isolatedScope.sortHits(list); | ||
|
||
expect($isolatedScope.isAscending).to.be(true); | ||
|
||
$isolatedScope.hits.forEach(function (hit, index) { | ||
if (property) { | ||
expect(hit[property]).to.equal(sortedList[index][property]); | ||
} else { | ||
expect(hit).to.equal(sortedList[index]); | ||
} | ||
}); | ||
}); | ||
|
||
it('should sort an array of objects in descending order', function () { | ||
var property = $isolatedScope.listProperty; | ||
var reversedList = property ? _.sortBy(list, property).reverse() : _.sortBy(list).reverse(); | ||
|
||
$isolatedScope.isAscending = true; | ||
$isolatedScope.sortHits(list); | ||
|
||
expect($isolatedScope.isAscending).to.be(false); | ||
|
||
$isolatedScope.hits.forEach(function (hit, index) { | ||
if (property) { | ||
expect(hit[property]).to.equal(reversedList[index][property]); | ||
} else { | ||
expect(hit).to.equal(reversedList[index]); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
describe('$scope.makeUrl', function () { | ||
beforeEach(function () { | ||
init(list); | ||
}); | ||
|
||
it('should return the result of the function its passed', function () { | ||
var property = $isolatedScope.listProperty; | ||
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list); | ||
|
||
$isolatedScope.hits.forEach(function (hit, index) { | ||
if (property) { | ||
expect($isolatedScope.makeUrl(hit)[property]).to.equal(sortedList[index][property]); | ||
} else { | ||
expect($isolatedScope.makeUrl(hit)).to.equal(sortedList[index]); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
describe('$scope.onSelect', function () { | ||
beforeEach(function () { | ||
init(list); | ||
}); | ||
|
||
it('should return the result of the function its passed', function () { | ||
var property = $isolatedScope.listProperty; | ||
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list); | ||
|
||
$isolatedScope.userOnSelect = function (val) { return val; }; | ||
|
||
$isolatedScope.hits.forEach(function (hit, index) { | ||
if (property) { | ||
expect($isolatedScope.onSelect(hit)[property]).to.equal(sortedList[index][property]); | ||
} else { | ||
expect($isolatedScope.onSelect(hit)).to.equal(sortedList[index]); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -194,5 +194,3 @@ uiModules.get('kibana') | |
template: paginateControlsTemplate | ||
}; | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import _ from 'lodash'; | ||
import uiModules from 'ui/modules'; | ||
import paginatedSelectableListTemplate from 'ui/partials/paginated_selectable_list.html'; | ||
|
||
const module = uiModules.get('kibana'); | ||
|
||
function throwError(message) { | ||
throw new Error(message); | ||
} | ||
|
||
module.directive('paginatedSelectableList', function (kbnUrl) { | ||
|
||
return { | ||
restrict: 'E', | ||
scope: { | ||
perPage: '=?', | ||
list: '=', | ||
listProperty: '=', | ||
userMakeUrl: '=?', | ||
userOnSelect: '=?' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a check in the controller to see if |
||
}, | ||
template: paginatedSelectableListTemplate, | ||
controller: function ($scope, $element, $filter) { | ||
// Should specify either user-make-url or user-on-select | ||
if (!$scope.userMakeUrl && !$scope.userOnSelect) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't check if they're both specified? |
||
throwError('paginatedSelectableList directive expects a makeUrl or onSelect function'); | ||
} | ||
|
||
// Should specify either user-make-url or user-on-select, but not both. | ||
if ($scope.userMakeUrl && $scope.userOnSelect) { | ||
throwError('paginatedSelectableList directive expects a makeUrl or onSelect attribute but not both'); | ||
} | ||
|
||
$scope.perPage = $scope.perPage || 10; | ||
$scope.hits = $scope.list = _.sortBy($scope.list, accessor); | ||
$scope.hitCount = $scope.hits.length; | ||
|
||
/** | ||
* Boolean that keeps track of whether hits are sorted ascending (true) | ||
* or descending (false) | ||
* * @type {Boolean} | ||
*/ | ||
$scope.isAscending = true; | ||
|
||
/** | ||
* Sorts saved object finder hits either ascending or descending | ||
* @param {Array} hits Array of saved finder object hits | ||
* @return {Array} Array sorted either ascending or descending | ||
*/ | ||
$scope.sortHits = function (hits) { | ||
const sortedList = _.sortBy(hits, accessor); | ||
|
||
$scope.isAscending = !$scope.isAscending; | ||
$scope.hits = $scope.isAscending ? sortedList : sortedList.reverse(); | ||
}; | ||
|
||
$scope.makeUrl = function (hit) { | ||
return $scope.userMakeUrl(hit); | ||
}; | ||
|
||
$scope.onSelect = function (hit, $event) { | ||
return $scope.userOnSelect(hit, $event); | ||
}; | ||
|
||
function accessor(val) { | ||
const prop = $scope.listProperty; | ||
return prop ? val[prop] : val; | ||
} | ||
} | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<form role="form" class="form-inline"> | ||
<div class="container-fluid"> | ||
<div class="row"> | ||
<div class="input-group form-group finder-form col-md-9"> | ||
<span class="input-group-addon"> | ||
<i class="fa fa-search"></i> | ||
</span> | ||
<input | ||
input-focus | ||
ng-model="query" | ||
placeholder="Filter..." | ||
class="form-control" | ||
name="query" | ||
type="text" | ||
autocomplete="off" /> | ||
</div> | ||
<div class="finder-hit-count col-md-3"> | ||
<span>{{ (hits | filter: query).length }} of {{ hitCount }}</span> | ||
</div> | ||
</div> | ||
</div> | ||
</form> | ||
<paginate list="hits | filter: query" per-page="{{ perPage }}"> | ||
<ul class="li-striped list-group list-group-menu"> | ||
<li class="list-group-item" ng-click="sortHits(hits)"> | ||
<span class="paginate-heading"> | ||
Name | ||
<i class="fa" ng-class="isAscending ? 'fa-caret-up' : 'fa-caret-down'"></i> | ||
</span> | ||
</li> | ||
<li class="list-group-item list-group-menu-item" ng-repeat="hit in page"> | ||
<a ng-show="userMakeUrl" kbn-href="{{ makeUrl(hit) }}"> | ||
<span>{{ hit }}</span> | ||
</a> | ||
<div ng-show="userOnSelect" ng-click="onSelect(hit, $event)"> | ||
<span>{{ hit }}</span> | ||
</div> | ||
</li> | ||
<li class="list-group-item list-group-no-results" ng-if="(hits | filter: query).length === 0"> | ||
<p>No matches found.</p> | ||
</li> | ||
</ul> | ||
</paginate> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can parameterize the
init
function and get rid of this duplication. Then you could justexpect(init).to.throwError()