Improve filter service. Centralize filter management in a single point
authorymh <ymh.work@gmail.com>
Fri, 04 Nov 2016 19:03:25 +0100
changeset 392 4fbe94af93e8
parent 391 4f6118afabee
child 393 5ef3bfef0bff
Improve filter service. Centralize filter management in a single point
build/set-version.sh
cms/app-client/app/components/discourses-component.js
cms/app-client/app/components/filter-component.js
cms/app-client/app/components/sorting-component.js
cms/app-client/app/components/visu-carto.js
cms/app-client/app/components/visu-chrono.js
cms/app-client/app/components/visu-langues.js
cms/app-client/app/controllers/application.js
cms/app-client/app/helpers/contains.js
cms/app-client/app/helpers/interval.js
cms/app-client/app/helpers/is-checked.js
cms/app-client/app/helpers/is-indeterminate.js
cms/app-client/app/routes/application.js
cms/app-client/app/routes/tabs/chrono.js
cms/app-client/app/services/filter.js
cms/app-client/app/templates/components/filter-component.hbs
cms/app-client/app/templates/components/sorting-component.hbs
cms/app-client/app/templates/components/visu-chrono.hbs
cms/app-client/app/templates/tabs/carto.hbs
cms/app-client/app/templates/tabs/chrono.hbs
common/corpus-common-addon/addon/components/doc-location.js
common/corpus-common-addon/addon/utils/constants.js
common/corpus-common-addon/addon/utils/utils.js
common/corpus-common-addon/app/services/geonames-resolver.js
common/corpus-common-addon/package.json
--- a/build/set-version.sh	Thu Nov 03 11:14:41 2016 +0100
+++ b/build/set-version.sh	Fri Nov 04 19:03:25 2016 +0100
@@ -61,6 +61,12 @@
 /usr/local/bin/npm run set-version -- "$version"
 popd
 
+pushd ../common/corpus-common-addon
+echoblue "Setting corpus-common-addon version"
+/usr/local/bin/npm run set-version -- "$version"
+popd
+
+
 echoblue "Version has been changed, please commit and push if necessary."
 
 
--- a/cms/app-client/app/components/discourses-component.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/components/discourses-component.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,6 +1,7 @@
 import Ember from 'ember';
 import d3 from 'd3';
 import ENV from 'app-client/config/environment';
+import _ from 'lodash/lodash';
 
 
 
@@ -13,7 +14,12 @@
 
     discourseObserver: Ember.observer('filter.discourse', function() {
         Ember.$('.item').removeClass("selected");
-        Ember.$('.item[data-id="' + this.get('filter').get('discourse') + '"]').addClass("selected");
+        if(this.get('filter').get('discourse') !== null) {
+            this.get('filter').get('discourse').forEach(function(e) {
+                Ember.$('.item[data-id="' + e + '"]').addClass("selected");
+            });
+        }
+
     }).on('init'),
 
     didRender: function() {
@@ -50,7 +56,7 @@
                 .data(bubbles);
 
             nodes.enter().append("div")
-                .attr("class", function(d) { return ( d.children ? "category": "item" ) + ( self.get('filter').get('discourse') === d.id ? " selected" : "" ) ; });
+                .attr("class", function(d) { return ( d.children ? "category": "item" ) + ( (self.get('filter').get('discourse') !== null && _.contains(self.get('filter').get('discourse'), d.id)) ? " selected" : "" ) ; });
 
             var item = element.selectAll(".item")
                 .attr("data-id", function(d) { return d.id; })
@@ -62,7 +68,7 @@
                 .style("border-color", function(d) { return d.stroke; })
                 .style("font-size", function(d) { return Math.floor((((d.value - oldMin) * (13 - 10)) / (oldMax - oldMin)) + 10) + 'px'; })
                 .on('click', function(d) {
-                    self.get('filter').set('discourse', d.id);
+                    self.get('filter').setFilter('discourse', d.id);
                 });
 
             item.append("span")
--- a/cms/app-client/app/components/filter-component.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/components/filter-component.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,38 +1,14 @@
 import Ember from 'ember';
 
 export default Ember.Component.extend({
-    
+
     classNames: ['filter-component'],
 
     filter: Ember.inject.service(),
 
-    intervals: Ember.computed('filter.date', function() {
-        var intervals = [];
-        this.get('filter.date').forEach(function(date) {
-            var isInterval = false;
-            intervals.forEach(function(interval) {
-                if(interval.length && (interval.includes(date + 1) || interval.includes(date - 1))) {
-                    interval.push(date);
-                    isInterval = true;
-                }
-            });
-            if (!isInterval) {
-                intervals.push([date]);
-            }
-        });
-        return intervals;
-    }),
-
     actions: {
         removeFilter: function(key, value) {
-            if(typeof this.get('filter').get(key) === 'object') {
-                var array = value.toArray();
-                value = this.get('filter').get(key).toArray();
-                array.forEach(function(element) {
-                    value.splice(value.indexOf(element), 1);
-                });
-            }
-            this.get('filter').set(key, value);
+            this.get('filter').removeFilter(key, value);
         }
     }
 
--- a/cms/app-client/app/components/sorting-component.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/components/sorting-component.js	Fri Nov 04 19:03:25 2016 +0100
@@ -31,7 +31,7 @@
     actions: {
 
         setQueryParameters: function(id) {
-            this.get('filter').set('theme', id);
+            this.get('filter').setFilter('theme', id);
         },
 
         sortBy: function (type) {
@@ -52,4 +52,4 @@
 
     }
 
-});
\ No newline at end of file
+});
--- a/cms/app-client/app/components/visu-carto.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/components/visu-carto.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,6 +1,8 @@
 import Ember from 'ember';
 import AmCharts from 'ammaps';
 import ENV from 'app-client/config/environment';
