Last active
December 29, 2015 17:19
-
-
Save AidasK/7703523 to your computer and use it in GitHub Desktop.
angular multi sort/order directive, allows sub-ordering with shift click.Works without jquery.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global angular */ | |
angular.module('app.directives').directive('appOrderByInit', function () { | |
return { | |
scope: true, | |
controller: ['$scope', '$parse', '$attrs', function ($scope, $parse, $attrs) { | |
var defaultOrder = $parse($attrs.appOrderDefault)($scope) || []; | |
/** | |
* @name $appOrderBy | |
*/ | |
var api = {}; | |
api.params = $parse($attrs.appOrderByInit)($scope) || []; | |
api.classes = $parse($attrs.appOrderClasses)($scope) || ['asc', 'none', 'desc']; | |
api.map = {}; | |
setDefaults(); | |
/** | |
* Is attribute ascending ordered. | |
* @param name | |
* @returns {boolean} | |
*/ | |
api.isAsc = function (name) { | |
return api.map[name] == 1; | |
}; | |
/** | |
* Is attribute descending ordered. | |
* @param name | |
* @returns {boolean} | |
*/ | |
api.isDesc = function (name) { | |
return api.map[name] == -1; | |
}; | |
/** | |
* Is attribute not ordered. | |
* @param name | |
* @returns {boolean} | |
*/ | |
api.isNone = function (name) { | |
return angular.isUndefined(api.map[name]); | |
}; | |
/** | |
* Attribute order index | |
* @param name | |
* @returns {int} -1 if not ordered | |
*/ | |
api.indexOf = function (name) { | |
var index = api.params.indexOf(name); | |
if (index == -1) { | |
index = api.params.indexOf('-' + name); | |
} | |
return index; | |
}; | |
/** | |
* Toggle order state | |
* @param {string} name | |
* @param {MouseEvent} event | |
*/ | |
api.toggle = function $appOrderByToggle(name, event) { | |
var value = toggle(name); | |
if (!event.shiftKey) { | |
api.params.length = 0; | |
if (value) { | |
api.params.push(value); | |
} | |
setDefaults(); | |
} | |
buildMap(); | |
}; | |
/** | |
* Get Indicator class | |
* @param name | |
* @returns {string} | |
*/ | |
api.getClass = function (name) { | |
if (api.isAsc(name)) { | |
return api.classes[0]; | |
} | |
if (api.isDesc(name)) { | |
return api.classes[2]; | |
} | |
return api.classes[1]; | |
}; | |
/** | |
* Get order by instance | |
*/ | |
$scope.$appOrderBy = api; | |
if ($attrs.hasOwnProperty('appOrderByName')) { | |
$parse($attrs.appOrderByName).assign($scope, api); | |
$scope.$on('$destroy', function () { | |
$parse($attrs.appOrderByName).assign($scope); | |
}); | |
} | |
buildMap(); | |
function toggle(name) { | |
var index = api.params.indexOf(name); | |
if (index > -1) { | |
api.params[index] = '-' + name; | |
return '-' + name; | |
} | |
index = api.params.indexOf('-' + name); | |
if (index > -1) { | |
api.params.splice(index, 1); | |
return null; | |
} | |
api.params.push(name); | |
return name; | |
} | |
function buildMap() { | |
api.map = {}; | |
angular.forEach(api.params, function (name) { | |
var order = 1; | |
if (name[0] == '-') { | |
name = name.slice(1); | |
order = -1; | |
} | |
api.map[name] = order; | |
}); | |
} | |
function setDefaults() { | |
angular.forEach(defaultOrder, function (param) { | |
api.params.push(param); | |
}); | |
} | |
}] | |
}; | |
}).directive('appOrderBy', function () { | |
return { | |
require: '^appOrderByInit', | |
link: function (scope, element, attrs) { | |
var name = attrs.appOrderBy; | |
element.bind('click', function (event) { | |
scope.$appOrderBy.toggle(name, event); | |
scope.$apply(); | |
}); | |
} | |
}; | |
}).directive('appOrderByIndicator', function () { | |
return { | |
require: '^appOrderByInit', | |
link: function (scope, element, attrs) { | |
var name = attrs.appOrderByIndicator; | |
scope.$watch(function () { | |
return scope.$appOrderBy.map[name]; | |
}, function () { | |
element.removeClass(scope.$appOrderBy.classes.join(' ')); | |
element.addClass(scope.$appOrderBy.getClass(name)); | |
}); | |
} | |
}; | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe('orderBy', function() { | |
var $compile; | |
var $rootScope; | |
var element; | |
var elementScope; | |
beforeEach(module('app.directives')); | |
beforeEach(inject(function(_$compile_, _$rootScope_){ | |
$compile = _$compile_; | |
$rootScope = _$rootScope_; | |
$rootScope.data = [ | |
{id:0, letter:'A'}, | |
{id:1, letter:'C'}, | |
{id:2, letter:'B'}, | |
{id:3, letter:'D'} | |
]; | |
$rootScope.params = ['-letter']; | |
element = $compile('<table app-order-by-init="params" ' + | |
'app-order-classes="[\'down\',\'none\',\'up\']"' + | |
'>' + | |
'<tr>' + | |
'<th app-order-by="id">id<i app-order-by-indicator="id"></i></th>' + | |
'<th app-order-by="letter">name<i app-order-by-indicator="letter"></i></th>' + | |
'</tr>' + | |
'<tr ng-repeat="row in data | orderBy:$appOrderBy.params">' + | |
'<td>{{row.id}}</td>' + | |
'<td>{{row.letter}}</td>' + | |
'</tr>' + | |
'</table>')($rootScope); | |
$rootScope.$digest(); | |
elementScope = element.scope(); | |
})); | |
function getHeader(index) { | |
return _jQuery(element).find('tr').eq(0).find('th').eq(index); | |
} | |
function getIndicator(index) { | |
return getHeader(index).find('i'); | |
} | |
function shiftClick(elem) { | |
elem.simulate('click', { shiftKey: true }); | |
} | |
it('displays data', function() { | |
expect(element.find('tr').length).toBe(5); | |
}); | |
it('default order data', function() { | |
expect(elementScope.$appOrderBy.params).toEqual(['-letter']); | |
}); | |
it('should generate map', function() { | |
expect(elementScope.$appOrderBy.map.letter).toBe(-1); | |
expect(elementScope.$appOrderBy.map.id).toBeUndefined(); | |
}); | |
it('should maintain reference to initial params array', function() { | |
expect(elementScope.$appOrderBy.params).toBe($rootScope.params); | |
getHeader(0).simulate('click'); | |
expect(elementScope.$appOrderBy.params).toBe($rootScope.params); | |
}); | |
it('should add appropriate indicator classes', function () { | |
expect(getIndicator(0).hasClass('none')).toBeTruthy(); | |
expect(getIndicator(1).hasClass('up')).toBeTruthy(); | |
}); | |
it('should order id', function () { | |
getHeader(0).simulate('click'); | |
expect(getIndicator(0).attr('class')).toBe('down'); | |
expect(getIndicator(1).attr('class')).toBe('none'); | |
expect(elementScope.$appOrderBy.params).toEqual(['id']); | |
getHeader(0).simulate('click'); | |
expect(getIndicator(0).attr('class')).toBe('up'); | |
expect(getIndicator(1).attr('class')).toBe('none'); | |
expect(elementScope.$appOrderBy.params).toEqual(['-id']); | |
getHeader(0).simulate('click'); | |
expect(getIndicator(0).attr('class')).toBe('none'); | |
expect(getIndicator(1).attr('class')).toBe('none'); | |
expect(elementScope.$appOrderBy.params.length).toBe(0); | |
}); | |
it('should order by id with shift', function () { | |
$rootScope.data.push({id:4, letter:'B'}); | |
expect(elementScope.$appOrderBy.indexOf('id')).toBe(-1); | |
expect(elementScope.$appOrderBy.indexOf('letter')).toBe(0); | |
shiftClick(getHeader(0)); | |
expect(getIndicator(0).attr('class')).toBe('down'); | |
expect(getIndicator(1).attr('class')).toBe('up'); | |
expect(elementScope.$appOrderBy.indexOf('id')).toBe(1); | |
expect(elementScope.$appOrderBy.indexOf('letter')).toBe(0); | |
shiftClick(getHeader(0)); | |
expect(getIndicator(0).attr('class')).toBe('up'); | |
expect(getIndicator(1).attr('class')).toBe('up'); | |
expect(elementScope.$appOrderBy.indexOf('id')).toBe(1); | |
expect(elementScope.$appOrderBy.indexOf('letter')).toBe(0); | |
shiftClick(getHeader(0)); | |
expect(getIndicator(0).attr('class')).toBe('none'); | |
expect(getIndicator(1).attr('class')).toBe('up'); | |
expect(elementScope.$appOrderBy.indexOf('id')).toBe(-1); | |
expect(elementScope.$appOrderBy.indexOf('letter')).toBe(0); | |
shiftClick(getHeader(1)); | |
expect(elementScope.$appOrderBy.indexOf('id')).toBe(-1); | |
expect(elementScope.$appOrderBy.indexOf('letter')).toBe(-1); | |
}); | |
describe('app-order-default', function () { | |
beforeEach(function () { | |
$rootScope.data = [ | |
{id:0, letter:'A'}, | |
{id:2, letter:'B'}, | |
{id:1, letter:'B'}, | |
{id:3, letter:'D'} | |
]; | |
element = $compile('<table app-order-by-init="[\'-letter\']" ' + | |
'app-order-classes="[\'down\',\'none\',\'up\']"' + | |
'app-order-default="[\'id\']"' + | |
'>' + | |
'<tr>' + | |
'<th app-order-by="id">id<i app-order-by-indicator="id"></i></th>' + | |
'<th app-order-by="letter">name<i app-order-by-indicator="letter"></i></th>' + | |
'</tr>' + | |
'<tr ng-repeat="row in data | orderBy:$appOrderBy.params">' + | |
'<td>{{row.id}}</td>' + | |
'<td>{{row.letter}}</td>' + | |
'</tr>' + | |
'</table>')($rootScope); | |
$rootScope.$digest(); | |
elementScope = element.scope(); | |
}); | |
it('should combine init with default options', function () { | |
expect(elementScope.$appOrderBy.params).toEqual(['-letter', 'id']); | |
}); | |
it('should not remove defaults', function () { | |
getHeader(1).simulate('click'); | |
expect(elementScope.$appOrderBy.params).toEqual(['id']); | |
}); | |
it('should bring new params in front of defaults', function () { | |
getHeader(1).simulate('click'); | |
getHeader(1).simulate('click'); | |
expect(elementScope.$appOrderBy.params).toEqual(['letter', 'id']); | |
}); | |
}); | |
describe('appOrderByName', function () { | |
beforeEach(function () { | |
$rootScope.sorter = {}; | |
element = $compile('<table app-order-by-init="[\'-letter\']" ' + | |
'app-order-by-name="sorter.order"' + | |
'>' + | |
'</table>')($rootScope.$new()); | |
elementScope = element.scope(); | |
$rootScope.$digest(); | |
}); | |
it('should have assigned $appOrderBy object', function () { | |
expect($rootScope.sorter.order).toBe(elementScope.$appOrderBy); | |
}); | |
it('should destroy assigned $appOrderBy object', function () { | |
expect($rootScope.sorter.order).toBeDefined(); | |
elementScope.$destroy(); | |
expect($rootScope.sorter.order).toBeUndefined(); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment