Skip to content

Commit

Permalink
Add FilterableMixin
Browse files Browse the repository at this point in the history
Works in a similar way to the SortableMixin.

Mix this into an ArrayProxy, define the filterProperties and the array
will be filtered to only include the objects whose filterProperties are
truthy.

You can get more advanced filtering by defining a custom
filterCondition. The filterCondition is used to determine if the object is
to be included in the filtered list.  Any item properties used in
the filterCondition will need to be lised in filterProperties.
  • Loading branch information
crofty committed Aug 16, 2012
1 parent 138fbdf commit 757bea3
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/ember-runtime/lib/controllers/array_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require('ember-runtime/system/array_proxy');
require('ember-runtime/controllers/controller');
require('ember-runtime/mixins/sortable');
require('ember-runtime/mixins/filterable');

var get = Ember.get, set = Ember.set;

Expand Down
140 changes: 140 additions & 0 deletions packages/ember-runtime/lib/mixins/filterable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;

/**
@class
@extends Ember.Mixin
@extends Ember.MutableEnumerable
*/
Ember.FilterableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
filterProperties: null,

filterCondition: function(item){
var filterProperties = get(this, 'filterProperties');
return Ember.A(filterProperties).every(function(property){
return !!get(item, property);
});
},

addObject: function(obj) {
var content = get(this, 'content');
content.pushObject(obj);
},

removeObject: function(obj) {
var content = get(this, 'content');
content.removeObject(obj);
},

destroy: function() {
var content = get(this, 'content'),
filterProperties = get(this, 'filterProperties');

if (content && filterProperties) {
forEach(content, function(item) {
forEach(filterProperties, function(filterProperty) {
Ember.removeObserver(item, filterProperty, this, 'contentItemFilterPropertyDidChange');
}, this);
}, this);
}

return this._super();
},

isFiltered: Ember.computed('filterProperties', function() {
return !!get(this, 'filterProperties');
}),

arrangedContent: Ember.computed('content', 'filterProperties.@each', function(key, value) {
var content = get(this, 'content'),
isFiltered = get(this, 'isFiltered'),
filterProperties = get(this, 'filterProperties'),
filterValue = get(this, 'filterValue'),
self = this;

if (content && isFiltered) {
forEach(content, function(item) {
forEach(filterProperties, function(filterProperty) {
Ember.addObserver(item, filterProperty, this, 'contentItemFilterPropertyDidChange');
}, this);
}, this);
content = content.slice();
content = content.filter(this.filterCondition, this);

return Ember.A(content);
}

return content;
}).cacheable(),

_contentWillChange: Ember.beforeObserver(function() {
var content = get(this, 'content'),
filterProperties = get(this, 'filterProperties');

if (content && filterProperties) {
forEach(content, function(item) {
forEach(filterProperties, function(filterProperty) {
Ember.removeObserver(item, filterProperty, this, 'contentItemFilterPropertyDidChange');
}, this);
}, this);
}

this._super();
}, 'content'),

contentArrayWillChange: function(array, idx, removedCount, addedCount) {
var isFiltered = get(this, 'isFiltered');

if (isFiltered) {
var arrangedContent = get(this, 'arrangedContent');
var removedObjects = array.slice(idx, idx+removedCount);
var filterProperties = get(this, 'filterProperties');

forEach(removedObjects, function(item) {
arrangedContent.removeObject(item);
forEach(filterProperties, function(filterProperty) {
Ember.removeObserver(item, filterProperty, this, 'contentItemFilterPropertyDidChange');
}, this);
});
}

return this._super(array, idx, removedCount, addedCount);
},

contentArrayDidChange: function(array, idx, removedCount, addedCount) {
var isFiltered = get(this, 'isFiltered'),
filterProperties = get(this, 'filterProperties');

if (isFiltered) {
var addedObjects = array.slice(idx, idx+addedCount);
var arrangedContent = get(this, 'arrangedContent');

forEach(addedObjects, function(item) {
this.insertItemFiltered(item);

forEach(filterProperties, function(filterProperty) {
Ember.addObserver(item, filterProperty, this, 'contentItemFilterPropertyDidChange');
}, this);
}, this);
}

return this._super(array, idx, removedCount, addedCount);
},

contentItemFilterPropertyDidChange: function(item) {
var arrangedContent = get(this, 'arrangedContent'),
index = arrangedContent.indexOf(item);

arrangedContent.removeObject(item);
this.insertItemFiltered(item);
},

insertItemFiltered: function(item){
var arrangedContent = get(this, 'arrangedContent');

if( this.filterCondition(item) ){
arrangedContent.pushObject(item);
}
}

});
179 changes: 179 additions & 0 deletions packages/ember-runtime/tests/mixins/filterable_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
var get = Ember.get, set = Ember.set;


var array, unfilteredArray, filteredArrayController;

module("Ember.Filterable");

module("Ember.Filterable with content", {
setup: function() {
Ember.run(function() {
array = [{ id: 1, name: "Scumbag Dale" }, { id: 2, name: "Scumbag Katz" }, { id: 3, name: "Scumbag Bryn" }];
unfilteredArray = Ember.A(array);

filteredArrayController = Ember.ArrayProxy.create(Ember.FilterableMixin, {
content: unfilteredArray
});
});
},

teardown: function() {
Ember.run(function() {
filteredArrayController.set('content', null);
filteredArrayController.destroy();
});
}
});

test("if you do not specify `filterProperties` filterable has no effect", function() {
equal(filteredArrayController.get('length'), 3, 'array has 3 items');

unfilteredArray.pushObject({id: 4, name: 'Scumbag Chavard'});

equal(filteredArrayController.get('length'), 4, 'array has 4 items');
});