+import * as commonConstants from 'corpus-common-addon/utils/constants';
+import * as commonUtils from 'corpus-common-addon/utils/utils';
 
 export default Ember.Component.extend({
 
@@ -15,15 +17,17 @@
     franceMapVar: AmCharts.maps.france2016Low,
     franceAreas: [],
 
-    locationQueryParam: '',
     dataProvider: {},
 
     color: 'rgba(113,132,141,.2)',
 
     filter: Ember.inject.service(),
     locationObserver: Ember.observer('filter.location', function() {
-        if(!this.get('filter').get('location')) {
+        if(this.get('filter').get('location') === null) {
             this.sendAction('setLocation', this.get('constants').GEONAMES['world']);
+            this.get('map').selectObject();
+        } else {
+            this.sendAction('setLocation', commonUtils.getGeonamesCode(this.get('filter').get('location')));
         }
     }),
 
@@ -71,6 +75,7 @@
         if (ENV.environment === 'development') {
             this.set('color', '#f3f3f3');
         }
+        this.get('service'); // gotcha cf: https://github.com/emberjs/ember.js/issues/10821
     },
 
     setFranceAreas: function() {
@@ -192,15 +197,12 @@
     },
 
     clickMapObject: function(event) {
-        this.set('locationQueryParam', event.mapObject.id);
-        this.sendAction('setLocation', event.mapObject.id);
-        this.get('filter').set('location', event.mapObject.id);
+        this.get('filter').setFilter('location', commonConstants.GEONAMES_BASE_URL + event.mapObject.id);
     },
 
-    homeButtonClicked: function(event) {
-        if(event.chart.dataProvider.map !== 'continentsLow') {
-            this.get('filter').set('location', null);
-        }
+    homeButtonClicked: function() {
+        this.get('filter').removeFilter('location', null);
+        this.sendAction('setLocation', this.get('constants').GEONAMES['world']);
     }
 
 });
--- a/cms/app-client/app/components/visu-chrono.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/components/visu-chrono.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,24 +1,90 @@
 import Ember from 'ember';
+import _ from 'lodash/lodash';
 
 export default Ember.Component.extend({
 
+    range: [],
+
+    decades: Ember.computed('range', function() {
+        var range = this.get('range');
+        return _.range(Math.floor(range[0]/10)*10, Math.floor(range[1]/10)*10, 10);
+    }),
+
     filter: Ember.inject.service(),
 
-    dateObserver: Ember.observer('filter.date', function() {
+    dateObserver: Ember.observer('date', function() {
         var self = this;
         this.$('li').removeClass('highlighted');
-        this.get('filter').get('date').forEach(function(date) {
+        this.get('date').forEach(function(date) {
             self.$('li#' + date).addClass('highlighted');
         });
     }),
 
+    date: Ember.computed('filter.date', {
+        get: function() {
+            const dates = this.get('filter').get('date');
+            console.log("get DATES", dates);
+            if(dates === null) {
+                return [];
+            }
+            const res = _.reduce(dates, function(res, d) {
+                let m = d.match(/^(\d+)(?:-(\d+))?$/);
+                if(m) {
+                    let start = parseInt(m[1]);
+                    let end = parseInt(m[2]);
+                    if(isNaN(end)) {
+                        res.push(parseInt(m[1]));
+                    } else {
+                        res = res.concat(_.range(start,end+1));
+                    }
+                }
+                return res;
+            }, []).sort();
+            console.log("GET DATE", res);
+            return res;
+        },
+        set: function(key, values) {
+            var srcDateList = _.clone(values).sort();
+            let start = null;
+            let end = null;
+            var pushValues = function(s,e,valuesList) {
+                if(s === e) {
+                    valuesList.push(s.toString());
+                } else {
+                    valuesList.push(s.toString()+"-"+e.toString());
+                }
+            };
+            let dateList = _.reduce(srcDateList, function(res, d, i) {
+                if(start === null) {
+                    start = end = d;
+                }
+                if(d > (end + 1)) {
+                    pushValues(start, end, res);
+                    start = end = d;
+                } else {
+                    end = d;
+                }
+                if(i === (srcDateList.length - 1)) {
+                    pushValues(start, end, res);
+                }
+                return res;
+          }, []);
+          console.log("SET DATE",key, values, dateList);
+          if(dateList.length === 0) {
+              dateList = null;
+          }
+          this.get('filter').set('date', dateList);
+          return srcDateList;
+      }
+    }),
+
     elementId: "chrono-table",
 
     didInsertElement: function(){
         var self = this;
 
-        if (this.get('filter').get('date') !== null){
-            this.highlightQuery(this.get('filter').get('date'));
+        if (this.get('date') !== null){
+            this.highlightQuery(this.get('date'));
         }
 
         var isMouseDown = false,
@@ -29,7 +95,7 @@
         Ember.$("#chrono-table li").mousedown(function (event) {
             // Prevent right click selection.
             if(event.button === 0) {
-                
+
                 isMouseDown = true;
                 var element = parseInt(Ember.$(this).attr('id'));
                 var elements = [element];
@@ -43,9 +109,9 @@
                         } else {
                             break;
                         }
-                    }    
+                    }
                 }
-                var dates = self.get('filter').get('date').toArray();
+                var dates = self.get('date');
                 var index = dates.indexOf(element);
                 if((!event.shiftKey && index === -1) || (event.shiftKey && didHighlight)) {
                     dates = dates.concat(elements);
@@ -54,14 +120,12 @@
                     elements.forEach(function(el) {
                         var id = dates.indexOf(el);
                         if((!event.shiftKey && index !== -1) || (event.shiftKey && id !== -1)) {
-                            dates.splice(id, 1);    
+                            dates.splice(id, 1);
                         }
                     });
                     isHighlighted = false;
                 }
-                // Ascending sorting.
-                dates.sort(function(a, b) { return a - b; });
-                self.get('filter').set('date', dates);
+                self.set('date', dates);
                 didHighlight = isHighlighted;
                 previousElement = element;
                 // Prevent text selection.
@@ -71,14 +135,14 @@
             if (isMouseDown) {
                 var element = parseInt(Ember.$(this).attr('id'));
                 if(Ember.$(this).hasClass("highlighted") !== isHighlighted) {
-                    var dates = self.get('filter').get('date').toArray();
+                    var dates = self.get('date');
                     var index = dates.indexOf(element);
                     if(index === -1) {
                         dates.push(element);
                     } else {
                         dates.splice(index, 1);
                     }
-                    self.get('filter').set('date', dates);
+                    self.set('date', dates);
                 }
                 previousElement = element;
             }
@@ -92,6 +156,10 @@
     },
 
     highlightQuery: function(dates){
+        console.log("highlightQuery", dates);
+        if(dates === null) {
+            return;
+        }
         dates.map(function(date){
             Ember.$("#" + date).toggleClass("highlighted", true);
         });
@@ -102,7 +170,7 @@
         selectDecade: function(decade) {
             var array = [];
             var next = decade + 10;
-            var dates = this.get('filter').get('date');
+            var dates = this.get('date');
             while(decade < next) {
                 array.push(decade);
                 decade ++;
@@ -112,14 +180,12 @@
                     return dates.indexOf(value) === -1;
                 });
                 array = dates.concat(array);
-                // Ascending sorting.
-                array.sort(function(a, b) { return a - b; });
             } else {
                 array = dates.filter(function(value) {
                     return array.indexOf(value) === -1;
                 });
             }
-            this.get('filter').set('date', array);
+            this.set('date', array);
         }
 
     }
--- a/cms/app-client/app/components/visu-langues.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/components/visu-langues.js	Fri Nov 04 19:03:25 2016 +0100
@@ -210,7 +210,7 @@
 
                 function selectHandler (d){
                     if (d.id){
-                        self.get('filter').set('language', d.id);
+                        self.get('filter').setFilter('language', d.id);
                     }
                 }
 
--- a/cms/app-client/app/controllers/application.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/controllers/application.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,99 +1,32 @@
 import Ember from 'ember';
+import _ from 'lodash/lodash';
 
 export default Ember.Controller.extend({
 
     player: Ember.inject.service(),
+    filter: Ember.inject.service(),
+
 
     playerVideoscreenObserver: Ember.observer('player.videoscreen', function() {
         Ember.$('body').toggleClass('videoscreen', this.get('player').get('videoscreen'));
     }),
 
-    queryParams: ['location', 'date', 'notice', {
-        language: 'language',
-        discourse: 'discourse',
-        theme: 'theme'
-    }],
-
-    date: [],
-    discourse: null,
-    language: null,
-    location: null,
-    theme: null,
-
-    filter: Ember.inject.service(),
-    setFilter: Ember.observer('discourse', 'language', 'location', 'theme', function(sender, key) {
-        if(this.get('filter').get(key) !== this.get(key)) {
-            this.get('filter').set(key, this.get(key));    
-        }
-    }),
-    filterObserver: Ember.observer('filter.discourse', 'filter.language', 'filter.location', 'filter.theme', function(sender, key) {
-        key = key.split('.').pop();
-        this.set(key, this.get('filter').get(key));
+    // queryParams: ['location', 'date', 'notice', 'language', 'discourse', 'theme'],
+    // queryParams: Ember.computed('filter.queryParams', 'notice', function() {
+    //     const queryParamsList = this.get('filter').get('queryParams');
+    //     return _.merge(queryParamsList, {'notice' : this.get('notice')});
+    // }),
+    queryParams : Ember.computed('filter.queryParams', function() {
+        var res = _.clone(this.get('filter').get('queryParams'));
+        res.push('notice');
+        return res;
     }),
 
-    setDateFilter: Ember.observer('date', function(sender, key) {
-        var intervals = [];
-        this.get(key).forEach(function(interval) {
-            interval = interval.split('-');
-            if(interval.length > 1) {
-                var array = [];
-                for(var i = interval[0]; i <= interval[1]; i++) {
-                    array.push(parseInt(i));
-                }
-                interval = array;
-            } else {
-                interval = parseInt(interval);
-            }
-            intervals = intervals.concat(interval);
-        });
-        this.get('filter').set(key, intervals);
-    }),
-    dateFilterObserver: Ember.observer('filter.date', function(sender, key) {
-        key = key.split('.').pop();
-        var intervals = [];
-        this.get('filter').get('date').forEach(function(date) {
-            var isInterval = false;
-            intervals.forEach(function(interval) {
-                if(interval.length && (interval.includes(date + 1) || interval.includes(date - 1))) {
-                    interval.push(date);
-                    isInterval = true;
-                }
-            });
-            if (!isInterval) {
-                intervals.push([date]);
-            }
-        });
-        intervals.forEach(function(interval, index) {
-            if(interval.length > 1) {
-               intervals[index] =  interval.shift() + '-' + interval.pop();
-            } else {
-               intervals[index] =  interval.toString();
-            }
-        });
-        if(!this.arraysEqual(this.get('date').toArray(), intervals)) {
-            this.set('date', intervals);
-        }
-    }),
-
-    arraysEqual: function(a, b) {
-        if (a === b) {
-            return true;
-        }
-        if (a == null || b == null) {
-            return false;
-        }
-        if (a.length !== b.length) {
-            return false;
-        }
-        for(var i = 0; i < a.length; ++i) {
-            if(a[i] !== b[i]) {
-                return false;
-            }
-        }
-        return true;
-    },
-
-
+    location: Ember.computed.alias('filter.location'),
+    date: Ember.computed.alias('filter.date'),
+    language: Ember.computed.alias('filter.language'),
+    discourse: Ember.computed.alias('filter.discourse'),
+    theme: Ember.computed.alias('filter.theme'),
 
     itemObserver: Ember.observer('player.item', function() {
         var self = this;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cms/app-client/app/helpers/contains.js	Fri Nov 04 19:03:25 2016 +0100
@@ -0,0 +1,8 @@
+import Ember from 'ember';
+import _ from 'lodash/lodash';
+
+export function contains(params) {
+    return _.contains(params[0], params[1]);
+}
+
+export default Ember.Helper.helper(contains);
--- a/cms/app-client/app/helpers/interval.js	Thu Nov 03 11:14:41 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-import Ember from 'ember';
-
-export function interval(params) {
-    var dates = params[0].slice();
-    return dates.shift() + ( dates.length ? '-' + dates.pop() : '' );
-}
-
-export default Ember.Helper.helper(interval);
--- a/cms/app-client/app/helpers/is-checked.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/helpers/is-checked.js	Fri Nov 04 19:03:25 2016 +0100
@@ -2,6 +2,9 @@
 
 export function isChecked(params) {
     var dates = params[0];
+    if(dates == null) {
+        return false;
+    }
 
     var decade = [];
     var next = params[1] + 10;
--- a/cms/app-client/app/helpers/is-indeterminate.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/helpers/is-indeterminate.js	Fri Nov 04 19:03:25 2016 +0100
@@ -2,6 +2,9 @@
 
 export function isIndeterminate(params) {
     var dates = params[0];
+    if(dates == null) {
+        return false;
+    }
 
     var decade = [];
     var next = params[1] + 10;
--- a/cms/app-client/app/routes/application.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/routes/application.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,7 +1,9 @@
 import Ember from 'ember';
+import _ from 'lodash/lodash';
 
 export default Ember.Route.extend({
 
+    filter: Ember.inject.service(),
     page: 1,
     limit: 10,
 
@@ -22,13 +24,6 @@
         return promise;
     }),
 
-    serializeQueryParam: function(value, urlKey) {
-        if (urlKey === 'date') {
-            return value;
-        }
-        return '' + value;
-    },
-
     setupController: function(controller) {
         this._super(...arguments);
         controller.set('page', this.get('page'));
@@ -36,17 +31,37 @@
         controller.set('documents', this.get('documents'));
     },
 
-    deserializeQueryParam: function(value, urlKey) {
-        if (urlKey === 'date') {
-            var array = [];
-            for (var i = 0; i < value.length; i++) {
-                array.push(value[i]);
-            }
-            return array;
+    /**
+      Serializes value of the query parameter based on defaultValueType
+      @method serializeQueryParam
+      @param {Object} value
+      @param {String} urlKey
+      @param {String} defaultValueType
+      @private
+    */
+    serializeQueryParam(value, urlKey, defaultValueType) {
+        if(_.contains(this.get('filter').get('queryParams'), urlKey)) {
+            return this.get('filter').serializeQueryParam(value, urlKey, defaultValueType);
         }
-        return value;
+        return this._super(value, urlKey, defaultValueType);
     },
 
+    /**
+      Deserializes value of the query parameter based on defaultValueType
+      @method deserializeQueryParam
+      @param {Object} value
+      @param {String} urlKey
+      @param {String} defaultValueType
+      @private
+    */
+    deserializeQueryParam(value, urlKey, defaultValueType) {
+        if(_.contains(this.get('filter').get('queryParams'), urlKey)) {
+            return this.get('filter').deserializeQueryParam(value, urlKey, defaultValueType);
+        }
+        return this._super(value, urlKey, defaultValueType);
+    },
+
+
     actions: {
 
         setPageQueryparams: function(type) {
@@ -61,20 +76,10 @@
             this.propertyDidChange('page');
         },
 
+
         willTransition: function() {
             // Prevent navigation from removing query parameters
-            var _this = this,
-                queryParams = {};
-            this.controller.get('queryParams').map(function(parameter){
-                if(typeof parameter === 'object') {
-                    Object.keys(parameter).forEach(function(value) {
-                        queryParams[value] = _this.controller.get(value);
-                    });
-                } else {
-                    queryParams[parameter] = _this.controller.get(parameter);
-                }
-            });
-            this.transitionTo({ queryParams: queryParams });
+            this.transitionTo({ queryParams: this.controller.get('queryParams') });
         },
 
         didTransition: function() {
--- a/cms/app-client/app/routes/tabs/chrono.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/routes/tabs/chrono.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,8 +1,15 @@
 import Ember from 'ember';
+import RSVP from 'rsvp';
 
 export default Ember.Route.extend({
 
-	player: Ember.inject.service(),
+	  player: Ember.inject.service(),
+
+    model: function() {
+        return RSVP.hash({
+            range: [1948, 2015]
+        });
+    },
 
     activate: function() {
         this.get('player').set('window', false);
--- a/cms/app-client/app/services/filter.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/services/filter.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,11 +1,94 @@
 import Ember from 'ember';
+import _ from 'lodash/lodash';
+
+const FILTER_QUERY_PARAMS = ['date', 'discourse', 'language', 'location', 'theme'];
 
 export default Ember.Service.extend({
 
-    dates: [],
+    date: null,
     discourse: null,
     language: null,
     location: null,
-    theme: null
+    theme: null,
+
+    queryParams: FILTER_QUERY_PARAMS,
+
+    queryParamsValues: Ember.computed('date', 'discourse', 'language', 'location', 'theme', function() {
+        return this.getProperties(FILTER_QUERY_PARAMS);
+    }),
+    removeFilter: function(filter, value) {
+        switch(filter) {
+            case 'date':
+            case 'discourse':
+            case 'theme':
+                let values = this.get(filter);
+                if(values === null) {
+                    return;
+                }
+                values = _.without(values, value);
+                if(values.length === 0) {
+                    this.set(filter, null);
+                } else {
+                    this.set(filter, values);
+                }
+                break;
+            case 'language':
+            case 'location':
+                this.set(filter, null);
+                break;
+        }
+    },
+    setFilter: function(filter, value) {
+        switch(filter) {
+            case 'date':
+            case 'discourse':
+            case 'theme':
+                let filterValues = this.get(filter);
+                console.log("filterValues", filterValues);
+                if(filterValues === null) {
+                    filterValues = [];
+                }
+                if(!_.contains(filterValues, value)) {
+                    filterValues = filterValues.slice(0);
+                    filterValues.push(value);
+                    filterValues.sort();
+                    this.set(filter, filterValues);
+                }
+                break;
+            case 'language':
+            case 'location':
+                if(this.get(filter) !== value) {
+                    this.set(filter, value);
+                }
+                break;
+        }
+    },
+    serializeQueryParam(value/*, urlKey, defaultValueType*/) {
+        if (_.isArray(value)) {
+          return value;
+        }
+        return `${value}`;
+    },
+    deserializeQueryParam(value, urlKey, defaultValueType) {
+        if (urlKey === 'date' || urlKey === 'theme' || urlKey === 'discourse') {
+            if(_.isArray(value)) {
+                return value;
+            } else {
+                var parsedValue = Ember.A(JSON.parse(value));
+                if (_.isArray(parsedValue)) {
+                    return parsedValue;
+                } else {
+                    return Ember.A([ parsedValue, ]);
+                }
+            }
+        } else if (defaultValueType === 'boolean') {
+            return (value === 'true') ? true : false;
+        } else if (defaultValueType === 'number') {
+            return (Number(value)).valueOf();
+        } else if (defaultValueType === 'array') {
+            return Ember.A(JSON.parse(value));
+        }
+        return value;
+    }
 
 });
--- a/cms/app-client/app/templates/components/filter-component.hbs	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/templates/components/filter-component.hbs	Fri Nov 04 19:03:25 2016 +0100
@@ -7,15 +7,19 @@
     <li class="filter language"><i class="fa fa-language"></i>{{doc-language class="label" url=filter.language}}<span class="fa fa-times"{{action 'removeFilter' 'language'}}>Remove</span></li>
 	{{/if}}
 	{{#if filter.discourse}}
-	<li class="filter discourse"><i class="fa fa-comments"></i>{{doc-subject url=filter.discourse}}<span class="fa fa-times"{{action 'removeFilter' 'discourse'}}>Remove</span></li>
+    {{#each filter.discourse as |discourse| }}
+	  <li class="filter discourse"><i class="fa fa-comments"></i>{{doc-subject url=discourse}}<span class="fa fa-times"{{action 'removeFilter' 'discourse' discourse}}>Remove</span></li>
+    {{/each}}
 	{{/if}}
 	{{#if filter.date}}
-    {{#each intervals as |interval| }}
-    <li class="filter date" data-dates="{{interval}}"><i class="fa fa-clock-o"></i>{{interval interval}}<span class="fa fa-times"{{action 'removeFilter' 'date' interval}}>Remove</span></li>
+    {{#each filter.date as |interval| }}
+    <li class="filter date" data-dates="{{interval}}"><i class="fa fa-clock-o"></i>{{interval}}<span class="fa fa-times"{{action 'removeFilter' 'date' interval}}>Remove</span></li>
     {{/each}}
 	{{/if}}
 	{{#if filter.theme}}
-    <li class="filter theme"><i class="fa fa-bookmark"></i>{{doc-subject url=filter.theme}}<span class="fa fa-times"{{action 'removeFilter' 'theme'}}>Remove</span></li>
+    {{#each filter.theme as |theme| }}
+    <li class="filter theme"><i class="fa fa-bookmark"></i>{{doc-subject url=theme}}<span class="fa fa-times"{{action 'removeFilter' 'theme' theme}}>Remove</span></li>
+    {{/each}}
 	{{/if}}
 </ul>
 {{/if}}
\ No newline at end of file
--- a/cms/app-client/app/templates/components/sorting-component.hbs	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/templates/components/sorting-component.hbs	Fri Nov 04 19:03:25 2016 +0100
@@ -9,7 +9,7 @@
 <div class="tags{{if more " hasMore"}}">
     <ul>
     {{#each themes as |theme|}}
-        <li {{ action 'setQueryParameters' theme.id }} class="{{ popularity theme.count minimum maximum }}{{if (eq filter.theme theme.id) ' selected'}}">{{ theme.label }} ({{ theme.count }})</li>
+        <li {{ action 'setQueryParameters' theme.id }} class="{{ popularity theme.count minimum maximum }}{{if (contains filter.theme theme.id) ' selected'}}">{{ theme.label }} ({{ theme.count }})</li>
     {{/each }}
     </ul>
     {{#if more}}<button class="more" {{ action 'loadMore' }}>Voir plus</button>{{/if}}
--- a/cms/app-client/app/templates/components/visu-chrono.hbs	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/templates/components/visu-chrono.hbs	Fri Nov 04 19:03:25 2016 +0100
@@ -1,5 +1,5 @@
 <div class="interval">
-    {{input type="checkbox" checked=(is-checked filter.date 1960) indeterminate=(is-indeterminate filter.date 1960) click=(action 'selectDecade' 1960)}}
+    {{input type="checkbox" checked=(is-checked date 1960) indeterminate=(is-indeterminate date 1960) click=(action 'selectDecade' 1960)}}
     <ul class="1960">
         <li title="1960" id="1960">1960</li>
         <li title="1961" id="1961">1961</li>
@@ -14,7 +14,7 @@
     </ul>
 </div>
 <div class="interval">
-    {{input type="checkbox" checked=(is-checked filter.date 1970) indeterminate=(is-indeterminate filter.date 1970) click=(action 'selectDecade' 1970)}}
+    {{input type="checkbox" checked=(is-checked date 1970) indeterminate=(is-indeterminate date 1970) click=(action 'selectDecade' 1970)}}
     <ul class="1970">
         <li title="1970" id="1970">1970</li>
         <li title="1971" id="1971">1971</li>
@@ -29,7 +29,7 @@
     </ul>
 </div>
 <div class="interval">
-    {{input type="checkbox" checked=(is-checked filter.date 1980) indeterminate=(is-indeterminate filter.date 1980) click=(action 'selectDecade' 1980)}}
+    {{input type="checkbox" checked=(is-checked date 1980) indeterminate=(is-indeterminate date 1980) click=(action 'selectDecade' 1980)}}
     <ul class="1980">
         <li title="1980" id="1980">1980</li>
         <li title="1981" id="1981">1981</li>
@@ -44,7 +44,7 @@
     </ul>
 </div>
 <div class="interval">
-    {{input type="checkbox" checked=(is-checked filter.date 1990) indeterminate=(is-indeterminate filter.date 1990) click=(action 'selectDecade' 1990)}}
+    {{input type="checkbox" checked=(is-checked date 1990) indeterminate=(is-indeterminate date 1990) click=(action 'selectDecade' 1990)}}
     <ul class="1990">
         <li title="1990" id="1990">1990</li>
         <li title="1991" id="1991">1991</li>
@@ -74,7 +74,7 @@
     </ul>
 </div>
 <div class="interval">
-    {{input type="checkbox" checked=(is-checked filter.date 2010) indeterminate=(is-indeterminate filter.date 2010) click=(action 'selectDecade' 2010)}}
+    {{input type="checkbox" checked=(is-checked date 2010) indeterminate=(is-indeterminate filter.date 2010) click=(action 'selectDecade' 2010)}}
     <ul class="2010">
         <li title="2010" id="2010">2010</li>
         <li title="2011" id="2011">2011</li>
--- a/cms/app-client/app/templates/tabs/carto.hbs	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/templates/tabs/carto.hbs	Fri Nov 04 19:03:25 2016 +0100
@@ -1,3 +1,3 @@
 <div id="tabs-carto">
-    {{ visu-carto geostats=model locationQueryParam=modelQueryParam setLocation='setModelQueryParam' }}
+    {{ visu-carto geostats=model setLocation='setModelQueryParam' }}
 </div>
\ No newline at end of file
--- a/cms/app-client/app/templates/tabs/chrono.hbs	Thu Nov 03 11:14:41 2016 +0100
+++ b/cms/app-client/app/templates/tabs/chrono.hbs	Fri Nov 04 19:03:25 2016 +0100
@@ -1,4 +1,4 @@
 <div id="tabs-chrono">
 	<p>Cocher les cases de gauche pour sélectionner des décennies ou cliquez pour sélectionner une année et glisser pour en selectionner plusieurs.</p>
-    {{ visu-chrono }}
+    {{ visu-chrono range=model.range }}
 </div>
--- a/common/corpus-common-addon/addon/components/doc-location.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/common/corpus-common-addon/addon/components/doc-location.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,5 +1,5 @@
 import Ember from 'ember';
-import * as constants from 'corpus-common-addon/utils/constants';
+import * as utils from 'corpus-common-addon/utils/utils';
 import layout from '../templates/components/doc-location';
 
 export default Ember.Component.extend({
@@ -16,14 +16,7 @@
   })),
 
   code: Ember.computed('url', function () {
-    let rawLocation = this.get('url');
-    const m = rawLocation.match(constants.GEONAMES_BASE_URLS);
-
-    if (m) {
-      rawLocation = rawLocation.slice(m[0].length);
-    }
-
-    return rawLocation.replace(/\/+$/, '');
+    return utils.getGeonamesCode(this.get('url'));
   }),
   isLocationLink: Ember.computed.match('url', /^http:\/\//)
 
--- a/common/corpus-common-addon/addon/utils/constants.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/common/corpus-common-addon/addon/utils/constants.js	Fri Nov 04 19:03:25 2016 +0100
@@ -11,6 +11,7 @@
 export const BNF_ARK_BASE_ID = 'ark:/12148/';
 
 export const GEONAMES_BASE_URLS = 'https?:\\/\\/(?:www|sws)\\.geonames\\.org/';
+export const GEONAMES_BASE_URL = 'http://sws.geonames.org/';
 
 export const CORPUS_DISCOURSE_TYPES = {
   'http://ark.bnf.fr/ark:/12148/cb12083158d': 'argumentation',
--- a/common/corpus-common-addon/addon/utils/utils.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/common/corpus-common-addon/addon/utils/utils.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,22 +1,42 @@
+/* eslint-disable no-magic-numbers, no-ternary */
+
 import * as constants from 'corpus-common-addon/utils/constants';
 import _ from 'lodash/lodash';
 
 export function isLexvoLink(s) {
   return s && typeof s === 'string' && s.startsWith(constants.LEXVO_BASE_URL);
-};
+}
 
 export function isBnfLink(s) {
-  return s && typeof s === 'string' && (s.startsWith(constants.BNF_BASE_URL) || s.startsWith(constants.BNF_ARK_BASE_URL) || s.startsWith(constants.BNF_ARK_BASE_ID));
-};
+  return s &&
+    typeof s === 'string' &&
+    (s.startsWith(constants.BNF_BASE_URL) ||
+    s.startsWith(constants.BNF_ARK_BASE_URL) ||
+    s.startsWith(constants.BNF_ARK_BASE_ID));
+}
 
 export function isArkBnfLink(s) {
-  return s && typeof s === 'string' && (s.startsWith(constants.BNF_BASE_URL + constants.BNF_ARK_BASE_ID) || s.startsWith(constants.BNF_ARK_BASE_URL + constants.BNF_ARK_BASE_ID));
-};
+  return s &&
+    typeof s === 'string' &&
+    (s.startsWith(constants.BNF_BASE_URL + constants.BNF_ARK_BASE_ID) ||
+    s.startsWith(constants.BNF_ARK_BASE_URL + constants.BNF_ARK_BASE_ID));
+}
 
 export function isGeonamesLink(s) {
   return (typeof s === 'string') && (s.match(constants.GEONAMES_BASE_URLS) !== null);
 }
 
+export function getGeonamesCode(s) {
+  const m = s.match(constants.GEONAMES_BASE_URLS);
+  let code = s;
+
+  if (m) {
+    code = s.slice(m[0].length);
+  }
+
+  return code.replace(/\/+$/, '');
+}
+
 export function switchArkBnfLink(s) {
   if (!s) {
     return s;
@@ -25,17 +45,18 @@
     return constants.BNF_ARK_BASE_URL + s.slice(constants.BNF_BASE_URL.length);
   } else if (typeof s === 'string' && s.startsWith(constants.BNF_ARK_BASE_URL + constants.BNF_ARK_BASE_ID)) {
     return constants.BNF_BASE_URL + s.slice(constants.BNF_ARK_BASE_URL.length);
-  } else {
-    return s;
   }
-};
+
+  return s;
+}
 
 export function calculateBnfArkControlChar(id) {
   if (!id) {
     return null;
   }
-  var sum = _.reduce(id, function(s, c, i) {
+  const sum = _.reduce(id, function (s, c, i) {
     return s + (i + 1) * (c in constants.NOID_CHARS_POS ? constants.NOID_CHARS_POS[c] : 0);
   }, 0);
+
   return constants.NOID_CHARS[sum % constants.NOID_CHARS.length];
-};
+}
--- a/common/corpus-common-addon/app/services/geonames-resolver.js	Thu Nov 03 11:14:41 2016 +0100
+++ b/common/corpus-common-addon/app/services/geonames-resolver.js	Fri Nov 04 19:03:25 2016 +0100
@@ -1,4 +1,4 @@
-import * as constants from 'corpus-common-addon/utils/constants';
+import * as utils from 'corpus-common-addon/utils/utils';
 
 import ResolverService from 'corpus-common-addon/services/resolver-service';
 
@@ -8,21 +8,20 @@
   apiPath: '/api/v1/resolvers/geonames/',
   resDocRoot: 'geonamesids',
 
-  init() {
-    this._super(...arguments);
+  init(...args) {
+    this._super(args);
+  },
+
+  processId: function (id) {
+    return utils.getGeonamesCode(id);
   },
 
-  processId: function(id) {
-    var geonamesId = id;
-    var m = id.match(constants.GEONAMES_BASE_URLS);
-    if (m) {
-      geonamesId = id.slice(m[0].length);
-    }
-    return geonamesId.replace(/\/+$/, '');
+  getReturnDictKey: function (id) {
+    return id;
   },
 
-  getReturnDictKey: function(id) { return id; },
-
-  getLabel: function(id) { return this.getName(id); }
+  getLabel: function (id) {
+    return this.getName(id);
+  }
 
 });
--- a/common/corpus-common-addon/package.json	Thu Nov 03 11:14:41 2016 +0100
+++ b/common/corpus-common-addon/package.json	Fri Nov 04 19:03:25 2016 +0100
@@ -9,7 +9,8 @@
   "scripts": {
     "build": "ember build",
     "start": "ember server",
-    "test": "ember try:testall"
+    "test": "ember try:testall",
+    "set-version": "bash -c 'sed -i \"\" -e \"s/\\([:space:]*\\\"version\\\"[[:space:]]*\\:[[:space:]]*\\\"\\)[\\.0-9]*\\(\\\".*\\)/\\1${1}\\2/\" package.json' 0"
   },
   "repository": "",
   "engines": {