Last active
December 25, 2015 13:39
-
-
Save huafu/6984862 to your computer and use it in GitHub Desktop.
Mixins and view to handle searchable array controller and infinite scroll list view
This file contains 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
App = require 'app' | |
###* | |
Controller ExampleItemsController | |
@class ExampleItemsController | |
@namespace App | |
@extends Ember.ArrayController | |
### | |
module.exports = App.ExampleItemsController = Ember.ArrayController.extend App.Mixins.PagedSearchableArrayController, | |
###* | |
Holds our content | |
@property content | |
@type Array | |
### | |
content: [] | |
###* | |
@inheritDoc | |
### | |
searchFilterKeys: ['name'] | |
###* | |
Initialize the search filters | |
@method initializeSearch | |
### | |
initializeSearch: (() -> | |
@set 'searchFilters.name', '' | |
).on('searchInit') | |
###* | |
@inheritDoc | |
### | |
searchGrabResults: (filters) -> | |
@get('store').findQuery 'example', filters |
This file contains 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
App = require 'app' | |
###* | |
View InfiniteScrollListView | |
@class InfiniteScrollListView | |
@namespace App | |
@extends Ember.VirtualListView | |
### | |
module.exports = App.InfiniteScrollListView = Ember.VirtualListView.extend | |
###* | |
Load more results when being that pixels close to the bottom | |
@property loadMoreAtPixelsFromBottom | |
@type Number | |
### | |
loadMoreAtPixelsFromBottom: 20 | |
###* | |
Our initializer | |
@method _intiailizeInfiniteScroll | |
### | |
_intiailizeInfiniteScroll: (() -> | |
# be sure our content is of good type | |
Ember.assert 'The content of the infinite scroll list view must be using App.Mixins.PagedSearchableArrayController', | |
App.Mixins.PagedSearchableArrayController.detect @get 'content' | |
).on('init') | |
###* | |
Listen for scroll Y changes to be able to trigger teh load more | |
@method handleScrollYChanges | |
@param {Number} y | |
### | |
handleScrollYChanges: ((y) -> | |
maxScrollTop = @get('maxScrollTop') | |
if maxScrollTop and y > 0 and y >= maxScrollTop - @get('loadMoreAtPixelsFromBottom') | |
@get('content').loadNextPage() | |
).on('scrollYChanged') | |
This file contains 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
App = require 'app' | |
###* | |
Mxin PagedSearchableArrayController | |
@class PagedSearchableArrayController | |
@namespace App.Mixins | |
### | |
module.exports = App.Mixins.PagedSearchableArrayController = Ember.Mixin.create App.Mixins.SearchableArrayController, | |
###* | |
@inheritDoc | |
### | |
searchFilterKeys: ['limit'] | |
###* | |
@inheritDoc | |
### | |
searchFilterAppendKeys: ['offset'] | |
###* | |
Default limit | |
@property searchDefaultLimit | |
@type Number | |
### | |
searchDefaultLimit: 20 | |
###* | |
Is there more pages? | |
@property searchHasMoreResults | |
@type Boolean | |
### | |
searchHasMoreResults: yes | |
###* | |
Initialize our mixin | |
@method _initializePagedSearch | |
### | |
_initializePagedSearch: (() -> | |
if (sf = @get 'searchFilters') | |
sf.set 'offset', 0 | |
sf.set 'limit', @get 'searchDefaultLimit' | |
@set 'searchHasMoreResults', yes | |
).on('searchInit') | |
###* | |
@inheritDoc | |
### | |
handleSearchFiltersChange: (() -> | |
changed = @get 'changedSearchFilterKeys' | |
nonAppendedKeys = @get 'searchFilterKeys' | |
for key in changed when key in nonAppendedKeys | |
@set 'searchFilters.offset', 0 | |
break | |
).on('searchFiltersDidChange') | |
###* | |
Load the next page with the same serach filters | |
@method loadNextPage | |
### | |
loadNextPage: () -> | |
# get out if we are already running a search or if we don't have more results | |
return if @get('isSearching') or not @get('searchHasMoreResults') | |
@incrementProperty 'searchFilters.offset', @get 'searchFilters.limit' | |
# trigger the search directly, no need to wait | |
@checkFiltersAndSearch() | |
###* | |
Called when we have new results | |
@method gotNewResults | |
@param {Ember.Enumerable} parsedResults | |
### | |
handleGotNewResults: ((parsedResults) -> | |
# we update the hasMoreResults | |
@set 'searchHasMoreResults', Boolean(parsedResults.get('length') >= @get 'searchFilters.limit') | |
).on('gotNewResults') | |
###* | |
Is loading more results | |
@property isLoadingMoreResults | |
@type Boolean | |
### | |
isLoadingMoreResults: (() -> | |
Boolean(@get('isSearching') and @get('runningSearchWillAppend')) | |
).property('isSearching', 'runningSearchWillAppend') | |
This file contains 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
App = require 'app' | |
###* | |
Mxin SearchableArrayController | |
@class SearchableArrayController | |
@namespace App.Mixins | |
### | |
module.exports = App.Mixins.SearchableArrayController = Ember.Mixin.create Ember.Evented, | |
###* | |
@inheritDoc | |
### | |
concatenatedProperties: ['searchFilterKeys', 'searchFilterAppendKeys'] | |
###* | |
Search on initialization? | |
@property searchOnInit | |
@type Boolean | |
### | |
searchOnInit: yes | |
###* | |
Filters | |
@property searchFilters | |
@type Ember.Object | |
### | |
searchFilters: null | |
###* | |
Search delay (the milliseconds to wait for other filter changes before running the search | |
@property searchDelay | |
@type Number | |
### | |
searchDelay: 500 | |
###* | |
Our filter keys | |
@property searchFilterKeys | |
@type Array<String> | |
### | |
searchFilterKeys: Ember.required Array | |
###* | |
Search filter append keys | |
@property searchFilterAppendKeys | |
@type Array<String> | |
### | |
searchFilterAppendKeys: [] | |
###* | |
Last search filters cache key | |
@property lastSearchFiltersCacheKey | |
@type String | |
### | |
lastSearchFiltersCacheKey: null | |
###* | |
Last search filters values | |
@property lastSearchFilterValues | |
@type Object | |
### | |
lastSearchFilterValues: null | |
###* | |
Function which will lookup results | |
Must return a promise which should resolve to the results | |
@property searchGrabResults | |
@type Function | |
### | |
searchGrabResults: Ember.required Function | |
###* | |
Are we running a search? | |
@property isSearching | |
@type Boolean | |
### | |
isSearching: no | |
###* | |
Running search will append or update results? | |
@property runningSearchWillAppend | |
@type Boolean | |
### | |
runningSearchWillAppend: no | |
###* | |
Running search cache key | |
@property runningSearchCacheKey | |
@type String | |
### | |
runningSearchCacheKey: null | |
###* | |
Serialized search cache key | |
@property serializedSearchCacheKey | |
@type String | |
### | |
serializedSearchCacheKey: (() -> | |
props = @get 'allSearchFilterKeys' | |
JSON.stringify @get('searchFilters').getProperties props | |
).property().volatile().readOnly() | |
###* | |
All search filter keys | |
@property allSearchFilterKeys | |
@type Array<String> | |
@private | |
### | |
allSearchFilterKeys: (() -> | |
props = [] | |
props.addObjects @get 'searchFilterKeys' | |
props.addObjects @get 'searchFilterAppendKeys' | |
props.uniq() | |
).property('searchFilterKeys.@each', 'searchFilterAppendKeys.@each').readOnly() | |
###* | |
Returns the keys of all search filters which have changed | |
@property changedSearchFilterKeys | |
@type Array<String> | |
### | |
changedSearchFilterKeys: (() -> | |
changed = [] | |
all = @get 'allSearchFilterKeys' | |
last = @get 'lastSearchFilterValues' | |
if last | |
all.forEach (key) => | |
if @get("lastSearchFilterValues.#{key}") isnt @get "searchFilters.#{key}" | |
changed.push key | |
else | |
changed = all | |
changed | |
).property().volatile().readOnly() | |
###* | |
Called on initialization | |
@method _initializeSearch | |
### | |
_initializeSearch: (() -> | |
@set 'searchFilters', Ember.Object.create() unless @get 'searchFilters' | |
@get('searchFilterKeys').forEach (key) => | |
@addObserver "searchFilters.#{key}", @, '_searchFiltersDidChange' | |
@get('searchFilterAppendKeys').forEach (key) => | |
@addObserver "searchFilters.key", @, '_searchFiltersDidChange' | |
@trigger 'searchInit' | |
@_searchFiltersDidChange() if @get 'searchOnInit' | |
).on('init') | |
###* | |
Called on destroy | |
@method _destroySearch | |
### | |
_destroySearch: (() -> | |
@get('searchFilterAppendKeys').forEach (key) => | |
@removeObserver "searchFilters.key", @, '_searchFiltersDidChange' | |
@get('searchFilterKeys').forEach (key) => | |
@removeObserver "searchFilters.#{key}", @, '_searchFiltersDidChange' | |
).on('willDestroy') | |
###* | |
Called when one of the search filter changed | |
@method searchFiltersDidChange | |
@private | |
### | |
_searchFiltersDidChange: () -> | |
Ember.run.debounce @, 'checkFiltersAndSearch', @get 'searchDelay' | |
###* | |
Check the filters for changes and run the search if necessary | |
@method checkFiltersAndSearch | |
### | |
checkFiltersAndSearch: () -> | |
# get out if we're already running a search | |
return if @get 'isSearching' | |
# get the old cache key and generate a new one | |
oldCacheKey = @get 'lastSearchFiltersCacheKey' | |
newCacheKey = @get 'serializedSearchCacheKey' | |
if oldCacheKey is newCacheKey | |
# the cache key is the same, no parameter change => get out | |
no | |
else | |
# we trigger a search filter change, so that any dependent stuff | |
# in the search filters can be changed | |
@trigger 'searchFiltersDidChange' | |
# we re-generate the cache key in case any parameter has been changed in the event handler | |
newCacheKey = @get 'serializedSearchCacheKey' | |
if oldCacheKey is newCacheKey | |
# again, if it's the same as the previous search, let's get out | |
no | |
else | |
# the parameters did change, we need to run the search | |
@set 'isSearching', yes | |
# get the list of changed parameters | |
changed = @get 'changedSearchFilterKeys' | |
appendKeys = @get 'searchFilterAppendKeys' | |
replaceKeys = @get 'searchFilterKeys' | |
# we gonna check if the search parameters which changed would just append results | |
# or if it needs to replace the results | |
append = no | |
replace = no | |
for key in changed | |
append = yes if not append and key in appendKeys | |
replace = yes if not replace and key in replaceKeys | |
# at the end we will be in append mode only if replace is false and append is true | |
append = not replace and append | |
# unserializing the cache key will give us a simple object with search filters | |
# but all would be a copy so any change doesn't matter | |
filters = JSON.parse newCacheKey | |
# we update the running search paraemters | |
@set 'runningSearchWillAppend', append | |
@set 'runningSearchCacheKey', newCacheKey | |
@searchGrabResults(filters).then((results) => | |
# once we got the result we can set the last search | |
@set 'lastSearchFiltersCacheKey', newCacheKey | |
@set 'lastSearchFilterValues', JSON.parse newCacheKey | |
# we call the results parser | |
results = @parseResults results | |
# we trigger our event | |
@trigger 'gotNewResults', results | |
# we append or replace the results | |
if append | |
@addObjects results | |
else | |
@set 'content', results | |
# we clear all data set for the running search | |
@set 'runningSearchCacheKey', null | |
@set 'runningSearchWillAppend', null | |
@set 'isSearching', no | |
# to do the search again in case the filters changed in between | |
@_searchFiltersDidChange() | |
).fail (reason) => | |
#TODO: log the error and try again once or twice | |
@set 'runningSearchCacheKey', null | |
@set 'runningSearchWillAppend', null | |
@set 'isSearching', no | |
# we return OK since the search will be running | |
yes | |
###* | |
Parse the results | |
@method parseResults | |
@param {Ember.Enumerable} results | |
@returns Ember.Enumerable | |
### | |
parseResults: (results) -> | |
Ember.A results |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment