Skip to content

Instantly share code, notes, and snippets.

@AidasK
Last active December 29, 2015 17:19
Show Gist options
  • Save AidasK/7703523 to your computer and use it in GitHub Desktop.
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.
/*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));
});
}
};
});
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