Skip to content

Commit

Permalink
Refactor sort-by with native implementation of sort (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
snewcomer authored Apr 26, 2020
1 parent 35d653a commit 9eb2030
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 30 deletions.
131 changes: 103 additions & 28 deletions addon/helpers/sort-by.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,110 @@
import { defineProperty } from '@ember/object';
import { isArray as isEmberArray } from '@ember/array';
import { sort } from '@ember/object/computed';
import Helper from '@ember/component/helper';
import { set } from '@ember/object';
import { isEmpty, typeOf } from '@ember/utils';

export default Helper.extend({
compute(params) {
// slice params to avoid mutating the provided params
let sortProps = params.slice();
let array = sortProps.pop();
let [firstSortProp] = sortProps;

if (typeOf(firstSortProp) === 'function' || isEmberArray(firstSortProp)) {
sortProps = firstSortProp;
import { get } from '@ember/object';
import { helper } from '@ember/component/helper';

function normalizeToBoolean(val) {
if (typeof val === 'boolean') {
return val;
}

if (typeof val === 'number') {
return val > 0 ? false : true;
}

return val;
}

function sortDesc(key, a, b) {
return get(a, key) > get(b, key);
}

function sortAsc(key, a, b) {
return get(a, key) < get(b, key);
}

class SortBy {
constructor(...args) {
let [array] = args;

this.array = [...array];
this.callbacks = null;
}

comparator(key) {
return this.callback ? this.callback : this.defaultSort(key);
}

defaultSort(sortKey) {
let func = sortAsc;
if (sortKey.match(':desc')) {
func = sortDesc;
}

// TODO: can we / should we use variables instead of computed properties?
set(this, 'array', array);
set(this, 'sortProps', sortProps);
return (a, b) => func(sortKey.replace(/:desc|:asc/, ''), a, b);
}

addCallback(callback) {
this.callback = callback;
}
}

if (isEmpty(sortProps)) {
defineProperty(this, 'content', []);
/**
* best O(n); worst O(n^2)
* If we feel like swapping with something more performant like QuickSort or MergeSort
* then it should be easy
*
* @class BubbleSort
* @extends SortBy
*/
class BubbleSort extends SortBy {
perform(key) {
let swapped = false;

let compFunc = this.comparator(key);
for (let i = 1; i < this.array.length; i += 1) {
for (let j = 0; j < this.array.length - i; j += 1) {
let shouldSwap = normalizeToBoolean(compFunc(this.array[j+1], this.array[j]));
if (shouldSwap) {
[this.array[j], this.array[j+1]] = [this.array[j+1], this.array[j]];

swapped = true;
}
}

// no need to continue sort if not swapped in any inner iteration
if (!swapped) {
return this.array;
}
}
}
}

export function sortBy(params) {
// slice params to avoid mutating the provided params
let sortParams = params.slice();
let array = sortParams.pop();
let sortKeys = sortParams;

if (!array || (!sortKeys || sortKeys.length === 0)) {
return [];
}

if (typeof sortProps === 'function') {
defineProperty(this, 'content', sort('array', sortProps));
} else {
defineProperty(this, 'content', sort('array', 'sortProps'));
if (sortKeys.length === 1 && Array.isArray(sortKeys[0])) {
sortKeys = sortKeys[0];
}

const sortKlass = new BubbleSort(array);

if (typeof sortKeys[0] === 'function') { // || isEmberArray(firstSortProp)) {
sortKlass.addCallback(sortKeys[0]);
sortKlass.perform();
} else {
for (let key of sortKeys) {
sortKlass.perform(key);
}
}


return sortKlass.array;
}

return this.content;
},
});
export default helper(sortBy);
4 changes: 2 additions & 2 deletions tests/integration/helpers/entries-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ module('Integration | Helper | entries', function(hooks) {

this.set('object', object);
this.set('myOwnSortBy', function(a, b) {
if (a[0] > b[0]) {
if (a[1] > b[1]) {
return 1;
} else if (a[0] < b[0]) {
} else if (a[1] < b[1]) {
return -1;
}
return 0;
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/helpers/sort-by-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ module('Integration | Helper | {{sort-by}}', function(hooks) {
});

test('It sorts by a value', async function(assert) {
this.set('array', [
{ name: 'c' },
{ name: 'a' },
{ name: 'b' }
]);

await render(hbs`
{{~#each (sort-by 'name' array) as |user|~}}
{{~user.name~}}
{{~/each~}}
`);

assert.equal(find('*').textContent.trim(), 'abc', 'cab is sorted to abc');
});

test('It sorts by a value with EmberArray', async function(assert) {
this.set('array', emberArray([
{ name: 'c' },
{ name: 'a' },
Expand All @@ -29,6 +45,22 @@ module('Integration | Helper | {{sort-by}}', function(hooks) {
assert.equal(find('*').textContent.trim(), 'abc', 'cab is sorted to abc');
});

test('It sorts by a value desc', async function(assert) {
this.set('array', emberArray([
{ name: 'c' },
{ name: 'a' },
{ name: 'b' }
]));

await render(hbs`
{{~#each (sort-by 'name:desc' array) as |user|~}}
{{~user.name~}}
{{~/each~}}
`);

assert.equal(find('*').textContent.trim(), 'cba', 'cab is sorted to cba');
});

test('It watches for changes', async function(assert) {
let array = emberArray([
{ name: 'b' },
Expand Down

0 comments on commit 9eb2030

Please sign in to comment.