test("you can change the filterProperties and filterCondition", function() {
equal(filteredArrayController.get('length'), 3, 'precond - array has 3 items');

filteredArrayController.filterCondition = function(item){ return get(item, 'id') === 1; };
filteredArrayController.set('filterProperties', ['id']);

equal(filteredArrayController.get('length'), 1, 'array has 1 item');
equal(filteredArrayController.objectAt(0).name, 'Scumbag Dale', 'array is filtered by id');
});


module("Ember.Filterable with content, filterProperties and filterCondition", {
setup: function() {
Ember.run(function() {
array = [{ id: 1, name: "Scumbag Dale" }, { id: 2, name: "Scumbag Katz" }, { id: 3, name: "Scumbag Bryn" }];
unfilteredArray = Ember.A(array);

filteredArrayController = Ember.ArrayProxy.create(Ember.FilterableMixin, {
content: unfilteredArray,
filterProperties: ['id'],
filterCondition: function(item){ return get(item, 'id') === 1; }
});
});
},

teardown: function() {
Ember.run(function() {
filteredArrayController.destroy();
});
}
});

test("filtered object will expose filtered content", function() {
equal(filteredArrayController.get('length'), 1, 'array is filtered by id');
equal(filteredArrayController.objectAt(0).name, 'Scumbag Dale', 'the object is the correct one');
});

test("you can add objects in the filtered array", function() {
equal(filteredArrayController.get('length'), 1, 'array has 1 item');

unfilteredArray.pushObject({id: 1, name: 'Scumbag Chavard'});

equal(filteredArrayController.get('length'), 2, 'array has 2 items');
equal(filteredArrayController.objectAt(1).name, 'Scumbag Chavard', 'a new object added to content was inserted according to given constraint');

unfilteredArray.addObject({id: 1, name: 'Scumbag Fucs'});

equal(filteredArrayController.get('length'), 3, 'array has 3 items');
equal(filteredArrayController.objectAt(2).name, 'Scumbag Fucs', 'a new object added to controller was inserted according to given constraint');
});

test("new objects don't get added if they don't meet the filter condition", function() {
equal(filteredArrayController.get('length'), 1, 'array has 1 item');

unfilteredArray.pushObject({id: 5, name: 'Scumbag Chavard'});

equal(filteredArrayController.get('length'), 1, 'array has 1 item');
});

test("you can change a filter property and the content will be removed", function() {
equal(filteredArrayController.get('length'), 1, 'array has 1 item');
equal(filteredArrayController.objectAt(0).name, 'Scumbag Dale', 'dale is the only one');

set(filteredArrayController.objectAt(0), 'id', 2);

equal(filteredArrayController.get('length'), 0, 'array has no items');
});

test("you can change a filter property and the content will be added", function() {
equal(filteredArrayController.get('length'), 1, 'array has 1 item');
equal(filteredArrayController.objectAt(0).name, 'Scumbag Dale', 'dale is the only one');

set(unfilteredArray.objectAt(1), 'id', 1);

equal(filteredArrayController.get('length'), 2, 'array has two items');
equal(filteredArrayController.objectAt(0).name, 'Scumbag Dale', 'dale is there');
equal(filteredArrayController.objectAt(1).name, 'Scumbag Katz', 'katz is there');
});

module("Ember.Filterable with filterProperties and filterCondition", {
setup: function() {
Ember.run(function() {
array = [{ id: 1, name: "Scumbag Dale" }, { id: 2, name: "Scumbag Katz" }, { id: 3, name: "Scumbag Bryn" }];
unfilteredArray = Ember.A(array);

filteredArrayController = Ember.ArrayProxy.create(Ember.FilterableMixin, {
filterProperties: ['id'],
filterCondition: function(item){
return get(item,'id') === 1;
}
});
});
},

teardown: function() {
Ember.run(function() {
filteredArrayController.destroy();
});
}
});

test("you can set content later and it will be filtered", function() {
equal(filteredArrayController.get('length'), 0, 'array has 0 items');

Ember.run(function() {
filteredArrayController.set('content', unfilteredArray);
});

equal(filteredArrayController.get('length'), 1, 'array has 1 item');
equal(filteredArrayController.objectAt(0).name, 'Scumbag Dale', 'dale is in the filtered array');
});

module("Ember.Filterable with content and filterProperties", {
setup: function() {
Ember.run(function() {
array = [{ id: 1, name: "Scumbag Dale" }, { id: 2, name: "Scumbag Katz" }, { id: 3, name: null }];
unfilteredArray = Ember.A(array);

filteredArrayController = Ember.ArrayProxy.create(Ember.FilterableMixin, {
content: unfilteredArray,
filterProperties: ['id', 'name']
});
});
},

teardown: function() {
Ember.run(function() {
filteredArrayController.destroy();
});
}
});

test("by default it tests if all filterProperties are truthy", function() {
equal(filteredArrayController.get('length'), 2, 'array has 2 items');

unfilteredArray.pushObject({id: 4, name: 'Scumbag Chavard'});

equal(filteredArrayController.get('length'), 3, 'adds valid items to the filtered array');

unfilteredArray.pushObject({id: undefined, name: 'Scumbag Chavard'});
unfilteredArray.pushObject({id: 6, name: ''});

equal(filteredArrayController.get('length'), 3, "it doesn't add invalid items to the filtered array");
});

0 comments on commit 757bea3

Please sign in to comment.